@forgeax/engine-scene 0.1.33 → 0.1.35

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 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
- For a valid hierarchy, propagation expands the ECS-maintained `Children`
105
- buffer from each root in parent-first order. `ChildOf` is the writable source
106
- fact; `Children` is a read-only materialized target, so source mutations and
107
- their reverse lists converge before the frame pass. The numeric traversal
108
- borrows ECS columns and uses only constant matrix scratch plus depth/row
109
- markers; it does not build a Scene graph or per-node matrix cache.
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hierarchy-dirty.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy-dirty.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hierarchy-dirty.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sparse-global-observation.contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sparse-global-observation.contract.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sparse-global-observation.contract.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
- for (const binding of transformBindings) {
2651
- const entities = binding.entities;
2652
- for (let row = 0; row < binding.rowCapacity; row += 1) {
2653
- const entity = entities[row] ?? 0;
2654
- const parentRaw = world[worldRead].getFieldValue(entity, ChildOf, "parent");
2655
- if (parentRaw === void 0 || parentRaw === ENTITY_NULL_RAW2) {
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
- for (let bindingIndex = 0; bindingIndex < hierarchyBindings.length; bindingIndex += 1) {
2671
- const binding = hierarchyBindings[bindingIndex];
2672
- if (binding === void 0) continue;
2673
- const entities = binding.entities;
2674
- const states = scratch.hierarchyStates[bindingIndex];
2675
- for (let row = 0; row < binding.rowCapacity; row += 1) {
2676
- if ((states?.[row] ?? 0) !== 0) continue;
2677
- const entity = entities[row] ?? 0;
2678
- resolveResidualPath(
2679
- world,
2680
- entity,
2681
- hierarchyWriter,
2682
- hierarchyBindings,
2683
- transformWriter,
2684
- transformBindings,
2685
- scratch,
2686
- report
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 _span of scratch.dirtyGlobals?.spans().unwrap() ?? []) globalEdited = true;
2706
2752
  const flat = propagateFlat(world, scratch);
2707
2753
  if (!flat.ok) return flat;
2708
- return propagateHierarchy(world, scratch);
2754
+ const hierarchy = propagateHierarchy(world, scratch, globalEdited);
2755
+ for (const _span of scratch.dirtyGlobals?.spans().unwrap() ?? []) {
2756
+ }
2757
+ return hierarchy;
2709
2758
  }
2710
2759
  var PropagateTransforms = defineSystem({
2711
2760
  name: PROPAGATE_TRANSFORMS_SYSTEM,