@forgeax/engine-scene 0.1.33 → 0.1.34
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/README.md +8 -6
- package/dist/__tests__/hierarchy-dirty.unit.test.d.ts +2 -0
- package/dist/__tests__/hierarchy-dirty.unit.test.d.ts.map +1 -0
- package/dist/index.mjs +82 -33
- package/dist/index.mjs.map +1 -1
- package/dist/systems/propagate-transforms.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/flat-propagation.contract.test.ts +3 -1
- package/src/__tests__/flat-propagation.perf.test.ts +3 -1
- package/src/__tests__/hierarchy-dirty.unit.test.ts +68 -0
- package/src/__tests__/propagation.unit.test.ts +4 -0
- package/src/__tests__/transform-carrier-prototype.bench.ts +2 -2
- package/src/__tests__/transform-carrier-prototype.test.ts +1 -1
- package/src/systems/propagate-transforms.ts +104 -40
package/README.md
CHANGED
|
@@ -101,12 +101,14 @@ subtree is exposed as contiguous spans without a parallel event journal.
|
|
|
101
101
|
`Transform` remains authored local TRS; `GlobalTransform` is the resolved
|
|
102
102
|
world-space authority.
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
A structural or parent-relation change validates the full hierarchy through
|
|
105
|
+
ECS-maintained `Children` lists. Between those changes, independent `changed`
|
|
106
|
+
queries select the highest edited Transform ancestors and only their subtrees
|
|
107
|
+
are expanded in parent-first order. A second FixedUpdate/Update propagation
|
|
108
|
+
without intervening writes performs no hierarchy traversal. External writes to
|
|
109
|
+
`GlobalTransform` trigger reconciliation; the owner's own publications are
|
|
110
|
+
consumed before returning. `Children` remains a read-only relationship projection;
|
|
111
|
+
there is no duplicate Scene graph or per-node matrix cache.
|
|
110
112
|
|
|
111
113
|
Removing `ChildOf` is a structural root transition. Even when the authored
|
|
112
114
|
`Transform` is unchanged, the next propagation composes and publishes that
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy-dirty.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hierarchy-dirty.unit.test.ts"],"names":[],"mappings":""}
|
package/dist/index.mjs
CHANGED
|
@@ -1897,6 +1897,9 @@ function ensureQueries(world, scratch) {
|
|
|
1897
1897
|
without: [ChildOf],
|
|
1898
1898
|
changed: [Transform]
|
|
1899
1899
|
});
|
|
1900
|
+
scratch.dirtyTransforms ??= world.query({ read: [Transform], changed: [Transform] }).unwrap();
|
|
1901
|
+
scratch.dirtyParents ??= world.query({ read: [ChildOf], changed: [ChildOf] }).unwrap();
|
|
1902
|
+
scratch.dirtyGlobals ??= world.query({ read: [GlobalTransform], changed: [GlobalTransform] }).unwrap();
|
|
1900
1903
|
const hierarchy = world.query({ read: [Transform, ChildOf], write: [GlobalTransform] });
|
|
1901
1904
|
const transform = world.query({ read: [Transform], write: [GlobalTransform] });
|
|
1902
1905
|
const missingGlobal = world.query({ with: [Transform], without: [GlobalTransform] });
|
|
@@ -1960,7 +1963,8 @@ function scratchFor(world) {
|
|
|
1960
1963
|
flatChanged: [],
|
|
1961
1964
|
flatBindingTables: [],
|
|
1962
1965
|
flatBindingRows: [],
|
|
1963
|
-
flatStructureEpoch: -1
|
|
1966
|
+
flatStructureEpoch: -1,
|
|
1967
|
+
hierarchyStructureEpoch: -1
|
|
1964
1968
|
};
|
|
1965
1969
|
SCRATCH.set(world, created);
|
|
1966
1970
|
return created;
|
|
@@ -2249,7 +2253,7 @@ function handleActiveCycle(repeated, stackEntities, stackChildren, writer, bindi
|
|
|
2249
2253
|
stackChildren[index] = 0;
|
|
2250
2254
|
}
|
|
2251
2255
|
}
|
|
2252
|
-
function walkChildren(world, root, writer, bindings, transformWriter, transformBindings, scratch, report, allowCompletedRoot) {
|
|
2256
|
+
function walkChildren(world, root, writer, bindings, transformWriter, transformBindings, scratch, report, allowCompletedRoot, refresh = false) {
|
|
2253
2257
|
countPropagation("hierarchyRootInvocations");
|
|
2254
2258
|
const stackEntities = scratch.hierarchyStackEntities;
|
|
2255
2259
|
const stackChildren = scratch.hierarchyStackChildren;
|
|
@@ -2268,7 +2272,7 @@ function walkChildren(world, root, writer, bindings, transformWriter, transformB
|
|
|
2268
2272
|
const rootBinding = findHierarchyLocation(writer, bindings, root, rootCursor);
|
|
2269
2273
|
if (rootBinding !== void 0) {
|
|
2270
2274
|
const state = scratch.hierarchyStates[rootCursor.bindingIndex]?.[rootCursor.row] ?? 0;
|
|
2271
|
-
if (state !== 0) return;
|
|
2275
|
+
if (state !== 0 && !refresh) return;
|
|
2272
2276
|
}
|
|
2273
2277
|
}
|
|
2274
2278
|
stackEntities.push(root);
|
|
@@ -2288,6 +2292,10 @@ function walkChildren(world, root, writer, bindings, transformWriter, transformB
|
|
|
2288
2292
|
const nextChild = stackChildren[top] ?? -1;
|
|
2289
2293
|
if (nextChild < 0) {
|
|
2290
2294
|
if (hierarchyBinding !== void 0) {
|
|
2295
|
+
if (refresh) {
|
|
2296
|
+
const states = scratch.hierarchyStates[currentCursor.bindingIndex];
|
|
2297
|
+
if (states !== void 0) states[currentCursor.row] = 0;
|
|
2298
|
+
}
|
|
2291
2299
|
const state = scratch.hierarchyStates[currentCursor.bindingIndex]?.[currentCursor.row] ?? 0;
|
|
2292
2300
|
if (state === 0) {
|
|
2293
2301
|
const states = scratch.hierarchyStates[currentCursor.bindingIndex];
|
|
@@ -2465,7 +2473,7 @@ function walkChildren(world, root, writer, bindings, transformWriter, transformB
|
|
|
2465
2473
|
scratch.hierarchyChanged,
|
|
2466
2474
|
report
|
|
2467
2475
|
);
|
|
2468
|
-
} else if (childState === 0) {
|
|
2476
|
+
} else if (childState === 0 || refresh) {
|
|
2469
2477
|
stackEntities.push(child);
|
|
2470
2478
|
stackChildren.push(-1);
|
|
2471
2479
|
}
|
|
@@ -2620,7 +2628,7 @@ function resolveResidualPath(world, entity, writer, bindings, transformWriter, t
|
|
|
2620
2628
|
return;
|
|
2621
2629
|
}
|
|
2622
2630
|
}
|
|
2623
|
-
function propagateHierarchy(world, scratch) {
|
|
2631
|
+
function propagateHierarchy(world, scratch, globalEdited) {
|
|
2624
2632
|
const hierarchyWriter = scratch.hierarchyWriter;
|
|
2625
2633
|
const transformWriter = scratch.transformWriter;
|
|
2626
2634
|
if (hierarchyWriter === void 0 || transformWriter === void 0) {
|
|
@@ -2639,20 +2647,34 @@ function propagateHierarchy(world, scratch) {
|
|
|
2639
2647
|
scratch.hierarchyBindingRows.length = 0;
|
|
2640
2648
|
scratch.hierarchyStates.length = 0;
|
|
2641
2649
|
scratch.hierarchyChanged.length = 0;
|
|
2650
|
+
scratch.hierarchyStructureEpoch = world.getStructureEpoch();
|
|
2642
2651
|
return ok6(void 0);
|
|
2643
2652
|
}
|
|
2653
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
2654
|
+
for (const row of scratch.dirtyTransforms ?? []) dirty.add(row.entity);
|
|
2655
|
+
let rebuild = scratch.hierarchyStructureEpoch !== world.getStructureEpoch();
|
|
2656
|
+
for (const _row of scratch.dirtyParents ?? []) rebuild = true;
|
|
2657
|
+
rebuild ||= globalEdited;
|
|
2658
|
+
if (!rebuild && dirty.size === 0) return ok6(void 0);
|
|
2644
2659
|
ensureHierarchyBuffers(scratch, hierarchyBindings);
|
|
2645
|
-
resetHierarchyBuffers(scratch);
|
|
2660
|
+
if (rebuild) resetHierarchyBuffers(scratch);
|
|
2661
|
+
else for (const changed of scratch.hierarchyChanged) changed.fill(0);
|
|
2646
2662
|
let firstError;
|
|
2647
2663
|
const report = (error) => {
|
|
2648
2664
|
firstError = noteHierarchyError(firstError, error);
|
|
2649
2665
|
};
|
|
2650
|
-
|
|
2651
|
-
const
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2666
|
+
if (!rebuild) {
|
|
2667
|
+
for (const entity of dirty) {
|
|
2668
|
+
let parent = world[worldRead].getFieldValue(entity, ChildOf, "parent");
|
|
2669
|
+
let covered = false;
|
|
2670
|
+
while (parent !== void 0 && parent !== ENTITY_NULL_RAW2) {
|
|
2671
|
+
if (dirty.has(parent)) {
|
|
2672
|
+
covered = true;
|
|
2673
|
+
break;
|
|
2674
|
+
}
|
|
2675
|
+
parent = world[worldRead].getFieldValue(parent, ChildOf, "parent");
|
|
2676
|
+
}
|
|
2677
|
+
if (!covered)
|
|
2656
2678
|
walkChildren(
|
|
2657
2679
|
world,
|
|
2658
2680
|
entity,
|
|
@@ -2662,29 +2684,50 @@ function propagateHierarchy(world, scratch) {
|
|
|
2662
2684
|
transformBindings,
|
|
2663
2685
|
scratch,
|
|
2664
2686
|
report,
|
|
2665
|
-
false
|
|
2687
|
+
false,
|
|
2688
|
+
true
|
|
2666
2689
|
);
|
|
2690
|
+
}
|
|
2691
|
+
} else {
|
|
2692
|
+
for (const binding of transformBindings) {
|
|
2693
|
+
const entities = binding.entities;
|
|
2694
|
+
for (let row = 0; row < binding.rowCapacity; row += 1) {
|
|
2695
|
+
const entity = entities[row] ?? 0;
|
|
2696
|
+
const parentRaw = world[worldRead].getFieldValue(entity, ChildOf, "parent");
|
|
2697
|
+
if (parentRaw === void 0 || parentRaw === ENTITY_NULL_RAW2) {
|
|
2698
|
+
walkChildren(
|
|
2699
|
+
world,
|
|
2700
|
+
entity,
|
|
2701
|
+
hierarchyWriter,
|
|
2702
|
+
hierarchyBindings,
|
|
2703
|
+
transformWriter,
|
|
2704
|
+
transformBindings,
|
|
2705
|
+
scratch,
|
|
2706
|
+
report,
|
|
2707
|
+
false
|
|
2708
|
+
);
|
|
2709
|
+
}
|
|
2667
2710
|
}
|
|
2668
2711
|
}
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2712
|
+
for (let bindingIndex = 0; bindingIndex < hierarchyBindings.length; bindingIndex += 1) {
|
|
2713
|
+
const binding = hierarchyBindings[bindingIndex];
|
|
2714
|
+
if (binding === void 0) continue;
|
|
2715
|
+
const entities = binding.entities;
|
|
2716
|
+
const states = scratch.hierarchyStates[bindingIndex];
|
|
2717
|
+
for (let row = 0; row < binding.rowCapacity; row += 1) {
|
|
2718
|
+
if ((states?.[row] ?? 0) !== 0) continue;
|
|
2719
|
+
const entity = entities[row] ?? 0;
|
|
2720
|
+
resolveResidualPath(
|
|
2721
|
+
world,
|
|
2722
|
+
entity,
|
|
2723
|
+
hierarchyWriter,
|
|
2724
|
+
hierarchyBindings,
|
|
2725
|
+
transformWriter,
|
|
2726
|
+
transformBindings,
|
|
2727
|
+
scratch,
|
|
2728
|
+
report
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2688
2731
|
}
|
|
2689
2732
|
}
|
|
2690
2733
|
for (let bindingIndex = 0; bindingIndex < hierarchyBindings.length; bindingIndex += 1) {
|
|
@@ -2697,15 +2740,21 @@ function propagateHierarchy(world, scratch) {
|
|
|
2697
2740
|
report(derivedWriteError(cause, bindingIndex));
|
|
2698
2741
|
}
|
|
2699
2742
|
}
|
|
2743
|
+
scratch.hierarchyStructureEpoch = firstError === void 0 ? world.getStructureEpoch() : -1;
|
|
2700
2744
|
return firstError === void 0 ? ok6(void 0) : err6(firstError);
|
|
2701
2745
|
}
|
|
2702
2746
|
function propagateTransforms(world) {
|
|
2703
2747
|
const scratch = scratchFor(world);
|
|
2704
2748
|
const pairs = validateTransformPairs(world, scratch);
|
|
2705
2749
|
if (!pairs.ok) return pairs;
|
|
2750
|
+
let globalEdited = false;
|
|
2751
|
+
for (const _row of scratch.dirtyGlobals ?? []) globalEdited = true;
|
|
2706
2752
|
const flat = propagateFlat(world, scratch);
|
|
2707
2753
|
if (!flat.ok) return flat;
|
|
2708
|
-
|
|
2754
|
+
const hierarchy = propagateHierarchy(world, scratch, globalEdited);
|
|
2755
|
+
for (const _row of scratch.dirtyGlobals ?? []) {
|
|
2756
|
+
}
|
|
2757
|
+
return hierarchy;
|
|
2709
2758
|
}
|
|
2710
2759
|
var PropagateTransforms = defineSystem({
|
|
2711
2760
|
name: PROPAGATE_TRANSFORMS_SYSTEM,
|