@solidtv/renderer 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/CoreNode.d.ts +33 -0
- package/dist/src/core/CoreNode.js +34 -6
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextNode.js +3 -2
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/CoreTextureManager.d.ts +39 -3
- package/dist/src/core/CoreTextureManager.js +72 -61
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/Stage.js +8 -4
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.js +1 -1
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/lib/collectionUtils.d.ts +1 -1
- package/dist/src/core/lib/collectionUtils.js +13 -31
- package/dist/src/core/lib/collectionUtils.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlCtxTexture.js +5 -3
- package/dist/src/core/renderers/webgl/WebGlCtxTexture.js.map +1 -1
- package/dist/src/core/textures/SubTexture.js +1 -1
- package/dist/src/core/textures/SubTexture.js.map +1 -1
- package/dist/src/core/textures/Texture.d.ts +1 -1
- package/dist/src/core/textures/Texture.js +10 -13
- package/dist/src/core/textures/Texture.js.map +1 -1
- package/dist/src/main-api/Inspector.js +1 -1
- package/dist/src/main-api/Inspector.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/CoreNode.test.ts +272 -0
- package/src/core/CoreNode.ts +62 -8
- package/src/core/CoreTextNode.test.ts +1 -0
- package/src/core/CoreTextNode.ts +3 -2
- package/src/core/CoreTextureManager.ts +75 -70
- package/src/core/Stage.ts +11 -4
- package/src/core/TextureMemoryManager.test.ts +2 -2
- package/src/core/TextureMemoryManager.ts +1 -1
- package/src/core/lib/collectionUtils.test.ts +91 -0
- package/src/core/lib/collectionUtils.ts +13 -34
- package/src/core/renderers/webgl/WebGlCtxTexture.ts +5 -4
- package/src/core/textures/SubTexture.ts +1 -1
- package/src/core/textures/Texture.test.ts +124 -0
- package/src/core/textures/Texture.ts +10 -13
- package/src/main-api/Inspector.ts +1 -1
|
@@ -349,7 +349,7 @@ export class TextureMemoryManager {
|
|
|
349
349
|
if (
|
|
350
350
|
evictable === true &&
|
|
351
351
|
texture.preventCleanup === false &&
|
|
352
|
-
texture.renderableOwners.
|
|
352
|
+
texture.renderableOwners.size === 0 &&
|
|
353
353
|
texture.hasListeners() === false
|
|
354
354
|
) {
|
|
355
355
|
this.destroyTexture(texture);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { CoreNode } from '../CoreNode.js';
|
|
3
|
+
import { sortByZIndexStable } from './collectionUtils.js';
|
|
4
|
+
|
|
5
|
+
const makeNode = (zIndex: number, tag: number): CoreNode =>
|
|
6
|
+
({ props: { zIndex }, tag } as unknown as CoreNode);
|
|
7
|
+
|
|
8
|
+
const tags = (nodes: CoreNode[]): number[] => {
|
|
9
|
+
const out: number[] = [];
|
|
10
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
11
|
+
out.push((nodes[i] as unknown as { tag: number }).tag);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('sortByZIndexStable', () => {
|
|
17
|
+
it('should sort nodes ascending by zIndex', () => {
|
|
18
|
+
const nodes = [makeNode(3, 0), makeNode(1, 1), makeNode(2, 2)];
|
|
19
|
+
|
|
20
|
+
sortByZIndexStable(nodes);
|
|
21
|
+
|
|
22
|
+
expect(tags(nodes)).toEqual([1, 2, 0]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should preserve insertion order for equal zIndex values', () => {
|
|
26
|
+
const nodes = [
|
|
27
|
+
makeNode(1, 0),
|
|
28
|
+
makeNode(0, 1),
|
|
29
|
+
makeNode(1, 2),
|
|
30
|
+
makeNode(0, 3),
|
|
31
|
+
makeNode(1, 4),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
sortByZIndexStable(nodes);
|
|
35
|
+
|
|
36
|
+
expect(tags(nodes)).toEqual([1, 3, 0, 2, 4]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should be stable for arrays longer than 10 elements', () => {
|
|
40
|
+
// V8 on Chrome < 70 switched to an unstable quicksort above 10 elements;
|
|
41
|
+
// this guards the case the native sort got wrong.
|
|
42
|
+
const nodes: CoreNode[] = [];
|
|
43
|
+
for (let i = 0; i < 20; i++) {
|
|
44
|
+
nodes.push(makeNode(i % 2, i));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
sortByZIndexStable(nodes);
|
|
48
|
+
|
|
49
|
+
const expected: number[] = [];
|
|
50
|
+
for (let i = 0; i < 20; i += 2) {
|
|
51
|
+
expected.push(i);
|
|
52
|
+
}
|
|
53
|
+
for (let i = 1; i < 20; i += 2) {
|
|
54
|
+
expected.push(i);
|
|
55
|
+
}
|
|
56
|
+
expect(tags(nodes)).toEqual(expected);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle negative and fractional zIndex values', () => {
|
|
60
|
+
const nodes = [
|
|
61
|
+
makeNode(0.5, 0),
|
|
62
|
+
makeNode(-2, 1),
|
|
63
|
+
makeNode(1000000000, 2),
|
|
64
|
+
makeNode(0, 3),
|
|
65
|
+
makeNode(-2, 4),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
sortByZIndexStable(nodes);
|
|
69
|
+
|
|
70
|
+
expect(tags(nodes)).toEqual([1, 4, 3, 0, 2]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should leave an already sorted array unchanged', () => {
|
|
74
|
+
const nodes = [makeNode(0, 0), makeNode(0, 1), makeNode(1, 2)];
|
|
75
|
+
|
|
76
|
+
sortByZIndexStable(nodes);
|
|
77
|
+
|
|
78
|
+
expect(tags(nodes)).toEqual([0, 1, 2]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle empty and single-element arrays', () => {
|
|
82
|
+
const empty: CoreNode[] = [];
|
|
83
|
+
const single = [makeNode(5, 0)];
|
|
84
|
+
|
|
85
|
+
sortByZIndexStable(empty);
|
|
86
|
+
sortByZIndexStable(single);
|
|
87
|
+
|
|
88
|
+
expect(empty.length).toBe(0);
|
|
89
|
+
expect(tags(single)).toEqual([0]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
import type { CoreNode } from '../CoreNode.js';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
// Stable in-place sort by zIndex. Array.prototype.sort is not stable on
|
|
4
|
+
// Chrome < 70 (V8 quicksorts arrays longer than 10), and equal-zIndex
|
|
5
|
+
// siblings must keep insertion order or paint order silently reshuffles.
|
|
6
|
+
// Children arrays are small and nearly sorted (kept sorted; a re-sort
|
|
7
|
+
// usually follows a single zIndex change), so insertion sort is O(n) in
|
|
8
|
+
// practice and allocation-free.
|
|
9
|
+
export const sortByZIndexStable = (nodes: CoreNode[]): void => {
|
|
10
|
+
const len = nodes.length;
|
|
11
|
+
for (let i = 1; i < len; i++) {
|
|
9
12
|
const node = nodes[i]!;
|
|
10
|
-
const
|
|
11
|
-
//create bucket if it doesn't exist
|
|
12
|
-
if (buckets[index] === undefined) {
|
|
13
|
-
buckets[index] = [];
|
|
14
|
-
bucketIndices.push(index);
|
|
15
|
-
}
|
|
16
|
-
buckets[index]!.push(node);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
//sort each bucket using insertion sort
|
|
20
|
-
for (let i = 1; i < bucketIndices.length; i++) {
|
|
21
|
-
const key = bucketIndices[i]!;
|
|
13
|
+
const z = node.props.zIndex;
|
|
22
14
|
let j = i - 1;
|
|
23
|
-
while (j >= 0 &&
|
|
24
|
-
|
|
15
|
+
while (j >= 0 && nodes[j]!.props.zIndex > z) {
|
|
16
|
+
nodes[j + 1] = nodes[j]!;
|
|
25
17
|
j--;
|
|
26
18
|
}
|
|
27
|
-
|
|
19
|
+
nodes[j + 1] = node;
|
|
28
20
|
}
|
|
29
|
-
|
|
30
|
-
//flatten buckets
|
|
31
|
-
let idx = 0;
|
|
32
|
-
for (let i = 0; i < bucketIndices.length; i++) {
|
|
33
|
-
const bucket = buckets[bucketIndices[i]!]!;
|
|
34
|
-
for (let j = 0; j < bucket.length; j++) {
|
|
35
|
-
nodes[idx++] = bucket[j]!;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
//clean up
|
|
40
|
-
buckets.length = 0;
|
|
41
|
-
bucketIndices.length = 0;
|
|
42
21
|
};
|
|
43
22
|
|
|
44
23
|
export const incrementalRepositionByZIndex = (
|
|
@@ -161,10 +161,11 @@ export class WebGlCtxTexture extends CoreContextTexture {
|
|
|
161
161
|
);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// No placeholder upload here: every branch below fully (re)uploads the
|
|
165
|
+
// texture before this method returns, and there is no `await` in between,
|
|
166
|
+
// so no frame can render an intermediate 1x1. The empty-texture case
|
|
167
|
+
// (`tdata === null`) does its own 1x1 upload. Uploading a throwaway 1x1
|
|
168
|
+
// first would be a wasted texImage2D + setTextureMemUse per texture.
|
|
168
169
|
let w = 0;
|
|
169
170
|
let h = 0;
|
|
170
171
|
|
|
@@ -79,7 +79,7 @@ export class SubTexture extends Texture {
|
|
|
79
79
|
// Resolve parent texture from cache or fallback to provided texture
|
|
80
80
|
this.parentTexture = txManager.resolveParentTexture(this.props.texture);
|
|
81
81
|
|
|
82
|
-
if (this.renderableOwners.
|
|
82
|
+
if (this.renderableOwners.size > 0) {
|
|
83
83
|
this.parentTexture.setRenderableOwner(this.subtextureId, true);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Texture, type TextureData } from './Texture.js';
|
|
3
|
+
import type { CoreTextureManager } from '../CoreTextureManager.js';
|
|
4
|
+
|
|
5
|
+
class TestTexture extends Texture {
|
|
6
|
+
override async getTextureSource(): Promise<TextureData> {
|
|
7
|
+
return { data: null };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function makeTexture(): {
|
|
12
|
+
texture: TestTexture;
|
|
13
|
+
loadTexture: ReturnType<typeof vi.fn>;
|
|
14
|
+
onChangeIsRenderable: ReturnType<typeof vi.fn>;
|
|
15
|
+
} {
|
|
16
|
+
const loadTexture = vi.fn();
|
|
17
|
+
const txManager = {
|
|
18
|
+
maxRetryCount: 3,
|
|
19
|
+
loadTexture,
|
|
20
|
+
} as unknown as CoreTextureManager;
|
|
21
|
+
const texture = new TestTexture(txManager);
|
|
22
|
+
const onChangeIsRenderable = vi.fn();
|
|
23
|
+
texture.onChangeIsRenderable = onChangeIsRenderable;
|
|
24
|
+
return { texture, loadTexture, onChangeIsRenderable };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('Texture.setRenderableOwner', () => {
|
|
28
|
+
it('adding an owner fires the 0→1 transition exactly once and loads', () => {
|
|
29
|
+
const { texture, loadTexture, onChangeIsRenderable } = makeTexture();
|
|
30
|
+
|
|
31
|
+
texture.setRenderableOwner(1, true);
|
|
32
|
+
|
|
33
|
+
expect(texture.renderableOwners.size).toBe(1);
|
|
34
|
+
expect(texture.renderable).toBe(true);
|
|
35
|
+
expect(onChangeIsRenderable).toHaveBeenCalledTimes(1);
|
|
36
|
+
expect(onChangeIsRenderable).toHaveBeenCalledWith(true);
|
|
37
|
+
expect(loadTexture).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('double-add of the same owner is a no-op (size stays 1, one event)', () => {
|
|
41
|
+
const { texture, loadTexture, onChangeIsRenderable } = makeTexture();
|
|
42
|
+
|
|
43
|
+
texture.setRenderableOwner(1, true);
|
|
44
|
+
texture.setRenderableOwner(1, true);
|
|
45
|
+
|
|
46
|
+
expect(texture.renderableOwners.size).toBe(1);
|
|
47
|
+
expect(onChangeIsRenderable).toHaveBeenCalledTimes(1);
|
|
48
|
+
expect(loadTexture).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('a second distinct owner does not re-fire the transition', () => {
|
|
52
|
+
const { texture, loadTexture, onChangeIsRenderable } = makeTexture();
|
|
53
|
+
|
|
54
|
+
texture.setRenderableOwner(1, true);
|
|
55
|
+
texture.setRenderableOwner(2, true);
|
|
56
|
+
|
|
57
|
+
expect(texture.renderableOwners.size).toBe(2);
|
|
58
|
+
expect(onChangeIsRenderable).toHaveBeenCalledTimes(1);
|
|
59
|
+
expect(loadTexture).toHaveBeenCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('removing a non-member owner does not fire a transition', () => {
|
|
63
|
+
const { texture, onChangeIsRenderable } = makeTexture();
|
|
64
|
+
|
|
65
|
+
texture.setRenderableOwner(1, false);
|
|
66
|
+
|
|
67
|
+
expect(texture.renderableOwners.size).toBe(0);
|
|
68
|
+
expect(texture.renderable).toBe(false);
|
|
69
|
+
expect(onChangeIsRenderable).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('1→0 fires onChangeIsRenderable(false) exactly once', () => {
|
|
73
|
+
const { texture, onChangeIsRenderable } = makeTexture();
|
|
74
|
+
|
|
75
|
+
texture.setRenderableOwner(1, true);
|
|
76
|
+
texture.setRenderableOwner(1, false);
|
|
77
|
+
texture.setRenderableOwner(1, false);
|
|
78
|
+
|
|
79
|
+
expect(texture.renderableOwners.size).toBe(0);
|
|
80
|
+
expect(texture.renderable).toBe(false);
|
|
81
|
+
expect(onChangeIsRenderable).toHaveBeenCalledTimes(2);
|
|
82
|
+
expect(onChangeIsRenderable).toHaveBeenLastCalledWith(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('re-adding an owner after 1→0 triggers load again (freed→reload cycle)', () => {
|
|
86
|
+
const { texture, loadTexture } = makeTexture();
|
|
87
|
+
|
|
88
|
+
texture.setRenderableOwner(1, true);
|
|
89
|
+
texture.setRenderableOwner(1, false);
|
|
90
|
+
texture.setRenderableOwner(1, true);
|
|
91
|
+
|
|
92
|
+
expect(texture.renderable).toBe(true);
|
|
93
|
+
expect(loadTexture).toHaveBeenCalledTimes(2);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('string and number owners coexist (node ids vs subtexture/font keys)', () => {
|
|
97
|
+
const { texture } = makeTexture();
|
|
98
|
+
|
|
99
|
+
texture.setRenderableOwner(7, true);
|
|
100
|
+
texture.setRenderableOwner('subtexture-7', true);
|
|
101
|
+
|
|
102
|
+
expect(texture.renderableOwners.size).toBe(2);
|
|
103
|
+
|
|
104
|
+
texture.setRenderableOwner(7, false);
|
|
105
|
+
expect(texture.renderableOwners.size).toBe(1);
|
|
106
|
+
expect(texture.renderable).toBe(true);
|
|
107
|
+
|
|
108
|
+
texture.setRenderableOwner('subtexture-7', false);
|
|
109
|
+
expect(texture.renderableOwners.size).toBe(0);
|
|
110
|
+
expect(texture.renderable).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('canBeCleanedUp respects remaining owners', () => {
|
|
114
|
+
const { texture } = makeTexture();
|
|
115
|
+
// Skip the startup grace period so owners are the deciding factor.
|
|
116
|
+
texture.isWithinStartupGracePeriod = () => false;
|
|
117
|
+
|
|
118
|
+
texture.setRenderableOwner(1, true);
|
|
119
|
+
expect(texture.canBeCleanedUp()).toBe(false);
|
|
120
|
+
|
|
121
|
+
texture.setRenderableOwner(1, false);
|
|
122
|
+
expect(texture.canBeCleanedUp()).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -141,7 +141,7 @@ export abstract class Texture extends EventEmitter {
|
|
|
141
141
|
// aggregate state
|
|
142
142
|
public state: TextureState = 'initial';
|
|
143
143
|
|
|
144
|
-
readonly renderableOwners:
|
|
144
|
+
readonly renderableOwners: Set<string | number> = new Set();
|
|
145
145
|
|
|
146
146
|
readonly renderable: boolean = false;
|
|
147
147
|
|
|
@@ -251,7 +251,7 @@ export abstract class Texture extends EventEmitter {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// Don't cleanup if there are still renderable owners
|
|
254
|
-
if (this.renderableOwners.
|
|
254
|
+
if (this.renderableOwners.size > 0) {
|
|
255
255
|
return false;
|
|
256
256
|
}
|
|
257
257
|
|
|
@@ -274,28 +274,25 @@ export abstract class Texture extends EventEmitter {
|
|
|
274
274
|
* @param renderable
|
|
275
275
|
*/
|
|
276
276
|
setRenderableOwner(owner: string | number, renderable: boolean): void {
|
|
277
|
-
const
|
|
278
|
-
const hasOwnerIndex = this.renderableOwners.indexOf(owner);
|
|
277
|
+
const owners = this.renderableOwners;
|
|
279
278
|
|
|
280
279
|
if (renderable === true) {
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
this.renderableOwners.push(owner);
|
|
280
|
+
if (owners.has(owner) === true) {
|
|
281
|
+
return;
|
|
284
282
|
}
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
if (
|
|
284
|
+
owners.add(owner);
|
|
285
|
+
if (owners.size === 1) {
|
|
288
286
|
(this.renderable as boolean) = true;
|
|
289
287
|
this.onChangeIsRenderable?.(true);
|
|
290
288
|
this.load();
|
|
291
289
|
}
|
|
292
290
|
} else {
|
|
293
|
-
if (
|
|
294
|
-
|
|
291
|
+
if (owners.delete(owner) === false) {
|
|
292
|
+
return;
|
|
295
293
|
}
|
|
296
294
|
|
|
297
|
-
|
|
298
|
-
if (oldSize !== newSize && newSize === 0) {
|
|
295
|
+
if (owners.size === 0) {
|
|
299
296
|
(this.renderable as boolean) = false;
|
|
300
297
|
this.onChangeIsRenderable?.(false);
|
|
301
298
|
|
|
@@ -1104,7 +1104,7 @@ export class Inspector {
|
|
|
1104
1104
|
// Update renderable owners count
|
|
1105
1105
|
div.setAttribute(
|
|
1106
1106
|
'data-texture-owners',
|
|
1107
|
-
String(texture.renderableOwners.
|
|
1107
|
+
String(texture.renderableOwners.size),
|
|
1108
1108
|
);
|
|
1109
1109
|
|
|
1110
1110
|
// Update retry count
|