refactor: clean GSD comments and translate remaining Java sources to English

- Reduce javadocs to one-liners across config/region/physics/tick/viz/plugin root

- Translate residual French comments; no behavioural change

- Tests adjusted where assertions referenced French strings
This commit is contained in:
2026-04-24 17:25:38 +02:00
parent 6b28dc2d2a
commit 6a830ed285
19 changed files with 163 additions and 607 deletions
@@ -22,32 +22,13 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
* In-memory index of {@link GravityFlipRegion}s with two layers of atomic publication:
*
* <ol>
* <li>An {@link AtomicReference} holding an immutable snapshot of the current region list,
* written by CRUD methods and {@link #refreshFromConfig(GravityFlipConfig)} on the command
* thread, read by the tick loop on the scheduler thread without locking.</li>
* <li>A per-{@link World} {@code AtomicReference&lt;RegionSnapshot&gt;} published by
* {@link #refreshFor(World)} every tick; off-thread consumers (Phase 3 physics) read it via
* {@link #currentSnapshot(World)}.</li>
* </ol>
*
* <p><strong>Threading contract:</strong> the tick loop NEVER calls {@code config.get()} directly —
* it only reads the atomic region-list snapshot held inside this registry. Phase 4 command handlers
* mutate the underlying config list (via {@link #add}, {@link #remove}, {@link #setEnabled}, or by
* mutating {@code config.getRegions()} directly and calling {@link #refreshFromConfig}), then call
* {@code configHolder.save().join()} to persist.
* In-memory index of {@link GravityFlipRegion}s with atomic publication for lock-free reads
* from the tick loop. Mutations go through CRUD methods which swap an immutable snapshot.
*/
public final class RegionRegistry {
/**
* Canonical ECS query handle: ComponentType IS-A Query, so passed directly to forEachEntityParallel.
* Lazily initialised because {@code TransformComponent.getComponentType()} triggers static init of
* Hytale {@code PluginBase} → {@code MetricsRegistry} → {@code HytaleLogger}, which throws under JUL
* unless the log manager system property is set. Lazy init keeps the test JVM clean for tests that
* never call {@link #refreshFor(World)}.
*/
// Lazy init: TransformComponent.getComponentType() triggers Hytale PluginBase static init,
// which fails under JUL unless the log manager system property is set — avoided in tests.
private static volatile ComponentType<EntityStore, TransformComponent> transformType;
private static ComponentType<EntityStore, TransformComponent> transform() {
@@ -67,16 +48,10 @@ public final class RegionRegistry {
private final GravityFlipConfig config;
private final Config<GravityFlipConfig> holder; // nullable in tests
/** Immutable snapshot of the region list, swapped atomically on every mutation. */
private final AtomicReference<List<GravityFlipRegion>> regionsSnapshot;
private final Object mutationLock = new Object();
/**
* Snapshot store keyed by {@link World} reference. The map type is {@code Object} (not {@code World})
* for testability: under JDK 25, Mockito cannot mock {@code World} because static-init of its supertype
* {@code PluginBase} fails outside a real server. Tests therefore use any non-null reference as a key
* via the package-private {@link #publishSnapshotByKey} helper.
*/
// Keyed by Object (not World) because Mockito cannot mock World under JDK 25 in tests.
private final ConcurrentHashMap<Object, AtomicReference<RegionSnapshot>> snapshots = new ConcurrentHashMap<>();
public RegionRegistry(GravityFlipConfig cfg) {
@@ -89,14 +64,12 @@ public final class RegionRegistry {
this.regionsSnapshot = new AtomicReference<>(List.copyOf(cfg.getRegions()));
}
// ---------- Region list (atomic snapshot reads) ----------
/** Returns the current immutable region-list snapshot. Safe to call from any thread. */
/** Returns the current immutable region-list snapshot. */
public Collection<GravityFlipRegion> all() {
return regionsSnapshot.get();
}
/** Read-only view of currently-enabled regions, derived from the atomic snapshot (NOT from config). */
/** Read-only view of currently-enabled regions. */
List<GravityFlipRegion> enabled() {
List<GravityFlipRegion> out = new ArrayList<>();
for (GravityFlipRegion r : regionsSnapshot.get()) {
@@ -105,6 +78,7 @@ public final class RegionRegistry {
return out;
}
/** Adds a region; throws IllegalArgumentException if the name already exists. */
public void add(GravityFlipRegion r) {
synchronized (mutationLock) {
for (GravityFlipRegion x : regionsSnapshot.get()) {
@@ -117,6 +91,7 @@ public final class RegionRegistry {
}
}
/** Removes the region with the given name; returns true iff it existed. */
public boolean remove(String name) {
synchronized (mutationLock) {
boolean removed = config.getRegions().removeIf(x -> x.getName().equals(name));
@@ -125,6 +100,7 @@ public final class RegionRegistry {
}
}
/** Toggles the enabled flag on the named region; returns true iff it existed. */
public boolean setEnabled(String name, boolean enabled) {
synchronized (mutationLock) {
for (GravityFlipRegion x : config.getRegions()) {
@@ -138,28 +114,19 @@ public final class RegionRegistry {
}
}
/** Phase 4 hook: re-snapshot the region list after a command handler has mutated the underlying config. */
/** Re-snapshots the region list after the underlying config was mutated externally. */
public void refreshFromConfig(GravityFlipConfig cfg) {
synchronized (mutationLock) {
regionsSnapshot.set(List.copyOf(cfg.getRegions()));
}
}
/** Persists via the bound {@code Config} holder; returns a completed future if no holder is bound (test mode). */
/** Persists via the bound {@code Config} holder (completed future if none is bound). */
public CompletableFuture<Void> save() {
return holder == null ? CompletableFuture.completedFuture(null) : holder.save();
}
// ---------- Per-world snapshot (occupancy) ----------
/**
* Iterates the ECS for {@code world} and publishes a fresh {@link RegionSnapshot} mapping each
* enabled region to the entities currently inside its AABB. Safe to call from a scheduler thread.
*
* <p>READ-ONLY across the trust boundary: TransformComponent positions are only read (copied to
* local doubles before {@link Box#containsPosition(double, double, double)}). Any MUTATIONS must
* go through {@code World.execute(Runnable)} or a {@code CommandBuffer} (Phase 3 concern).
*/
/** Iterates the ECS for {@code world} and publishes a fresh per-region occupancy snapshot. */
public void refreshFor(World world) {
List<GravityFlipRegion> enabled = enabled();
Map<GravityFlipRegion, Collection<Ref<EntityStore>>> byRegion = new ConcurrentHashMap<>();
@@ -168,25 +135,20 @@ 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.
// Store.forEachEntityParallel requires the WorldThread (assertThread), so dispatch via
// world.execute(Runnable). Publication becomes asynchronous (<= 1 tick of lag) — tolerable
// because RegionTickLoop runs @100ms, so snapshot freshness stays <= 100ms in the worst case.
world.execute(() -> {
try {
Store<EntityStore> store = world.getEntityStore().getStore();
ComponentType<EntityStore, TransformComponent> TRANSFORM = transform();
// ComponentType IS-A Query, so TRANSFORM is passed directly (no builder).
// ComponentType IS-A Query, so TRANSFORM is passed directly.
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;
@@ -200,35 +162,29 @@ public final class RegionRegistry {
}
});
} 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).
// Swallow — keeps the scheduler alive across transient ECS-state errors
// (e.g., world being torn down). Publish whatever we collected so far.
}
// 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. */
/** Off-thread consumer entry point; returns null if no snapshot has been published yet. */
public RegionSnapshot currentSnapshot(World world) {
if (world == null) return null;
AtomicReference<RegionSnapshot> ref = snapshots.get(world);
return ref == null ? null : ref.get();
}
/** {@link #refreshFor} helper: publishes an already-built snapshot for the given world. */
void publishSnapshot(World world, RegionSnapshot snap) {
publishSnapshotByKey(world, snap);
}
/** Test hook: publish a snapshot keyed by an arbitrary object reference. */
void publishSnapshotByKey(Object key, RegionSnapshot snap) {
snapshots.computeIfAbsent(key, k -> new AtomicReference<>()).set(snap);
}
/** Test hook: read a snapshot keyed by an arbitrary object reference. */
RegionSnapshot currentSnapshotByKey(Object key) {
AtomicReference<RegionSnapshot> ref = snapshots.get(key);
return ref == null ? null : ref.get();
@@ -238,7 +194,7 @@ public final class RegionRegistry {
Map<GravityFlipRegion, Collection<Ref<EntityStore>>> byRegion) {
long tick = 0L;
try { tick = w.getTick(); } catch (Throwable ignored) {}
final long tickId = Math.max(tick, 1L); // tests assert tickId() > 0
final long tickId = Math.max(tick, 1L);
final Map<GravityFlipRegion, Collection<Ref<EntityStore>>> frozen =
Collections.unmodifiableMap(byRegion);
return new RegionSnapshot() {