fix(region): wrap refreshFor(World) dans world.execute (WorldThread assert)

- RegionRegistry.refreshFor dispatche scan ECS + publishSnapshot via world.execute
- Publication devient asynchrone (1-tick staleness max, borne par tick loop @100ms)
- Null-path enabled.isEmpty publie directement (pas de travail ECS requis)
- Amendement D-04 du plan 03-01 : world.execute requis côté initiation de forEachEntityParallel
This commit is contained in:
2026-04-23 11:49:00 +02:00
parent af02e19685
commit 4a02ceaa82
@@ -168,10 +168,17 @@ public final class RegionRegistry {
}
if (enabled.isEmpty()) {
// Aucun travail ECS → publication directe depuis le thread appelant.
publishSnapshot(world, snapshotOf(world, byRegion));
return;
}
// THREADING (fix WorldThread assert 2026-04-23) : `Store.forEachEntityParallel` exige la
// WorldThread. On dispatche scan + publication via `world.execute(Runnable)` pour satisfaire
// `assertThread`. Conséquence : la publication devient asynchrone (1 tick décalé max) côté
// consumers de `currentSnapshot(world)` — tolérable car le RegionTickLoop tourne @100ms, donc
// la fraîcheur du snapshot reste ≤ 100ms dans le pire cas.
world.execute(() -> {
try {
Store<EntityStore> store = world.getEntityStore().getStore();
ComponentType<EntityStore, TransformComponent> TRANSFORM = transform();
@@ -198,7 +205,10 @@ public final class RegionRegistry {
// scheduler alive across transient ECS-state errors (e.g., world being torn down).
}
// Publication intra-Runnable : garantit que la table byRegion est complète quand
// on la fige dans snapshotOf(...).
publishSnapshot(world, snapshotOf(world, byRegion));
});
}
/** Off-thread consumer entry point. Returns {@code null} if no snapshot has been published yet. */