minimize sorting

This commit is contained in:
mertalev 2025-05-07 18:11:56 -04:00
parent c9728a107e
commit fa216b55d7
No known key found for this signature in database
GPG key ID: DF6ABC77AAD98C95

View file

@ -24,11 +24,13 @@ import { SvelteSet } from 'svelte/reactivity';
import { get, writable, type Unsubscriber } from 'svelte/store'; import { get, writable, type Unsubscriber } from 'svelte/store';
import { handleError } from '../utils/handle-error'; import { handleError } from '../utils/handle-error';
import { websocketEvents } from './websocket'; import { websocketEvents } from './websocket';
const { const {
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM }, TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
} = TUNABLES; } = TUNABLES;
type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0]; type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0];
export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & { export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & {
timelineAlbumId?: string; timelineAlbumId?: string;
deferInit?: boolean; deferInit?: boolean;
@ -85,6 +87,7 @@ export type TimelineAsset = {
country: string | null; country: string | null;
people: string[]; people: string[];
}; };
class IntersectingAsset { class IntersectingAsset {
// --- public --- // --- public ---
readonly #group: AssetDateGroup; readonly #group: AssetDateGroup;
@ -116,9 +119,11 @@ class IntersectingAsset {
this.asset = asset; this.asset = asset;
} }
} }
type AssetOperation = (asset: TimelineAsset) => { remove: boolean }; type AssetOperation = (asset: TimelineAsset) => { remove: boolean };
type MoveAsset = { asset: TimelineAsset; year: number; month: number }; type MoveAsset = { asset: TimelineAsset; year: number; month: number };
export class AssetDateGroup { export class AssetDateGroup {
// --- public // --- public
readonly bucket: AssetBucket; readonly bucket: AssetBucket;
@ -161,6 +166,7 @@ export class AssetDateGroup {
getFirstAsset() { getFirstAsset() {
return this.intersetingAssets[0]?.asset; return this.intersetingAssets[0]?.asset;
} }
getRandomAsset() { getRandomAsset() {
const random = Math.floor(Math.random() * this.intersetingAssets.length); const random = Math.floor(Math.random() * this.intersetingAssets.length);
return this.intersetingAssets[random]; return this.intersetingAssets[random];
@ -238,6 +244,7 @@ export interface Viewport {
width: number; width: number;
height: number; height: number;
} }
export type ViewportXY = Viewport & { export type ViewportXY = Viewport & {
x: number; x: number;
y: number; y: number;
@ -245,23 +252,47 @@ export type ViewportXY = Viewport & {
class AddContext { class AddContext {
lookupCache: { lookupCache: {
[dayOfMonth: number]: AssetDateGroup; [year: number]: { [month: number]: { [day: number]: AssetDateGroup } };
} = {}; } = {};
unprocessedAssets: TimelineAsset[] = []; unprocessedAssets: TimelineAsset[] = [];
changedDateGroups = new Set<AssetDateGroup>(); changedDateGroups = new Set<AssetDateGroup>();
newDateGroups = new Set<AssetDateGroup>(); newDateGroups = new Set<AssetDateGroup>();
sort(bucket: AssetBucket, sortOrder: AssetOrder = AssetOrder.Desc) {
getDateGroup(year: number, month: number, day: number): AssetDateGroup | undefined {
return this.lookupCache[year]?.[month]?.[day];
}
setDateGroup(dateGroup: AssetDateGroup, year: number, month: number, day: number) {
if (!this.lookupCache[year]) {
this.lookupCache[year] = {};
}
if (!this.lookupCache[year][month]) {
this.lookupCache[year][month] = {};
}
this.lookupCache[year][month][day] = dateGroup;
}
get existingDateGroups() {
return this.changedDateGroups.difference(this.newDateGroups);
}
get updatedBuckets() {
const updated = new Set<AssetBucket>();
for (const group of this.changedDateGroups) { for (const group of this.changedDateGroups) {
group.sortAssets(sortOrder); updated.add(group.bucket);
} }
return updated;
}
get bucketsWithNewDateGroups() {
const updated = new Set<AssetBucket>();
for (const group of this.newDateGroups) { for (const group of this.newDateGroups) {
group.sortAssets(sortOrder); updated.add(group.bucket);
}
if (this.newDateGroups.size > 0) {
bucket.sortDateGroups();
} }
return updated;
} }
} }
export class AssetBucket { export class AssetBucket {
// --- public --- // --- public ---
#intersecting: boolean = $state(false); #intersecting: boolean = $state(false);
@ -326,6 +357,7 @@ export class AssetBucket {
this.handleLoadError, this.handleLoadError,
); );
} }
set intersecting(newValue: boolean) { set intersecting(newValue: boolean) {
const old = this.#intersecting; const old = this.#intersecting;
if (old !== newValue) { if (old !== newValue) {
@ -449,7 +481,14 @@ export class AssetBucket {
this.addTimelineAsset(timelineAsset, addContext); this.addTimelineAsset(timelineAsset, addContext);
} }
addContext.sort(this, this.#sortOrder); for (const group of addContext.existingDateGroups) {
group.sortAssets(this.#sortOrder);
}
if (addContext.newDateGroups.size > 0) {
this.sortDateGroups();
}
return addContext.unprocessedAssets; return addContext.unprocessedAssets;
} }
@ -462,32 +501,29 @@ export class AssetBucket {
if (this.month === month && this.year === year) { if (this.month === month && this.year === year) {
const day = date.get('day'); const day = date.get('day');
let dateGroup: AssetDateGroup | undefined = addContext.lookupCache[day]; let dateGroup = addContext.getDateGroup(year, month, day);
if (!dateGroup) { if (!dateGroup) {
dateGroup = this.findDateGroupByDay(day); dateGroup = this.findDateGroupByDay(day);
if (dateGroup) { if (dateGroup) {
addContext.lookupCache[day] = dateGroup; addContext.setDateGroup(dateGroup, year, month, day);
} }
} }
if (dateGroup) { if (dateGroup) {
const intersectingAsset = new IntersectingAsset(dateGroup, timelineAsset); const intersectingAsset = new IntersectingAsset(dateGroup, timelineAsset);
if (dateGroup.intersetingAssets.some((a) => a.id === id)) {
console.error(`Ignoring attempt to add duplicate asset ${id} to ${dateGroup.groupTitle}`);
} else {
dateGroup.intersetingAssets.push(intersectingAsset); dateGroup.intersetingAssets.push(intersectingAsset);
addContext.changedDateGroups.add(dateGroup); addContext.changedDateGroups.add(dateGroup);
}
} else { } else {
dateGroup = new AssetDateGroup(this, this.dateGroups.length, date, day); dateGroup = new AssetDateGroup(this, this.dateGroups.length, date, day);
dateGroup.intersetingAssets.push(new IntersectingAsset(dateGroup, timelineAsset)); dateGroup.intersetingAssets.push(new IntersectingAsset(dateGroup, timelineAsset));
this.dateGroups.push(dateGroup); this.dateGroups.push(dateGroup);
addContext.lookupCache[day] = dateGroup; addContext.setDateGroup(dateGroup, year, month, day);
addContext.newDateGroups.add(dateGroup); addContext.newDateGroups.add(dateGroup);
} }
} else { } else {
addContext.unprocessedAssets.push(timelineAsset); addContext.unprocessedAssets.push(timelineAsset);
} }
} }
getRandomDateGroup() { getRandomDateGroup() {
const random = Math.floor(Math.random() * this.dateGroups.length); const random = Math.floor(Math.random() * this.dateGroups.length);
return this.dateGroups[random]; return this.dateGroups[random];
@ -536,6 +572,7 @@ export class AssetBucket {
} }
} }
} }
get bucketHeight() { get bucketHeight() {
return this.#bucketHeight; return this.#bucketHeight;
} }
@ -1031,6 +1068,7 @@ export class AssetStore {
rowWidth: Math.floor(viewportWidth), rowWidth: Math.floor(viewportWidth),
}; };
} }
#updateGeometry(bucket: AssetBucket, invalidateHeight: boolean) { #updateGeometry(bucket: AssetBucket, invalidateHeight: boolean) {
if (invalidateHeight) { if (invalidateHeight) {
bucket.isBucketHeightActual = false; bucket.isBucketHeightActual = false;
@ -1183,9 +1221,9 @@ export class AssetStore {
if (assets.length === 0) { if (assets.length === 0) {
return; return;
} }
const updatedBuckets = new Set<AssetBucket>();
const updatedDateGroups = new Set<AssetDateGroup>();
const addContext = new AddContext();
const bucketCount = this.buckets.length;
for (const asset of assets) { for (const asset of assets) {
const utc = DateTime.fromISO(asset.localDateTime).toUTC().startOf('month'); const utc = DateTime.fromISO(asset.localDateTime).toUTC().startOf('month');
const year = utc.get('year'); const year = utc.get('year');
@ -1196,21 +1234,24 @@ export class AssetStore {
bucket = new AssetBucket(this, utc, 1, this.#options.order); bucket = new AssetBucket(this, utc, 1, this.#options.order);
this.buckets.push(bucket); this.buckets.push(bucket);
} }
const addContext = new AddContext();
bucket.addTimelineAsset(asset, addContext); bucket.addTimelineAsset(asset, addContext);
addContext.sort(bucket, this.#options.order);
updatedBuckets.add(bucket);
} }
if (this.buckets.length !== bucketCount) {
this.buckets.sort((a, b) => { this.buckets.sort((a, b) => {
return a.year === b.year ? b.month - a.month : b.year - a.year; return a.year === b.year ? b.month - a.month : b.year - a.year;
}); });
for (const dateGroup of updatedDateGroups) {
dateGroup.sortAssets(this.#options.order);
} }
for (const bucket of updatedBuckets) {
for (const group of addContext.existingDateGroups) {
group.sortAssets(this.#options.order);
}
for (const bucket of addContext.bucketsWithNewDateGroups) {
bucket.sortDateGroups(); bucket.sortDateGroups();
}
for (const bucket of addContext.updatedBuckets) {
this.#updateGeometry(bucket, true); this.#updateGeometry(bucket, true);
} }
this.updateIntersections(); this.updateIntersections();