Skip to content

Commit 18512c7

Browse files
committed
feat: implement chore array
1 parent 44768e7 commit 18512c7

11 files changed

+303
-216
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { AsyncComputedSignalImpl } from '../reactive-primitives/impl/async-computed-signal-impl';
2+
import { StoreHandler } from '../reactive-primitives/impl/store';
3+
import { assertFalse } from '../shared/error/assert';
4+
import { isQrl } from '../shared/qrl/qrl-utils';
5+
import type { Chore } from '../shared/scheduler';
6+
import {
7+
ssrNodeDocumentPosition,
8+
vnode_documentPosition,
9+
} from '../shared/scheduler-document-position';
10+
import { ChoreType } from '../shared/util-chore-type';
11+
import type { ISsrNode } from '../ssr/ssr-types';
12+
import { vnode_isVNode } from './vnode';
13+
14+
export class ChoreArray extends Array<Chore> {
15+
add(value: Chore): number {
16+
/// We need to ensure that the `queue` is sorted by priority.
17+
/// 1. Find a place where to insert into.
18+
const idx = sortedFindIndex(this, value);
19+
20+
if (idx < 0) {
21+
/// 2. Insert the chore into the queue.
22+
this.splice(~idx, 0, value);
23+
return idx;
24+
}
25+
26+
const existing = this[idx];
27+
/**
28+
* When a derived signal is updated we need to run vnode_diff. However the signal can update
29+
* multiple times during component execution. For this reason it is necessary for us to update
30+
* the chore with the latest result of the signal.
31+
*/
32+
if (existing.$payload$ !== value.$payload$) {
33+
existing.$payload$ = value.$payload$;
34+
}
35+
return idx;
36+
}
37+
38+
delete(value: Chore) {
39+
// const idx = this.sortedFindIndex(this, value);
40+
// if (idx >= 0) {
41+
// this.splice(idx, 1);
42+
// }
43+
// return idx;
44+
const idx = this.indexOf(value);
45+
if (idx >= 0) {
46+
this.splice(idx, 1);
47+
}
48+
return idx;
49+
}
50+
}
51+
52+
export function sortedFindIndex(sortedArray: Chore[], value: Chore): number {
53+
/// We need to ensure that the `queue` is sorted by priority.
54+
/// 1. Find a place where to insert into.
55+
let bottom = 0;
56+
let top = sortedArray.length;
57+
while (bottom < top) {
58+
const middle = bottom + ((top - bottom) >> 1);
59+
const midChore = sortedArray[middle];
60+
const comp = choreComparator(value, midChore);
61+
if (comp < 0) {
62+
top = middle;
63+
} else if (comp > 0) {
64+
bottom = middle + 1;
65+
} else {
66+
// We already have the host in the queue.
67+
return middle;
68+
}
69+
}
70+
return ~bottom;
71+
}
72+
73+
/**
74+
* Compares two chores to determine their execution order in the scheduler's queue.
75+
*
76+
* @param a - The first chore to compare
77+
* @param b - The second chore to compare
78+
* @returns A number indicating the relative order of the chores. A negative number means `a` runs
79+
* before `b`.
80+
*/
81+
export function choreComparator(a: Chore, b: Chore): number {
82+
const macroTypeDiff = (a.$type$ & ChoreType.MACRO) - (b.$type$ & ChoreType.MACRO);
83+
if (macroTypeDiff !== 0) {
84+
return macroTypeDiff;
85+
}
86+
87+
const aHost = a.$host$;
88+
const bHost = b.$host$;
89+
90+
if (aHost !== bHost && aHost !== null && bHost !== null) {
91+
if (vnode_isVNode(aHost) && vnode_isVNode(bHost)) {
92+
// we are running on the client.
93+
const hostDiff = vnode_documentPosition(aHost, bHost);
94+
if (hostDiff !== 0) {
95+
return hostDiff;
96+
}
97+
} else {
98+
assertFalse(vnode_isVNode(aHost), 'expected aHost to be SSRNode but it is a VNode');
99+
assertFalse(vnode_isVNode(bHost), 'expected bHost to be SSRNode but it is a VNode');
100+
const hostDiff = ssrNodeDocumentPosition(aHost as ISsrNode, bHost as ISsrNode);
101+
if (hostDiff !== 0) {
102+
return hostDiff;
103+
}
104+
}
105+
}
106+
107+
const microTypeDiff = (a.$type$ & ChoreType.MICRO) - (b.$type$ & ChoreType.MICRO);
108+
if (microTypeDiff !== 0) {
109+
return microTypeDiff;
110+
}
111+
// types are the same
112+
113+
const idxDiff = toNumber(a.$idx$) - toNumber(b.$idx$);
114+
if (idxDiff !== 0) {
115+
return idxDiff;
116+
}
117+
118+
// If the host is the same (or missing), and the type is the same, we need to compare the target.
119+
if (a.$target$ !== b.$target$) {
120+
if (isQrl(a.$target$) && isQrl(b.$target$) && a.$target$.$hash$ === b.$target$.$hash$) {
121+
return 0;
122+
}
123+
// 1 means that we are going to process chores as FIFO
124+
return 1;
125+
}
126+
127+
// ensure that the effect chores are scheduled for the same target
128+
// TODO: can we do this better?
129+
if (
130+
a.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS &&
131+
b.$type$ === ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS &&
132+
((a.$target$ instanceof StoreHandler && b.$target$ instanceof StoreHandler) ||
133+
(a.$target$ instanceof AsyncComputedSignalImpl &&
134+
b.$target$ instanceof AsyncComputedSignalImpl)) &&
135+
a.$payload$ !== b.$payload$
136+
) {
137+
return 1;
138+
}
139+
140+
// The chores are the same and will run only once
141+
return 0;
142+
}
143+
144+
function toNumber(value: number | string): number {
145+
return typeof value === 'number' ? value : -1;
146+
}

packages/qwik/src/core/client/vnode-impl.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { VNodeFlags } from './types';
2-
import { Chore } from '../shared/scheduler';
32
import { mapApp_findIndx, mapArray_get } from './util-mapArray';
43
import {
54
vnode_ensureElementInflated,
65
vnode_toString,
76
VNodeJournalOpCode,
87
type VNodeJournal,
98
} from './vnode';
9+
import type { ChoreArray } from './chore-array';
1010

1111
/** @internal */
1212
export abstract class VNode {
1313
props: (string | null | boolean)[] | null = null;
1414
slotParent: VNode | null = null;
15-
chores: Set<Chore> | null = null;
16-
blockedChores: Set<Chore> | null = null;
15+
// scheduled chores for this vnode
16+
chores: ChoreArray | null = null;
17+
// blocked chores for this vnode
18+
blockedChores: ChoreArray | null = null;
1719

1820
getSlotParent(): VNode | null {
1921
return this.slotParent;
@@ -123,17 +125,17 @@ export class VirtualVNode extends VNode {
123125
}
124126

125127
/** @internal */
126-
export class ElementVNode extends VirtualVNode {
128+
export class ElementVNode extends VNode {
127129
constructor(
128130
flags: VNodeFlags,
129131
parent: ElementVNode | VirtualVNode | null,
130132
previousSibling: VNode | null | undefined,
131133
nextSibling: VNode | null | undefined,
132-
firstChild: VNode | null | undefined,
133-
lastChild: VNode | null | undefined,
134+
public firstChild: VNode | null | undefined,
135+
public lastChild: VNode | null | undefined,
134136
public element: Element,
135137
public elementName: string | undefined
136138
) {
137-
super(flags, parent, previousSibling, nextSibling, firstChild, lastChild);
139+
super(flags, parent, previousSibling, nextSibling);
138140
}
139141
}

packages/qwik/src/core/client/vnode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1667,7 +1667,7 @@ export const vnode_getProps = (vnode: ElementVNode | VirtualVNode): unknown[] =>
16671667
};
16681668

16691669
export const vnode_getParent = (vnode: VNode): VNode | null => {
1670-
return vnode.parent!;
1670+
return vnode.parent;
16711671
};
16721672

16731673
export const vnode_isDescendantOf = (vnode: VNode, ancestor: VNode): boolean => {

packages/qwik/src/core/qwik.core.api.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,16 @@ export const _dumpState: (state: unknown[], color?: boolean, prefix?: string, li
277277
export const _EFFECT_BACK_REF: unique symbol;
278278

279279
// @internal (undocumented)
280-
export class _ElementVNode extends _VirtualVNode {
281-
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null | undefined, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, firstChild: _VNode | null | undefined, lastChild: _VNode | null | undefined, element: Element, elementName: string | undefined);
280+
export class _ElementVNode extends _VNode {
281+
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, firstChild: _VNode | null | undefined, lastChild: _VNode | null | undefined, element: Element, elementName: string | undefined);
282282
// (undocumented)
283283
element: Element;
284284
// (undocumented)
285285
elementName: string | undefined;
286+
// (undocumented)
287+
firstChild: _VNode | null | undefined;
288+
// (undocumented)
289+
lastChild: _VNode | null | undefined;
286290
}
287291

288292
// @internal (undocumented)
@@ -1657,7 +1661,7 @@ export type TaskFn = (ctx: TaskCtx) => ValueOrPromise<void | (() => void)>;
16571661

16581662
// @internal (undocumented)
16591663
export class _TextVNode extends _VNode {
1660-
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null | undefined, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, textNode: Text | null, text: string | undefined);
1664+
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, textNode: Text | null, text: string | undefined);
16611665
// (undocumented)
16621666
text: string | undefined;
16631667
// (undocumented)
@@ -1825,7 +1829,7 @@ export const version: string;
18251829

18261830
// @internal (undocumented)
18271831
export class _VirtualVNode extends _VNode {
1828-
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null | undefined, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, firstChild: _VNode | null | undefined, lastChild: _VNode | null | undefined);
1832+
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, firstChild: _VNode | null | undefined, lastChild: _VNode | null | undefined);
18291833
// (undocumented)
18301834
firstChild: _VNode | null | undefined;
18311835
// (undocumented)
@@ -1837,7 +1841,13 @@ export type VisibleTaskStrategy = 'intersection-observer' | 'document-ready' | '
18371841

18381842
// @internal (undocumented)
18391843
export abstract class _VNode {
1840-
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null | undefined, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined);
1844+
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined);
1845+
// (undocumented)
1846+
blockedChores: ChoreArray | null;
1847+
// Warning: (ae-forgotten-export) The symbol "ChoreArray" needs to be exported by the entry point index.d.ts
1848+
//
1849+
// (undocumented)
1850+
chores: ChoreArray | null;
18411851
// (undocumented)
18421852
flags: _VNodeFlags;
18431853
// (undocumented)
@@ -1849,7 +1859,7 @@ export abstract class _VNode {
18491859
// (undocumented)
18501860
nextSibling: _VNode | null | undefined;
18511861
// (undocumented)
1852-
parent: _ElementVNode | _VirtualVNode | null | undefined;
1862+
parent: _ElementVNode | _VirtualVNode | null;
18531863
// (undocumented)
18541864
previousSibling: _VNode | null | undefined;
18551865
// (undocumented)

packages/qwik/src/core/shared/scheduler-document-position.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { vnode_getNextSibling, vnode_getPreviousSibling } from '../client/vnode';
2-
import type { ElementVNode, VNode } from '../client/vnode-impl';
2+
import type { VNode } from '../client/vnode-impl';
33
import type { ISsrNode } from '../ssr/ssr-types';
44

55
/// These global variables are used to avoid creating new arrays for each call to `vnode_documentPosition`.
@@ -10,14 +10,9 @@ const bVNodePath: VNode[] = [];
1010
*
1111
* @param a VNode to compare
1212
* @param b VNode to compare
13-
* @param rootVNode - Root VNode of a container
1413
* @returns -1 if `a` is before `b`, 0 if `a` is the same as `b`, 1 if `a` is after `b`.
1514
*/
16-
export const vnode_documentPosition = (
17-
a: VNode,
18-
b: VNode,
19-
rootVNode: ElementVNode | null
20-
): -1 | 0 | 1 => {
15+
export const vnode_documentPosition = (a: VNode, b: VNode): -1 | 0 | 1 => {
2116
if (a === b) {
2217
return 0;
2318
}

packages/qwik/src/core/shared/scheduler-document-position.unit.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,24 @@ describe('vnode_documentPosition', () => {
3030
parent.innerHTML = '<b></b><i></i>';
3131
const b = vnode_getFirstChild(vParent) as ElementVNode;
3232
const i = vnode_getNextSibling(b) as ElementVNode;
33-
expect(vnode_documentPosition(b, i, null)).toBe(-1);
34-
expect(vnode_documentPosition(i, b, null)).toBe(1);
33+
expect(vnode_documentPosition(b, i)).toBe(-1);
34+
expect(vnode_documentPosition(i, b)).toBe(1);
3535
});
3636
it('should compare two virtual vNodes', () => {
3737
parent.innerHTML = 'AB';
3838
document.qVNodeData.set(parent, '{B}{B}');
3939
const a = vnode_getFirstChild(vParent) as ElementVNode;
4040
const b = vnode_getNextSibling(a) as ElementVNode;
41-
expect(vnode_documentPosition(a, b, null)).toBe(-1);
42-
expect(vnode_documentPosition(b, a, null)).toBe(1);
41+
expect(vnode_documentPosition(a, b)).toBe(-1);
42+
expect(vnode_documentPosition(b, a)).toBe(1);
4343
});
4444
it('should compare two virtual vNodes', () => {
4545
parent.innerHTML = 'AB';
4646
document.qVNodeData.set(parent, '{{B}}{B}');
4747
const a = vnode_getFirstChild(vParent) as ElementVNode;
4848
const a2 = vnode_getFirstChild(a) as ElementVNode;
4949
const b = vnode_getNextSibling(a) as ElementVNode;
50-
expect(vnode_documentPosition(a2, b, null)).toBe(-1);
51-
expect(vnode_documentPosition(b, a2, null)).toBe(1);
50+
expect(vnode_documentPosition(a2, b)).toBe(-1);
51+
expect(vnode_documentPosition(b, a2)).toBe(1);
5252
});
5353
});

packages/qwik/src/core/shared/scheduler-rules.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ChoreType } from './util-chore-type';
1111
import { ELEMENT_SEQ } from './utils/markers';
1212
import { isNumber } from './utils/types';
1313
import type { VNode } from '../client/vnode-impl';
14+
import type { ChoreArray } from '../client/chore-array';
1415

1516
type BlockingRule = {
1617
blockedType: ChoreType;
@@ -154,7 +155,7 @@ function findAncestorBlockingChore(chore: Chore, type: ChoreSetType): Chore | nu
154155

155156
export function findBlockingChore(
156157
chore: Chore,
157-
choreQueue: Chore[],
158+
choreQueue: ChoreArray,
158159
blockedChores: Set<Chore>,
159160
runningChores: Set<Chore>,
160161
container: Container

0 commit comments

Comments
 (0)