Skip to content

Commit

Permalink
reuse old sorting results when possible
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Dec 12, 2018
1 parent 749b42a commit 6f088be
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 39 deletions.
5 changes: 4 additions & 1 deletion src/internal/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,10 @@ export function createIndexArray(length: number) {
return new Uint32Array(length);
}

export function toIndexArray(arr: ISequence<number>): UIntTypedArray {
export function toIndexArray(arr: ISequence<number> | IndicesArray): UIntTypedArray {
if (arr instanceof Uint8Array || arr instanceof Uint16Array || arr instanceof Uint32Array) {
return arr.slice();
}
const l = arr.length;
if (l <= 255) {
return Uint8Array.from(arr);
Expand Down
125 changes: 91 additions & 34 deletions src/provider/LocalDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface ILocalDataProviderOptions {

interface ISortHelper {
group: IGroup;
rows: number[];
rows: IndicesArray;
}

/**
Expand Down Expand Up @@ -275,31 +275,87 @@ export default class LocalDataProvider extends ACommonDataProvider {
return {groups: [Object.assign({order}, defaultGroup)], index2pos};
}

private createSorter(ranking: Ranking, filter: ((d: IDataRow) => boolean) | null, isSortedBy: boolean) {
private createSorter(ranking: Ranking, filter: ((d: IDataRow) => boolean) | null, isSortedBy: boolean, needsFiltering: boolean, needsGrouping: boolean, needsSorting: boolean) {
const groups = new Map<string, ISortHelper>();
const lookups = isSortedBy ? new CompareLookup(this._data.length, ranking.toCompareValueType()) : undefined;
const groupOrder: ISortHelper[] = [];
let maxDataIndex = -1;

for (const r of this._dataRows) {
if (filter && !filter(r)) {
continue;
}
const group = ranking.grouper(r) || defaultGroup;
const lookups = isSortedBy && needsSorting ? new CompareLookup(this._data.length, ranking.toCompareValueType()) : undefined;

const pushGroup = (group: IGroup, r: IDataRow) => {
const groupKey = group.name.toLowerCase();
if (lookups) {
lookups.push(r.i, ranking.toCompareValue(r));
}
if (groups.has(groupKey)) {
groups.get(groupKey)!.rows.push(r.i);
} else {
groups.set(groupKey, {group, rows: [r.i]});
(<number[]>groups.get(groupKey)!.rows).push(r.i);
return;
}
if (maxDataIndex < r.i) {
maxDataIndex = r.i;
const s = {group, rows: [r.i]};
groups.set(groupKey, s);
groupOrder.push(s);
};

if (needsFiltering) {
// filter, group, sort
for (const r of this._dataRows) {
if (filter && !filter(r)) {
continue;
}
if (maxDataIndex < r.i) {
maxDataIndex = r.i;
}
if (lookups) {
lookups.push(r.i, ranking.toCompareValue(r));
}
pushGroup(ranking.grouper(r), r);
}

// some default sorting
groupOrder.sort((a, b) => a.group.name.toLowerCase().localeCompare(b.group.name.toLowerCase()));
return {maxDataIndex, lookups, groupOrder};
}

return {maxDataIndex, lookups, groups};
// reuse the existing groups

for (const before of ranking.getGroups()) {
const order = before.order;
const plain = Object.assign({}, before);
delete plain.order;

if (!needsGrouping) {
// reuse in full
groupOrder.push({group: plain, rows: order});

if (!lookups) {
continue;
}
// sort
for (let i = 0; i < order.length; ++i) {
if (maxDataIndex < i) {
maxDataIndex = i;
}
const r = this._dataRows[i];
lookups.push(r.i, ranking.toCompareValue(r));
}
continue;
}

// group, [sort]
for (let i = 0; i < order.length; ++i) {
if (maxDataIndex < i) {
maxDataIndex = i;
}
const r = this._dataRows[i];
if (lookups) {
lookups.push(r.i, ranking.toCompareValue(r));
}
pushGroup(needsGrouping ? ranking.grouper(r) : plain, r);
}
}

if (needsGrouping) {
// some default sorting
groupOrder.sort((a, b) => a.group.name.toLowerCase().localeCompare(b.group.name.toLowerCase()));
}
return {maxDataIndex, lookups, groupOrder};
}

private sortGroup(g: ISortHelper, i: number, ranking: Ranking, lookups: CompareLookup | undefined, groupLookup: CompareLookup | undefined, singleGroup: boolean): Promise<IOrderedGroup> {
Expand All @@ -323,9 +379,7 @@ export default class LocalDataProvider extends ACommonDataProvider {

private sortGroups(groups: IOrderedGroup[], groupLookup: CompareLookup | undefined) {
// sort groups
if (!groupLookup) {
groups.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
} else {
if (groupLookup) {
const groupIndices = groups.map((_, i) => i);
sortComplex(groupIndices, groupLookup.sortOrders);
groups = groupIndices.map((i) => groups[i]);
Expand Down Expand Up @@ -357,9 +411,14 @@ export default class LocalDataProvider extends ACommonDataProvider {

const filter = this.resolveFilter(ranking);

if (reasons.has(EDirtyReason.UNKNOWN) || reasons.has(EDirtyReason.FILTER_CHANGED)) {
const needsFiltering = reasons.has(EDirtyReason.UNKNOWN) || reasons.has(EDirtyReason.FILTER_CHANGED);
const needsGrouping = needsFiltering || reasons.has(EDirtyReason.GROUP_CRITERIA_CHANGED) || reasons.has(EDirtyReason.GROUP_CRITERIA_DIRTY);
const needsSorting = needsGrouping || reasons.has(EDirtyReason.SORT_CRITERIA_CHANGED) || reasons.has(EDirtyReason.SORT_CRITERIA_DIRTY);
const needsGroupSorting = needsSorting || reasons.has(EDirtyReason.GROUP_SORT_CRITERIA_CHANGED) || reasons.has(EDirtyReason.GROUP_SORT_CRITERIA_DIRTY);

if (needsFiltering) {
this.tasks.dirtyRanking(ranking, 'summary');
} else if (reasons.has(EDirtyReason.GROUP_CRITERIA_CHANGED) || reasons.has(EDirtyReason.GROUP_CRITERIA_DIRTY)) {
} else if (needsGrouping) {
this.tasks.dirtyRanking(ranking, 'group');
}
// otherwise the summary and group summaries should still be valid
Expand All @@ -377,33 +436,31 @@ export default class LocalDataProvider extends ACommonDataProvider {
return this.noSorting(ranking);
}

// TODO not required if: sort criteria changed, group sort criteria changed
const {maxDataIndex, lookups, groups} = this.createSorter(ranking, filter, isSortedBy);
const {maxDataIndex, lookups, groupOrder} = this.createSorter(ranking, filter, isSortedBy, needsFiltering, needsGrouping, needsSorting);

if (groups.size === 0) {
if (groupOrder.length === 0) {
return {groups: [], index2pos: []};
}

const ggroups = Array.from(groups.values());
this.tasks.preCompute(ranking, ggroups);
this.tasks.preCompute(ranking, groupOrder);


if (groups.size === 1) {
const g = ggroups[0]!;
if (groupOrder.length === 1) {
const g = groupOrder[0]!;

// TODO not required if: group sort criteria changed
// not required if: group sort criteria changed -> lookups will be none
return this.sortGroup(g, 0, ranking, lookups, undefined, true).then((group) => {
return this.index2pos([group], maxDataIndex);
});
}

const groupLookup = isGroupedSortedBy ? new CompareLookup(groups.size, ranking.toGroupCompareValueType()) : undefined;
const groupLookup = isGroupedSortedBy && needsGroupSorting ? new CompareLookup(groupOrder.length, ranking.toGroupCompareValueType()) : undefined;

return Promise.all(ggroups.map((g, i) => {
// TODO not required if: group sort criteria changed
return Promise.all(groupOrder.map((g, i) => {
// not required if: group sort criteria changed -> lookups will be none
return this.sortGroup(g, i, ranking, lookups, groupLookup, false);
})).then((groups) => {
// TODO not required if: sort criteria changed
// not required if: sort criteria changed -> groupLookup will be none
const sortedGroups = this.sortGroups(groups, groupLookup);
return this.index2pos(sortedGroups, maxDataIndex);
});
Expand Down
6 changes: 3 additions & 3 deletions src/provider/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class CompareLookup {
}
}

function sort(indices: number[], _singleCall: boolean, lookups?: CompareLookup) {
function sort(indices: IndicesArray, _singleCall: boolean, lookups?: CompareLookup) {
const order = toIndexArray(indices);
if (lookups) {
sortComplex(order, lookups.sortOrders);
Expand All @@ -167,7 +167,7 @@ function sort(indices: number[], _singleCall: boolean, lookups?: CompareLookup)
}

export interface ISortWorker {
sort(indices: number[], singleCall: boolean, lookups?: CompareLookup): Promise<IndicesArray>;
sort(indices: IndicesArray, singleCall: boolean, lookups?: CompareLookup): Promise<IndicesArray>;
terminate(): void;
}

Expand Down Expand Up @@ -222,7 +222,7 @@ export class WorkerSortWorker implements ISortWorker {
}
}

sort(indices: number[], singleCall: boolean, lookups?: CompareLookup) {
sort(indices: IndicesArray, singleCall: boolean, lookups?: CompareLookup) {

if (!lookups || indices.length < SHOULD_USE_WORKER) {
// no thread needed
Expand Down
1 change: 0 additions & 1 deletion src/ui/header.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// import Popper from 'popper.js';
import {MIN_LABEL_WIDTH} from '../config';
import {equalArrays} from '../internal';
import {dragAble, dropAble, hasDnDType, IDropResult} from '../internal/dnd';
Expand Down

0 comments on commit 6f088be

Please sign in to comment.