Skip to content

Commit 00348e6

Browse files
committed
Create LayoutCache interface
1 parent fd36744 commit 00348e6

12 files changed

Lines changed: 142 additions & 110 deletions

src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { createLinearCache } from "./layout.js";
12
export {
23
ACTION_ITEMS_LENGTH_CHANGE,
34
ACTION_START_OFFSET_CHANGE,

src/core/layout.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
initCache,
3+
getItemSize,
4+
UNCACHED,
5+
setItemSize,
6+
findIndex as _findIndex,
7+
computeRange,
8+
getItemOffset,
9+
updateCacheLength,
10+
estimateDefaultItemSize,
11+
} from "./cache.js";
12+
import type {
13+
CacheSnapshot,
14+
InternalCacheSnapshot,
15+
ItemsRange,
16+
} from "./types.js";
17+
18+
/**
19+
* @internal
20+
*/
21+
export interface LayoutCache {
22+
$getRange: (startOffset: number, endOffset: number) => ItemsRange;
23+
$findIndex: (offset: number) => number;
24+
$setItemSize: (index: number, size: number) => boolean;
25+
$getItemSize: (index: number) => number;
26+
$getItemOffset: (index: number) => number;
27+
$getTotalSize: () => number;
28+
$setLength: (length: number, isShift?: boolean) => number;
29+
$getLength: () => number;
30+
$isSizeEqual: (index: number, value?: number) => boolean;
31+
$calcDefaultSize: (visibleOffset: number) => number;
32+
}
33+
34+
interface LinearCache extends LayoutCache {
35+
$snapshot: () => CacheSnapshot;
36+
}
37+
38+
/**
39+
* @internal
40+
*/
41+
export const createLinearCache = (
42+
elementsCount: number,
43+
itemSize: number = 40,
44+
cacheSnapshot?: CacheSnapshot | undefined
45+
): LinearCache => {
46+
let prevStartIndex = 0;
47+
48+
const cache = initCache(
49+
elementsCount,
50+
itemSize,
51+
cacheSnapshot as unknown as InternalCacheSnapshot | undefined
52+
);
53+
54+
const findIndex = (offset: number) => _findIndex(cache, offset);
55+
56+
return {
57+
$snapshot: () => {
58+
return [
59+
cache._sizes.slice(),
60+
cache._defaultItemSize,
61+
] satisfies InternalCacheSnapshot as unknown as CacheSnapshot;
62+
},
63+
$getRange: (startOffset, endOffset) => {
64+
const range = computeRange(cache, startOffset, endOffset, prevStartIndex);
65+
prevStartIndex = range[0];
66+
return range;
67+
},
68+
$findIndex: findIndex,
69+
$setItemSize: (index, size) => setItemSize(cache, index, size),
70+
$getItemSize: (index) => getItemSize(cache, index),
71+
$getItemOffset: (index) => getItemOffset(cache, index),
72+
$getTotalSize: () => getItemOffset(cache, cache._length),
73+
$setLength: (length, isShift) => {
74+
return updateCacheLength(cache, length, isShift);
75+
},
76+
$getLength: () => cache._length,
77+
$isSizeEqual: (index, value = UNCACHED) => cache._sizes[index] === value,
78+
$calcDefaultSize: (offset) => {
79+
return estimateDefaultItemSize(cache, findIndex(offset));
80+
},
81+
};
82+
};

src/core/store.ts

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,6 @@
1-
import {
2-
initCache,
3-
getItemSize as _getItemSize,
4-
getItemOffset as _getItemOffset,
5-
UNCACHED,
6-
setItemSize,
7-
estimateDefaultItemSize,
8-
updateCacheLength,
9-
computeRange,
10-
takeCacheSnapshot,
11-
findIndex,
12-
} from "./cache.js";
131
import { isIOSWebKit } from "./environment.js";
14-
import type {
15-
CacheSnapshot,
16-
InternalCacheSnapshot,
17-
ItemResize,
18-
ItemsRange,
19-
} from "./types.js";
2+
import { LayoutCache } from "./layout.js";
3+
import type { ItemResize, ItemsRange } from "./types.js";
204
import { abs, max, min, NULL } from "./utils.js";
215

226
const MAX_INT_32 = 0x7fffffff;
@@ -95,7 +79,6 @@ export type StateVersion =
9579
export type VirtualStore = {
9680
$dispose(): void;
9781
$getStateVersion(): StateVersion;
98-
$getCacheSnapshot(): CacheSnapshot;
9982
$getRange(bufferSize?: number): ItemsRange;
10083
$findStartIndex(): number;
10184
$findEndIndex(): number;
@@ -117,10 +100,19 @@ export type VirtualStore = {
117100
* @internal
118101
*/
119102
export const createVirtualStore = (
120-
elementsCount: number,
121-
itemSize: number = 40,
103+
{
104+
$getRange: getRange,
105+
$findIndex: findIndex,
106+
$setItemSize: setItemSize,
107+
$getItemSize: getItemSize,
108+
$getItemOffset: _getItemOffset,
109+
$getTotalSize: getTotalSize,
110+
$setLength: updateCacheLength,
111+
$getLength: getLength,
112+
$isSizeEqual: isSizeEqual,
113+
$calcDefaultSize: updateDefaultSize,
114+
}: LayoutCache,
122115
ssrCount: number = 0,
123-
cacheSnapshot?: CacheSnapshot | undefined,
124116
shouldAutoEstimateItemSize: boolean = false
125117
): VirtualStore => {
126118
let isSSR = !!ssrCount;
@@ -137,27 +129,11 @@ export const createVirtualStore = (
137129
let _prevRange: ItemsRange = [0, isSSR ? max(ssrCount - 1, 0) : -1];
138130
let _totalMeasuredSize = 0;
139131

140-
const cache = initCache(
141-
elementsCount,
142-
itemSize,
143-
cacheSnapshot as unknown as InternalCacheSnapshot | undefined
144-
);
145132
const subscribers = new Set<[number, Subscriber]>();
133+
146134
const getRelativeScrollOffset = () => scrollOffset - startSpacerSize;
147135
const getVisibleOffset = () => getRelativeScrollOffset() + pendingJump + jump;
148-
const getRange = (startOffset: number, endOffset: number) => {
149-
return computeRange(cache, startOffset, endOffset, _prevRange[0]);
150-
};
151-
const getTotalSize = (): number => _getItemOffset(cache, cache._length);
152-
const getItemOffset = (index: number): number => {
153-
return _getItemOffset(cache, index) - pendingJump;
154-
};
155-
const getItemSize = (index: number): number => {
156-
return _getItemSize(cache, index);
157-
};
158-
const isSizeEqual = (index: number, value: number = UNCACHED): boolean => {
159-
return cache._sizes[index] === value;
160-
};
136+
const getItemOffset = (index: number) => _getItemOffset(index) - pendingJump;
161137

162138
const applyJump = (j: number) => {
163139
if (j) {
@@ -180,9 +156,6 @@ export const createVirtualStore = (
180156
subscribers.clear();
181157
},
182158
$getStateVersion: () => stateVersion,
183-
$getCacheSnapshot: () => {
184-
return takeCacheSnapshot(cache) as unknown as CacheSnapshot;
185-
},
186159
$getRange: (bufferSize = 200) => {
187160
if (!viewportSize || isSSR) {
188161
// Empty viewportSize means the first render.
@@ -222,14 +195,14 @@ export const createVirtualStore = (
222195
}
223196
}
224197

225-
return [max(startIndex, 0), min(endIndex, cache._length - 1)];
198+
return [max(startIndex, 0), min(endIndex, getLength() - 1)];
226199
},
227-
$findStartIndex: () => findIndex(cache, getVisibleOffset()),
228-
$findEndIndex: () => findIndex(cache, getVisibleOffset() + viewportSize),
200+
$findStartIndex: () => findIndex(getVisibleOffset()),
201+
$findEndIndex: () => findIndex(getVisibleOffset() + viewportSize),
229202
$isUnmeasuredItem: isSizeEqual,
230203
$getItemOffset: getItemOffset,
231204
$getItemSize: getItemSize,
232-
$getItemsLength: () => cache._length,
205+
$getItemsLength: getLength,
233206
$getScrollOffset: () => scrollOffset,
234207
$isScrolling: () => _scrollDirection !== SCROLL_IDLE,
235208
$getViewportSize: () => viewportSize,
@@ -362,7 +335,7 @@ export const createVirtualStore = (
362335
// Update item sizes
363336
for (const [index, size] of updated) {
364337
const prevSize = getItemSize(index);
365-
const isInitialMeasurement = setItemSize(cache, index, size);
338+
const isInitialMeasurement = setItemSize(index, size);
366339

367340
if (shouldAutoEstimateItemSize) {
368341
_totalMeasuredSize += isInitialMeasurement
@@ -378,12 +351,7 @@ export const createVirtualStore = (
378351
// If the total size is lower than the viewport, the item may be a empty state
379352
_totalMeasuredSize > viewportSize
380353
) {
381-
applyJump(
382-
estimateDefaultItemSize(
383-
cache,
384-
findIndex(cache, getVisibleOffset())
385-
)
386-
);
354+
applyJump(updateDefaultSize(getVisibleOffset()));
387355
shouldAutoEstimateItemSize = false;
388356
}
389357

@@ -410,11 +378,11 @@ export const createVirtualStore = (
410378
}
411379
case ACTION_ITEMS_LENGTH_CHANGE: {
412380
if (payload[1]) {
413-
applyJump(updateCacheLength(cache, payload[0], true));
381+
applyJump(updateCacheLength(payload[0], true));
414382
_scrollMode = SCROLL_BY_SHIFT;
415383
mutated = UPDATE_VIRTUAL_STATE;
416384
} else {
417-
updateCacheLength(cache, payload[0]);
385+
updateCacheLength(payload[0]);
418386
// https://github.com/inokawa/virtua/issues/552
419387
// https://github.com/inokawa/virtua/issues/557
420388
mutated = UPDATE_VIRTUAL_STATE;

src/react/VGrid.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
isRTLDocument,
2323
UPDATE_SCROLL_EVENT,
2424
UPDATE_SCROLL_END_EVENT,
25+
createLinearCache,
2526
} from "../core/index.js";
2627
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
2728
import { refKey } from "./utils.js";
@@ -275,13 +276,11 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
275276
): ReactElement => {
276277
const [rowStore, colStore, resizer, scroller] = useStatic(() => {
277278
const _rowStore = createVirtualStore(
278-
rowCount,
279-
cellHeight,
279+
createLinearCache(rowCount, cellHeight),
280280
initialRowCount
281281
);
282282
const _colStore = createVirtualStore(
283-
colCount,
284-
cellWidth,
283+
createLinearCache(colCount, cellWidth),
285284
initialColCount
286285
);
287286
return [

src/react/Virtualizer.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ACTION_START_OFFSET_CHANGE,
2020
createScroller,
2121
createResizer,
22+
createLinearCache,
2223
type CacheSnapshot,
2324
type ScrollToIndexOpts,
2425
microtask,
@@ -182,7 +183,7 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
182183
shift,
183184
horizontal: horizontalProp,
184185
keepMounted,
185-
cache,
186+
cache: cacheSnapshot,
186187
startMargin = 0,
187188
ssrCount,
188189
as: Element = "div",
@@ -204,17 +205,13 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
204205
const onScroll = useLatestRef(onScrollProp);
205206
const onScrollEnd = useLatestRef(onScrollEndProp);
206207

207-
const [store, resizer, scroller, isHorizontal] = useStatic(() => {
208+
const [store, cache, resizer, scroller, isHorizontal] = useStatic(() => {
208209
const _isHorizontal = !!horizontalProp;
209-
const _store = createVirtualStore(
210-
count,
211-
itemSize,
212-
ssrCount,
213-
cache,
214-
!itemSize
215-
);
210+
const _cache = createLinearCache(count, itemSize, cacheSnapshot);
211+
const _store = createVirtualStore(_cache, ssrCount, !itemSize);
216212
return [
217213
_store,
214+
_cache,
218215
createResizer(_store, _isHorizontal),
219216
createScroller(_store, _isHorizontal),
220217
_isHorizontal,
@@ -307,7 +304,7 @@ export const Virtualizer = forwardRef<VirtualizerHandle, VirtualizerProps>(
307304
() => {
308305
return {
309306
get cache() {
310-
return store.$getCacheSnapshot();
307+
return cache.$snapshot();
311308
},
312309
get scrollOffset() {
313310
return store.$getScrollOffset();

src/react/WindowVirtualizer.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
createWindowResizer,
1919
type CacheSnapshot,
2020
type ScrollToIndexOpts,
21+
createLinearCache,
2122
} from "../core/index.js";
2223
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js";
2324
import { getKey, refKey } from "./utils.js";
@@ -134,7 +135,7 @@ export const WindowVirtualizer = forwardRef<
134135
itemSize,
135136
shift,
136137
horizontal: horizontalProp,
137-
cache,
138+
cache: cacheSnapshot,
138139
ssrCount,
139140
as: Element = "div",
140141
item: ItemElement = "div",
@@ -154,18 +155,14 @@ export const WindowVirtualizer = forwardRef<
154155

155156
const isSSR = useRef(!!ssrCount);
156157

157-
const [store, resizer, scroller, isHorizontal] = useStatic(() => {
158+
const [store, cache, resizer, scroller, isHorizontal] = useStatic(() => {
158159
const _isHorizontal = !!horizontalProp;
159-
const _store = createVirtualStore(
160-
count,
161-
itemSize,
162-
ssrCount,
163-
cache,
164-
!itemSize
165-
);
160+
const _cache = createLinearCache(count, itemSize, cacheSnapshot);
161+
const _store = createVirtualStore(_cache, ssrCount, !itemSize);
166162

167163
return [
168164
_store,
165+
_cache,
169166
createWindowResizer(_store, _isHorizontal),
170167
createWindowScroller(_store, _isHorizontal),
171168
_isHorizontal,
@@ -225,7 +222,7 @@ export const WindowVirtualizer = forwardRef<
225222
() => {
226223
return {
227224
get cache() {
228-
return store.$getCacheSnapshot();
225+
return cache.$snapshot();
229226
},
230227
findStartIndex: store.$findStartIndex,
231228
findEndIndex: store.$findEndIndex,

0 commit comments

Comments
 (0)