feat: switch to integer-based scope id/offset queuing

This commit is contained in:
Michael Rawlings 2022-01-12 09:47:18 -08:00
parent 5ae33a66f8
commit 0d3b4058ba
20 changed files with 112 additions and 117 deletions

View File

@ -6,9 +6,9 @@
{
"name": "*",
"individual": {
"min": 11322,
"gzip": 4999,
"brotli": 4513
"min": 11286,
"gzip": 4991,
"brotli": 4509
}
}
]

View File

@ -1,5 +1,5 @@
# Write
<!M^ROOT>a
<!M^0>a
# Write
@ -15,17 +15,17 @@
# Write
jkl<!M/ROOT>
jkl<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abcdefghijkl
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,5 +1,5 @@
# Write
<!M^ROOT>a
<!M^0>a
# Write
@ -7,17 +7,17 @@
# Write
de<!M/ROOT>
de<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abcde
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,19 +1,19 @@
# Write
<!M^ROOT>a
<!M^0>a
# Write
bcde<!M/ROOT>
bcde<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abcde
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,23 +1,23 @@
# Write
<!M^ROOT><body><!M#2 ROOT 2><button><!M#1 ROOT 3>0</button></body><!M/ROOT><script>(M$h=[]).push((b,s)=>({ROOT:[,,,,0]}),["counter",2,"ROOT",])</script>
<!M^0><body><!M#2 0 2><button><!M#1 0 3>0</button></body><!M/0><script>(M$h=[]).push((b,s)=>({"0":[,,,,0]}),["counter",2,0,])</script>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
<!--M#2 ROOT 2-->
<!--M#2 0 2-->
<button>
<!--M#1 ROOT 3-->
<!--M#1 0 3-->
0
</button>
<script>
(M$h=[]).push((b,s)=&gt;({ROOT:[,,,,0]}),["counter",2,"ROOT",])
(M$h=[]).push((b,s)=&gt;({"0":[,,,,0]}),["counter",2,0,])
</script>
</body>
<!--M/ROOT-->
<!--M/0-->
</html>
```
@ -39,20 +39,20 @@ inserted #document/html1/body1/script2/#text0
# Render "Hydrate"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
<!--M#2 ROOT 2-->
<!--M#2 0 2-->
<button>
<!--M#1 ROOT 3-->
<!--M#1 0 3-->
0
</button>
<script>
(M$h=[]).push((b,s)=&gt;({ROOT:[,,,,0]}),["counter",2,"ROOT",])
(M$h=[]).push((b,s)=&gt;({"0":[,,,,0]}),["counter",2,0,])
</script>
</body>
<!--M/ROOT-->
<!--M/0-->
</html>
```
@ -66,20 +66,20 @@ inserted #document/html1/body1/script2/#text0
container.querySelector("button").click();
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
<!--M#2 ROOT 2-->
<!--M#2 0 2-->
<button>
<!--M#1 ROOT 3-->
<!--M#1 0 3-->
1
</button>
<script>
(M$h=[]).push((b,s)=&gt;({ROOT:[,,,,0]}),["counter",2,"ROOT",])
(M$h=[]).push((b,s)=&gt;({"0":[,,,,0]}),["counter",2,0,])
</script>
</body>
<!--M/ROOT-->
<!--M/0-->
</html>
```
@ -93,20 +93,20 @@ container.querySelector("button").click();
container.querySelector("button").click();
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
<!--M#2 ROOT 2-->
<!--M#2 0 2-->
<button>
<!--M#1 ROOT 3-->
<!--M#1 0 3-->
2
</button>
<script>
(M$h=[]).push((b,s)=&gt;({ROOT:[,,,,0]}),["counter",2,"ROOT",])
(M$h=[]).push((b,s)=&gt;({"0":[,,,,0]}),["counter",2,0,])
</script>
</body>
<!--M/ROOT-->
<!--M/0-->
</html>
```
@ -120,20 +120,20 @@ container.querySelector("button").click();
container.querySelector("button").click();
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
<!--M#2 ROOT 2-->
<!--M#2 0 2-->
<button>
<!--M#1 ROOT 3-->
<!--M#1 0 3-->
3
</button>
<script>
(M$h=[]).push((b,s)=&gt;({ROOT:[,,,,0]}),["counter",2,"ROOT",])
(M$h=[]).push((b,s)=&gt;({"0":[,,,,0]}),["counter",2,0,])
</script>
</body>
<!--M/ROOT-->
<!--M/0-->
</html>
```

View File

@ -1,9 +1,9 @@
# Write
<!M^ROOT>a<!M$0>b
<!M^0>a<!M$0>b
# Write
d<!M$0/>efg<!M/ROOT>
d<!M$0/>efg<!M/0>
# Write
@ -12,12 +12,12 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
aERROR!efg
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,14 +1,14 @@
# Write
<!M^ROOT>a<!M$0>b
<!M^0>a<!M$0>b
# Write
cd<!M$0/>fgh<!M/ROOT>
cd<!M$0/>fgh<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
@ -17,7 +17,7 @@
bcd
<!--M$0/-->
fgh
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,15 +1,15 @@
# Write
<!M^ROOT>abc<!M/ROOT>
<!M^0>abc<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abc
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,15 +1,15 @@
# Write
<!M^ROOT>aERROR!d<!M/ROOT>
<!M^0>aERROR!d<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
aERROR!d
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,9 +1,9 @@
# Write
<!M^ROOT>a
<!M^0>a
# Write
b<!M/ROOT>
b<!M/0>
# Emit error
@ -12,12 +12,12 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
ab
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,5 +1,5 @@
# Write
<!M^ROOT>a
<!M^0>a
# Emit error
@ -8,7 +8,7 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>

View File

@ -1,10 +1,10 @@
# Write
<!M^ROOT>a<!M$0>e...<!M$0/>f
<!M^0>a<!M$0>e...<!M$0/>f
# Write
gh
context cleared<!M/ROOT>
context cleared<!M/0>
# Write
@ -13,13 +13,13 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abc2dfgh
context cleared
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,9 +1,9 @@
# Write
<!M^ROOT>a<!M$0>e...<!M$0/>f
<!M^0>a<!M$0>e...<!M$0/>f
# Write
gh<!M/ROOT>
gh<!M/0>
# Write
@ -12,12 +12,12 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abcdfgh
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,19 +1,19 @@
# Write
<!M^ROOT>abd
<!M^0>abd
# Write
ef<!M/ROOT>
ef<!M/0>
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abdef
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -1,9 +1,9 @@
# Write
<!M^ROOT>a<!M$1>i...<!M$1/>j
<!M^0>a<!M$1>i...<!M$1/>j
# Write
kl<!M/ROOT>
kl<!M/0>
# Write
@ -16,12 +16,12 @@
# Render "End"
```html
<!--M^ROOT-->
<!--M^0-->
<html>
<head />
<body>
abcdefgjkl
<!--M/ROOT-->
<!--M/0-->
</body>
</html>
```

View File

@ -13,7 +13,7 @@ export type Scope = [
number | undefined, // OWNER_OFFSET
...unknown[]
] & {
___id: string;
___id: number;
___startNode: (Node & ChildNode) | number | undefined;
___endNode: (Node & ChildNode) | number | undefined;
___cleanup: Set<number | Scope> | undefined;

View File

@ -4,6 +4,7 @@ import { bind, runWithScope } from "./scope";
type HydrateFn = () => void;
const fnsById: Record<string, HydrateFn> = {};
const SCOPE_ID_MULTIPLIER = 2 ** 16;
export function register<F extends HydrateFn>(id: string, fn: F) {
fnsById[id] = fn;
@ -23,10 +24,10 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
let currentScope: Scope;
let currentOffset: number;
let currentNode: Node;
const scopeLookup: Record<string, Scope> = {};
const stack: Array<string | number> = [];
const scopeLookup: Record<number, Scope> = {};
const stack: number[] = [];
const fakeArray = { push: hydrate };
const bindFunction = (fnId: string, offset: number, scopeId: string) => {
const bindFunction = (fnId: string, offset: number, scopeId: number) => {
const fn = fnsById[fnId];
const scope = scopeLookup[scopeId];
return bind(fn, offset, scope);
@ -63,7 +64,8 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
* had to create a dummy scope to store Nodes of interest.
* If so merge them and set/replace the scope in the scopeLookup.
*/
for (const scopeId in scopes) {
for (const scopeIdAsString in scopes) {
const scopeId = parseInt(scopeIdAsString);
const scope = scopes[scopeId];
const storedScope = scopeLookup[scopeId];
@ -71,7 +73,7 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
if (storedScope) {
Object.assign(scope, storedScope);
} else {
scope.___id = scopeId;
scope.___id = scopeId * SCOPE_ID_MULTIPLIER;
scopeLookup[scopeId] = scope;
}
if (currentScope === storedScope) {
@ -84,22 +86,26 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
const nodeValue = currentNode.nodeValue;
if (nodeValue?.startsWith(`${runtimeId}`)) {
const token = nodeValue[runtimeLength];
const data = nodeValue.slice(runtimeLength + 1);
const data = parseInt(nodeValue.slice(runtimeLength + 1));
if (token === HydrateSymbols.SCOPE_OFFSET) {
// eslint-disable-next-line no-constant-condition
if ("MARKO_DEBUG") {
const [offset, scopeId, index] = data.split(" ");
if (scopeId !== currentScope.___id) {
const [offset, scopeId, index] = nodeValue
.slice(runtimeLength + 1)
.split(" ");
if (
parseInt(scopeId) * SCOPE_ID_MULTIPLIER !==
currentScope.___id
) {
throw new Error("INVALID_MARKER_NESTING: " + nodeValue);
}
if (parseInt(offset) + currentOffset !== parseInt(index)) {
throw new Error("SCOPE_OFFSET_MISMATCH: " + nodeValue);
}
}
const offset = parseInt(data);
const node = currentNode.nextSibling;
// currentNode.parentNode!.removeChild(currentNode);
currentScope[(currentOffset += offset)] = node;
currentScope[(currentOffset += data)] = node;
} else if (token === HydrateSymbols.SCOPE_START) {
if (currentScope) {
stack.push(currentScope.___id, currentOffset);
@ -108,19 +114,19 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
currentOffset = 0;
if (!currentScope) {
scopeLookup[data] = currentScope = [] as unknown as Scope;
currentScope.___id = data;
currentScope.___id = data * SCOPE_ID_MULTIPLIER;
}
currentScope.___startNode = currentNode as ChildNode;
} else if (token === HydrateSymbols.SCOPE_END) {
// eslint-disable-next-line no-constant-condition
if ("MARKO_DEBUG") {
if (data !== currentScope.___id) {
if (data * SCOPE_ID_MULTIPLIER !== currentScope.___id) {
throw new Error("SCOPE_END_MISMATCH: " + nodeValue);
}
}
currentScope.___endNode = currentNode as ChildNode;
currentOffset = stack.pop() as number;
currentScope = scopeLookup[stack.pop() as string]!;
currentScope = scopeLookup[stack.pop() as number]!;
// eslint-disable-next-line no-constant-condition
} else if ("MARKO_DEBUG") {
throw new Error("MALFORMED MARKER: " + nodeValue);
@ -132,7 +138,7 @@ export function init(runtimeId = "M" /* [a-zA-Z0-9]+ */) {
runWithScope(
fnsById[calls[i]]!,
calls[i + 1] as number,
scopeLookup[calls[i + 2]]
scopeLookup[calls[i + 2] as number]
);
}
}

View File

@ -45,7 +45,8 @@ export function queue<T extends ExecFn>(
scope = currentScope,
offset = currentOffset
) {
const index = findQueueIndex(scope, offset + localIndex);
const sortValue = scope.___id + offset + localIndex;
const index = findQueueIndex(sortValue);
// index is where the function should be in the queue
// but if it already exists, we should not add it again
@ -66,7 +67,7 @@ export function queue<T extends ExecFn>(
queuedFns[index + QueueOffsets.FN] = fn;
queuedFns[index + QueueOffsets.SCOPE] = scope;
queuedFns[index + QueueOffsets.OFFSET] = offset;
queuedFns[index + QueueOffsets.SORT_VALUE] = offset + localIndex;
queuedFns[index + QueueOffsets.SORT_VALUE] = sortValue;
}
queuedFns[index + QueueOffsets.ARGUMENT] = argument;
}
@ -103,17 +104,16 @@ export function run() {
}
}
function findQueueIndex(scope: Scope, sortValue: number) {
function findQueueIndex(sortValue: number) {
let index = 0;
let max = queuedFns.length / QueueOffsets.TOTAL;
while (index < max) {
const mid = (index + max) >>> 1;
const compareResult = compareQueue(
mid * QueueOffsets.TOTAL,
scope,
sortValue
);
const compareResult =
(queuedFns[
mid * QueueOffsets.TOTAL + QueueOffsets.SORT_VALUE
] as number) - sortValue;
if (compareResult > 0) {
max = mid;
} else if (compareResult < 0) {
@ -125,16 +125,3 @@ function findQueueIndex(scope: Scope, sortValue: number) {
return index * QueueOffsets.TOTAL;
}
function compareQueue(index: number, scope: Scope, sortValue: number) {
return (
compare(
(queuedFns[index + QueueOffsets.SCOPE] as Scope).___id,
scope.___id
) || (queuedFns[index + QueueOffsets.SORT_VALUE] as number) - sortValue
);
}
function compare(a: number | string, b: number | string) {
return a < b ? -1 : a > b ? 1 : 0;
}

View File

@ -4,11 +4,13 @@ import { Scope, ScopeOffsets } from "../common/types";
export let currentScope: Scope;
export let currentOffset: number;
export let ownerOffset: number;
const CLIENT_SCOPE_ID_BIT = 2 ** 52;
const SCOPE_ID_MULTIPLIER = 2 ** 16;
let scopeId = 0;
export function createScope(size: number): Scope {
const scope = new Array(size) as Scope;
scope.___id = "" + scopeId++;
scope.___id = CLIENT_SCOPE_ID_BIT + SCOPE_ID_MULTIPLIER * scopeId++;
scope[ScopeOffsets.OWNER_SCOPE] = currentScope;
scope[ScopeOffsets.OWNER_OFFSET] = currentOffset;
return scope;

View File

@ -44,7 +44,7 @@ export function createRenderer(renderer: Renderer, hydrateRoot?: boolean) {
let renderedPromises: typeof $_promises;
try {
const scope = hydrateRoot
? (Object.assign([], { ___id: "ROOT" }) as any as Scope)
? (Object.assign([], { ___id: 0 }) as any as Scope)
: nullScope;
hydrateRoot && write(markScopeStart(scope));
renderer(input, scope, ScopeOffsets.BEGIN_DATA);
@ -70,8 +70,8 @@ export function write(data: string) {
$_buffer!.content += data;
}
export function writeCall(fnId: string, offset: number, scopeId: string) {
$_buffer!.calls += `"${fnId}",${offset},"${scopeId}",`;
export function writeCall(fnId: string, offset: number, scopeId: number) {
$_buffer!.calls += `"${fnId}",${offset},${scopeId},`;
}
export function writeScope(scope: Scope) {