From 4a02ceaa82a723f5abd702c233d6854ec5f9bde1 Mon Sep 17 00:00:00 2001 From: kayjaydee Date: Thu, 23 Apr 2026 11:49:00 +0200 Subject: [PATCH] fix(region): wrap refreshFor(World) dans world.execute (WorldThread assert) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../gravityflip/region/RegionRegistry.java | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java b/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java index 730d8fe..0dce941 100644 --- a/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java +++ b/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java @@ -168,37 +168,47 @@ public final class RegionRegistry { } if (enabled.isEmpty()) { + // Aucun travail ECS → publication directe depuis le thread appelant. publishSnapshot(world, snapshotOf(world, byRegion)); return; } - try { - Store store = world.getEntityStore().getStore(); - ComponentType TRANSFORM = transform(); - // ComponentType IS-A Query, so TRANSFORM is passed directly (no builder). - store.forEachEntityParallel(TRANSFORM, (index, chunk, cmdBuf) -> { - TransformComponent t = chunk.getComponent(index, TRANSFORM); - // Pinned API 2026.03.26 returns com.hypixel.hytale.math.vector.Vector3d - // (Hytale's own type), NOT org.joml.Vector3d. Same deviation as Phase 02-01. - com.hypixel.hytale.math.vector.Vector3d pos = t.getPosition(); - // Copy to locals — getPosition() returns a backing field; never mutated here. - double x = pos.x, y = pos.y, z = pos.z; - Ref ref = null; - for (GravityFlipRegion r : enabled) { - Box box = r.asBox(); - if (box.containsPosition(x, y, z)) { - if (ref == null) ref = chunk.getReferenceTo(index); - byRegion.get(r).add(ref); + // 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 store = world.getEntityStore().getStore(); + ComponentType TRANSFORM = transform(); + // ComponentType IS-A Query, so TRANSFORM is passed directly (no builder). + store.forEachEntityParallel(TRANSFORM, (index, chunk, cmdBuf) -> { + TransformComponent t = chunk.getComponent(index, TRANSFORM); + // Pinned API 2026.03.26 returns com.hypixel.hytale.math.vector.Vector3d + // (Hytale's own type), NOT org.joml.Vector3d. Same deviation as Phase 02-01. + com.hypixel.hytale.math.vector.Vector3d pos = t.getPosition(); + // Copy to locals — getPosition() returns a backing field; never mutated here. + double x = pos.x, y = pos.y, z = pos.z; + Ref ref = null; + for (GravityFlipRegion r : enabled) { + Box box = r.asBox(); + if (box.containsPosition(x, y, z)) { + if (ref == null) ref = chunk.getReferenceTo(index); + byRegion.get(r).add(ref); + } } - } - }); - } catch (Throwable th) { - // Swallow — publish whatever we collected (possibly empty). The tick loop's - // errorHandler already routes uncaught throwables; this catch keeps the - // scheduler alive across transient ECS-state errors (e.g., world being torn down). - } + }); + } catch (Throwable th) { + // Swallow — publish whatever we collected (possibly empty). The tick loop's + // errorHandler already routes uncaught throwables; this catch keeps the + // scheduler alive across transient ECS-state errors (e.g., world being torn down). + } - publishSnapshot(world, snapshotOf(world, byRegion)); + // 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. */