feat(03-05): cable RegionVisualizer dans RegionTickLoop + shutdown clearAll (Task 3)

- RegionTickLoop: nouveau ctor 4-arg (registry, gravityApplier, regionVisualizer,
  errorHandler). Les ctors 2-arg et 3-arg existants delegent (back-compat tests).
  Tick appelle regionVisualizer.visualize(w, snapshot) apres gravityApplier.apply()
  avec null-safe gate (meme pattern que gravityApplier).
- GravityFlipPlugin.start(): construit RegionVisualizer avec errorHandler log WARN,
  le passe au tickLoop.
- GravityFlipPlugin.shutdown(): appelle regionVisualizer.clearAll(world) AVANT
  tickLoop.stop() (si Universe encore vivante), emet ClearDebugShapes a tous
  les PlayerRefs. Si universe null, fallback TTL expiration.
- Aucun nouveau scheduler/thread cree.
This commit is contained in:
2026-04-23 14:55:24 +02:00
parent 99b9072b78
commit 3511493687
2 changed files with 24 additions and 2 deletions
@@ -11,6 +11,7 @@ import com.mythlane.gravityflip.physics.FallDamageSuppressorSystem;
import com.mythlane.gravityflip.physics.GravityApplier; import com.mythlane.gravityflip.physics.GravityApplier;
import com.mythlane.gravityflip.region.RegionRegistry; import com.mythlane.gravityflip.region.RegionRegistry;
import com.mythlane.gravityflip.tick.RegionTickLoop; import com.mythlane.gravityflip.tick.RegionTickLoop;
import com.mythlane.gravityflip.viz.RegionVisualizer;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level; import java.util.logging.Level;
@@ -41,6 +42,7 @@ public class GravityFlipPlugin extends JavaPlugin {
private RegionTickLoop tickLoop; private RegionTickLoop tickLoop;
private GravityApplier gravityApplier; private GravityApplier gravityApplier;
private FallDamageGuard fallDamageGuard; private FallDamageGuard fallDamageGuard;
private RegionVisualizer regionVisualizer;
public GravityFlipPlugin(JavaPluginInit init) { public GravityFlipPlugin(JavaPluginInit init) {
super(init); super(init);
@@ -77,7 +79,9 @@ public class GravityFlipPlugin extends JavaPlugin {
th -> getLogger().at(Level.WARNING).withCause(th).log("gravityApply failed"), th -> getLogger().at(Level.WARNING).withCause(th).log("gravityApply failed"),
msg -> getLogger().at(Level.INFO).log("%s", msg), msg -> getLogger().at(Level.INFO).log("%s", msg),
fallDamageGuard); fallDamageGuard);
this.tickLoop = new RegionTickLoop(registry, gravityApplier, th -> this.regionVisualizer = new RegionVisualizer(
th -> getLogger().at(Level.WARNING).withCause(th).log("regionVisualize failed"));
this.tickLoop = new RegionTickLoop(registry, gravityApplier, regionVisualizer, th ->
getLogger().at(Level.WARNING).withCause(th).log("detectTick failed")); getLogger().at(Level.WARNING).withCause(th).log("detectTick failed"));
// Lazy world resolution — see setup() comment. // Lazy world resolution — see setup() comment.
@@ -103,6 +107,13 @@ public class GravityFlipPlugin extends JavaPlugin {
@Override @Override
protected void shutdown() { protected void shutdown() {
// Plan 03-05 : clear des debug shapes cote clients AVANT tickLoop.stop().
// Si l'Universe est deja fermee, world==null => shapes expireront via TTL (acceptable).
if (regionVisualizer != null) {
Universe u = Universe.get();
World w = (u == null) ? null : u.getDefaultWorld();
if (w != null) regionVisualizer.clearAll(w);
}
// Stop the detector BEFORE super.shutdown() so no tick races plugin teardown. // Stop the detector BEFORE super.shutdown() so no tick races plugin teardown.
if (tickLoop != null) tickLoop.stop(); if (tickLoop != null) tickLoop.stop();
// No auto-save contract: any mutation made during the session must already // No auto-save contract: any mutation made during the session must already
@@ -3,6 +3,7 @@ package com.mythlane.gravityflip.tick;
import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.World;
import com.mythlane.gravityflip.physics.GravityApplier; import com.mythlane.gravityflip.physics.GravityApplier;
import com.mythlane.gravityflip.region.RegionRegistry; import com.mythlane.gravityflip.region.RegionRegistry;
import com.mythlane.gravityflip.viz.RegionVisualizer;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -34,14 +35,21 @@ public final class RegionTickLoop {
private final Consumer<Throwable> errorHandler; private final Consumer<Throwable> errorHandler;
private final RegionRegistry registry; private final RegionRegistry registry;
private final GravityApplier gravityApplier; private final GravityApplier gravityApplier;
private final RegionVisualizer regionVisualizer;
public RegionTickLoop(RegionRegistry registry, Consumer<Throwable> errorHandler) { public RegionTickLoop(RegionRegistry registry, Consumer<Throwable> errorHandler) {
this(registry, null, errorHandler); this(registry, null, null, errorHandler);
} }
public RegionTickLoop(RegionRegistry registry, GravityApplier gravityApplier, Consumer<Throwable> errorHandler) { public RegionTickLoop(RegionRegistry registry, GravityApplier gravityApplier, Consumer<Throwable> errorHandler) {
this(registry, gravityApplier, null, errorHandler);
}
public RegionTickLoop(RegionRegistry registry, GravityApplier gravityApplier,
RegionVisualizer regionVisualizer, Consumer<Throwable> errorHandler) {
this.registry = registry; this.registry = registry;
this.gravityApplier = gravityApplier; this.gravityApplier = gravityApplier;
this.regionVisualizer = regionVisualizer;
this.errorHandler = errorHandler == null ? t -> {} : errorHandler; this.errorHandler = errorHandler == null ? t -> {} : errorHandler;
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "GravityFlip-Detect"); Thread t = new Thread(r, "GravityFlip-Detect");
@@ -75,6 +83,9 @@ public final class RegionTickLoop {
if (gravityApplier != null) { if (gravityApplier != null) {
gravityApplier.apply(w, registry.currentSnapshot(w)); gravityApplier.apply(w, registry.currentSnapshot(w));
} }
if (regionVisualizer != null) {
regionVisualizer.visualize(w, registry.currentSnapshot(w));
}
}; };
scheduleGuarded(initialDelayMs, tick); scheduleGuarded(initialDelayMs, tick);
} }