feat(02-02): add RegionTickLoop + plugin wiring (Task 3)
- RegionTickLoop: single-thread daemon scheduler @100ms, 2s initial delay,
shutdown -> awaitTermination(5s) -> shutdownNow lifecycle.
Three start overloads: (World), (Supplier<World>), (Runnable). Errors are
routed to a Consumer<Throwable> so an exception in one tick never kills
the scheduler.
- GravityFlipPlugin.start():
* builds RegionRegistry from configHolder.get() + holder for save()
* starts the tick loop with a Supplier<World> that lazily resolves
Universe.get().getDefaultWorld() (deviation: PrepareUniverseEvent in the
pinned API only carries WorldConfigProvider — no Universe/World access,
so we use the lazy-supplier pattern from MythWorld instead)
* registers the ScheduledFuture with TaskRegistry (raw cast, try/catch fallback)
- GravityFlipPlugin.shutdown(): tickLoop.stop() BEFORE super.shutdown()
- RegionTickLoopTest: 3 timing tests pass (>=8 ticks/sec, stop within 5s,
exception resilience).
- gradle build green; fat jar contains both region/ and tick/ class dirs.
This commit is contained in:
@@ -2,9 +2,14 @@ package com.mythlane.gravityflip;
|
||||
|
||||
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||
import com.hypixel.hytale.server.core.universe.Universe;
|
||||
import com.hypixel.hytale.server.core.universe.world.World;
|
||||
import com.hypixel.hytale.server.core.util.Config;
|
||||
import com.mythlane.gravityflip.config.GravityFlipConfig;
|
||||
import com.mythlane.gravityflip.region.RegionRegistry;
|
||||
import com.mythlane.gravityflip.tick.RegionTickLoop;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -29,6 +34,9 @@ public class GravityFlipPlugin extends JavaPlugin {
|
||||
private final Config<GravityFlipConfig> configHolder =
|
||||
withConfig("regions", GravityFlipConfig.CODEC);
|
||||
|
||||
private RegionRegistry registry;
|
||||
private RegionTickLoop tickLoop;
|
||||
|
||||
public GravityFlipPlugin(JavaPluginInit init) {
|
||||
super(init);
|
||||
}
|
||||
@@ -37,17 +45,60 @@ public class GravityFlipPlugin extends JavaPlugin {
|
||||
protected void setup() {
|
||||
// NOTE: do NOT call configHolder.get() here — it blocks until preLoad() completes.
|
||||
// Safe call sites are start() and any later lifecycle phase (incl. tick loop).
|
||||
//
|
||||
// World acquisition note (Phase 02-02): the plan called for a PrepareUniverseEvent
|
||||
// listener that stashes a World reference. Empirically, PrepareUniverseEvent
|
||||
// (com.hypixel.hytale.server.core.event.events.PrepareUniverseEvent) only carries
|
||||
// a WorldConfigProvider — it does NOT expose a Universe or World. We therefore use
|
||||
// a Supplier<World> that resolves Universe.get().getDefaultWorld() lazily on each
|
||||
// tick (matching the MythWorld WorldBorderManager precedent). Until the universe
|
||||
// is ready, the supplier returns null and the tick is a no-op.
|
||||
getLogger().at(Level.INFO).log("Gravity Flip enabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void start() {
|
||||
super.start();
|
||||
GravityFlipConfig cfg = configHolder.get();
|
||||
this.registry = new RegionRegistry(cfg, configHolder);
|
||||
this.tickLoop = new RegionTickLoop(registry, th ->
|
||||
getLogger().at(Level.WARNING).withCause(th).log("detectTick failed"));
|
||||
|
||||
// Lazy world resolution — see setup() comment.
|
||||
this.tickLoop.startWithDelay(2_000L, () -> {
|
||||
Universe u = Universe.get();
|
||||
return u == null ? null : u.getDefaultWorld();
|
||||
});
|
||||
|
||||
// TaskRegistry registration: registerTask only accepts ScheduledFuture<Void>; the
|
||||
// scheduler returns ScheduledFuture<?>. Cast via raw types per Mythlane idiom; the
|
||||
// try/catch falls back to manual shutdown() if registration fails (deterministic
|
||||
// either way because shutdown() always invokes tickLoop.stop()).
|
||||
try {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ScheduledFuture<Void> vf = (ScheduledFuture) tickLoop.future();
|
||||
getTaskRegistry().registerTask(vf);
|
||||
} catch (Throwable ignored) { /* manual shutdown() fallback */ }
|
||||
|
||||
getLogger().at(Level.INFO).log(
|
||||
"Gravity Flip enabled — %d region(s) loaded, detector @100ms",
|
||||
cfg.getRegions().size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdown() {
|
||||
// Stop the detector BEFORE super.shutdown() so no tick races plugin teardown.
|
||||
if (tickLoop != null) tickLoop.stop();
|
||||
// No auto-save contract: any mutation made during the session must already
|
||||
// have been persisted via configHolder().save() by the command handler that
|
||||
// performed it. See configHolder() javadoc.
|
||||
getLogger().at(Level.INFO).log("Gravity Flip disabled");
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
/** Exposed for Phase 3 (gravity physics) and Phase 4 (commands). */
|
||||
public RegionRegistry regions() { return registry; }
|
||||
|
||||
/**
|
||||
* Accessor for the region config holder. <strong>SAVE CONTRACT:</strong> any
|
||||
* caller that mutates {@code configHolder().get().getRegions()} MUST call
|
||||
|
||||
Reference in New Issue
Block a user