Skip to content
83 changes: 73 additions & 10 deletions packages/core/src/layout/engine/layoutEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ type MeasureCacheEntry = Readonly<{
}>;

type MeasureCache = WeakMap<VNode, MeasureCacheEntry>;
type LayoutCacheLeaf = Map<number, LayoutResult<LayoutTree>>;
type LayoutCacheByX = Map<number, LayoutCacheLeaf>;
type LayoutCacheByForcedH = Map<number, LayoutCacheByX>;
type LayoutCacheByForcedW = Map<number, LayoutCacheByForcedH>;
type LayoutCacheByMaxH = Map<number, LayoutCacheByForcedW>;
type LayoutAxisCache = Map<number, LayoutCacheByMaxH>;
type LayoutCacheEntry = Readonly<{
row: Map<string, LayoutResult<LayoutTree>>;
column: Map<string, LayoutResult<LayoutTree>>;
row: LayoutAxisCache;
column: LayoutAxisCache;
}>;
type LayoutCache = WeakMap<VNode, LayoutCacheEntry>;

Expand Down Expand Up @@ -80,6 +86,7 @@ const measureCacheStack: MeasureCache[] = [];
let activeLayoutCache: LayoutCache | null = null;
const layoutCacheStack: LayoutCache[] = [];
const syntheticThemedColumnCache = new WeakMap<VNode, SyntheticThemedColumnCacheEntry>();
const NULL_FORCED_DIMENSION = -1;

function pushMeasureCache(cache: MeasureCache): void {
measureCacheStack.push(cache);
Expand All @@ -103,17 +110,74 @@ function popLayoutCache(): void {
layoutCacheStack.length > 0 ? (layoutCacheStack[layoutCacheStack.length - 1] ?? null) : null;
}

function layoutCacheKey(
function forcedDimensionKey(value: number | null): number {
if (value === null) return NULL_FORCED_DIMENSION;
if (value < 0) {
throw new RangeError("layout: forced dimensions must be >= 0");
}
return value;
}

function getLayoutCacheHit(
axisMap: LayoutAxisCache,
maxW: number,
maxH: number,
forcedW: number | null,
forcedH: number | null,
x: number,
y: number,
): LayoutResult<LayoutTree> | null {
const byMaxH = axisMap.get(maxW);
if (!byMaxH) return null;
const byForcedW = byMaxH.get(maxH);
if (!byForcedW) return null;
const byForcedH = byForcedW.get(forcedDimensionKey(forcedW));
if (!byForcedH) return null;
const byX = byForcedH.get(forcedDimensionKey(forcedH));
if (!byX) return null;
const byY = byX.get(x);
if (!byY) return null;
return byY.get(y) ?? null;
}

function setLayoutCacheValue(
axisMap: LayoutAxisCache,
maxW: number,
maxH: number,
forcedW: number | null,
forcedH: number | null,
x: number,
y: number,
): string {
return `${String(maxW)}:${String(maxH)}:${forcedW === null ? "n" : String(forcedW)}:${
forcedH === null ? "n" : String(forcedH)
}:${String(x)}:${String(y)}`;
value: LayoutResult<LayoutTree>,
): void {
let byMaxH = axisMap.get(maxW);
if (!byMaxH) {
byMaxH = new Map();
axisMap.set(maxW, byMaxH);
}
let byForcedW = byMaxH.get(maxH);
if (!byForcedW) {
byForcedW = new Map();
byMaxH.set(maxH, byForcedW);
}
const forcedWKey = forcedDimensionKey(forcedW);
let byForcedH = byForcedW.get(forcedWKey);
if (!byForcedH) {
byForcedH = new Map();
byForcedW.set(forcedWKey, byForcedH);
}
const forcedHKey = forcedDimensionKey(forcedH);
let byX = byForcedH.get(forcedHKey);
if (!byX) {
byX = new Map();
byForcedH.set(forcedHKey, byX);
}
let byY = byX.get(x);
if (!byY) {
byY = new Map();
byX.set(x, byY);
}
byY.set(y, value);
}

function getSyntheticThemedColumn(vnode: ThemedVNode): VNode {
Expand Down Expand Up @@ -378,14 +442,13 @@ function layoutNode(
}

const cache = activeLayoutCache;
const cacheKey = layoutCacheKey(maxW, maxH, forcedW, forcedH, x, y);
const dirtySet = getActiveDirtySet();
let cacheHit: LayoutResult<LayoutTree> | null = null;
if (cache) {
const entry = cache.get(vnode);
if (entry) {
const axisMap = axis === "row" ? entry.row : entry.column;
cacheHit = axisMap.get(cacheKey) ?? null;
cacheHit = getLayoutCacheHit(axisMap, maxW, maxH, forcedW, forcedH, x, y);
if (cacheHit && (dirtySet === null || !dirtySet.has(vnode))) {
if (__layoutProfile.enabled) __layoutProfile.layoutCacheHits++;
return cacheHit;
Expand Down Expand Up @@ -697,7 +760,7 @@ function layoutNode(
cache.set(vnode, entry);
}
const axisMap = axis === "row" ? entry.row : entry.column;
axisMap.set(cacheKey, computed);
setLayoutCacheValue(axisMap, maxW, maxH, forcedW, forcedH, x, y, computed);
}

return computed;
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/layout/engine/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

/** Pool of reusable number arrays for layout computation. */
const arrayPool: number[][] = [];
const MAX_POOL_SIZE = 8;
const MAX_POOL_SIZE = 32;

/**
* Get or create a number array of the specified length, zeroed.
Expand All @@ -15,7 +15,12 @@ export function acquireArray(length: number): number[] {
for (let i = 0; i < arrayPool.length; i++) {
const arr = arrayPool[i];
if (arr !== undefined && arr.length >= length) {
arrayPool.splice(i, 1);
const lastIndex = arrayPool.length - 1;
if (i !== lastIndex) {
const last = arrayPool[lastIndex];
if (last !== undefined) arrayPool[i] = last;
}
arrayPool.length = lastIndex;
// Zero the portion we'll use
arr.fill(0, 0, length);
return arr;
Expand Down
Loading
Loading