From 5f733e81e0882229ada6dcbccf9de4b3e20b99fb Mon Sep 17 00:00:00 2001 From: inokawa <48897392+inokawa@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:27:58 +0900 Subject: [PATCH] Change CacheSnapshot structure and reduce its size --- src/core/cache.spec.ts | 70 ++++++++++++++++++++++++++++++++++++++++++ src/core/cache.ts | 19 +++++++++--- src/core/store.ts | 18 ++++++++--- src/core/types.ts | 3 ++ 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/src/core/cache.spec.ts b/src/core/cache.spec.ts index bf3b9d856..2f4044913 100644 --- a/src/core/cache.spec.ts +++ b/src/core/cache.spec.ts @@ -10,6 +10,7 @@ import { initCache, computeRange, estimateDefaultItemSize, + takeCacheSnapshot, } from "./cache"; const range = (length: number, cb: (i: number) => T): T[] => { @@ -780,6 +781,75 @@ describe(initCache.name, () => { } `); }); + + it("should create cache from snapshot", () => { + expect(initCache(10, 23, [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 123])) + .toMatchInlineSnapshot(` + { + "_computedOffsetIndex": -1, + "_defaultItemSize": 123, + "_length": 10, + "_offsets": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], + "_sizes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ], + } + `); + }); +}); + +describe(takeCacheSnapshot.name, () => { + it("smoke", () => { + const cache = initCacheWithComputedOffsets( + range(10, (i) => (i + 1) * 10), + 40 + ); + const snapshot = takeCacheSnapshot(cache); + expect(snapshot).toMatchInlineSnapshot(` + [ + [ + 10, + 20, + 30, + 40, + 50, + 60, + 70, + 80, + 90, + 100, + ], + 40, + ] + `); + + // Check if modifying snapshot doesn't affect cache + const clonedSnapshot = structuredClone(snapshot); + snapshot[0][0] = 999; + snapshot[1] = 123; + expect(snapshot).not.toEqual(clonedSnapshot); + expect(takeCacheSnapshot(cache)).toEqual(clonedSnapshot); + }); }); describe(updateCacheLength.name, () => { diff --git a/src/core/cache.ts b/src/core/cache.ts index 827edf3c6..f8dd6edec 100644 --- a/src/core/cache.ts +++ b/src/core/cache.ts @@ -1,4 +1,4 @@ -import { ItemsRange } from "./types"; +import { InternalCacheSnapshot, ItemsRange } from "./types"; import { clamp, max, median, min } from "./utils"; type Writeable = { @@ -160,16 +160,27 @@ export const estimateDefaultItemSize = ( /** * @internal */ -export const initCache = (length: number, itemSize: number): Cache => { +export const initCache = ( + length: number, + itemSize: number, + snapshot?: InternalCacheSnapshot +): Cache => { return { - _defaultItemSize: itemSize, + _defaultItemSize: snapshot ? snapshot[1] : itemSize, + _sizes: snapshot ? snapshot[0] : fill([], length), _length: length, _computedOffsetIndex: -1, - _sizes: fill([], length), _offsets: fill([], length), }; }; +/** + * @internal + */ +export const takeCacheSnapshot = (cache: Cache): InternalCacheSnapshot => { + return [[...cache._sizes], cache._defaultItemSize]; +}; + /** * @internal */ diff --git a/src/core/store.ts b/src/core/store.ts index 590126566..f2dee32ce 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -3,15 +3,20 @@ import { getItemSize as _getItemSize, computeTotalSize, computeOffset as computeStartOffset, - Cache, UNCACHED, setItemSize, estimateDefaultItemSize, updateCacheLength, computeRange, + takeCacheSnapshot, } from "./cache"; import { isIOSWebKit } from "./environment"; -import type { CacheSnapshot, ItemResize, ItemsRange } from "./types"; +import type { + CacheSnapshot, + InternalCacheSnapshot, + ItemResize, + ItemsRange, +} from "./types"; import { abs, clamp, max, min } from "./utils"; // Scroll offset and sizes can have sub-pixel value if window.devicePixelRatio has decimal value @@ -155,8 +160,11 @@ export const createVirtualStore = ( let _prevRange: ItemsRange = [0, 0]; let _totalMeasuredSize = 0; - const cache = - (cacheSnapshot as Cache | undefined) || initCache(elementsCount, itemSize); + const cache = initCache( + elementsCount, + itemSize, + cacheSnapshot as unknown as InternalCacheSnapshot | undefined + ); const subscribers = new Set<[number, Subscriber]>(); const getRange = (offset: number) => { return computeRange(cache, offset, _prevRange[0], viewportSize); @@ -192,7 +200,7 @@ export const createVirtualStore = ( return stateVersion; }, _getCacheSnapshot() { - return JSON.parse(JSON.stringify(cache)) as unknown as CacheSnapshot; + return takeCacheSnapshot(cache) as unknown as CacheSnapshot; }, _getRange() { // Return previous range for consistent render until next scroll event comes in. diff --git a/src/core/types.ts b/src/core/types.ts index 9253f264a..956c2434e 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -3,6 +3,9 @@ export type ItemResize = Readonly<[index: number, size: number]>; /** @internal */ export type ItemsRange = Readonly<[startIndex: number, endIndex: number]>; +/** @internal */ +export type InternalCacheSnapshot = [sizes: number[], defaultSize: number]; + declare const cacheSymbol: unique symbol; /** * Serializable cache snapshot.