Skip to content

Commit d0c4d70

Browse files
committed
Create LayoutCache interface
1 parent fd36744 commit d0c4d70

12 files changed

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

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,

src/solid/Virtualizer.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
type ScrollToIndexOpts,
3232
type CacheSnapshot,
3333
sort,
34+
createLinearCache,
3435
} from "../core/index.js";
3536
import { ListItem } from "./ListItem.js";
3637
import { isSameRange } from "./utils.js";
@@ -171,19 +172,14 @@ export interface VirtualizerProps<T> {
171172
*/
172173
export const Virtualizer = <T,>(props: VirtualizerProps<T>): JSX.Element => {
173174
let containerRef: HTMLDivElement | undefined;
174-
const { itemSize, horizontal = false, cache } = props;
175+
const { itemSize, horizontal = false, cache: cacheSnapshot } = props;
175176
props = mergeProps<[Partial<VirtualizerProps<T>>, VirtualizerProps<T>]>(
176177
{ as: "div" },
177178
props
178179
);
179180

180-
const store = createVirtualStore(
181-
props.data.length,
182-
itemSize,
183-
undefined,
184-
cache,
185-
!itemSize
186-
);
181+
const cache = createLinearCache(props.data.length, itemSize, cacheSnapshot);
182+
const store = createVirtualStore(cache, undefined, !itemSize);
187183
const resizer = createResizer(store, horizontal);
188184
const scroller = createScroller(store, horizontal);
189185

@@ -214,7 +210,7 @@ export const Virtualizer = <T,>(props: VirtualizerProps<T>): JSX.Element => {
214210
if (props.ref) {
215211
props.ref({
216212
get cache() {
217-
return store.$getCacheSnapshot();
213+
return cache.$snapshot();
218214
},
219215
get scrollOffset() {
220216
return store.$getScrollOffset();

0 commit comments

Comments
 (0)