From 1b03009fa30e845a880625aba27ee3b8afa49827 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 9 Aug 2026 13:11:38 -0400 Subject: [PATCH] Periodically sync remote gates instead of only loading once at startup Gates were only ever loaded from the shared database at plugin enable or /sg reload, so a server that's already running never found out about a gate another backend created afterward - the row existed in MySQL, but nothing told this JVM to look again. Reported as "no other gates on network 'main'" despite both gates being visibly present in the database. Added GateManager.refreshRemoteGates(), scheduled every cross-server.refresh-seconds (default 30) whenever cross-server is enabled: it re-queries storage off the main thread, then merges only OTHER servers' gates into gatesById on the main thread - adding new ones, removing deleted ones, and never touching this server's own gates or any live open/connected/outgoing runtime state. --- README.md | 7 +++ .../stargate/paper/StargatePlugin.java | 3 ++ .../stargate/paper/gate/GateManager.java | 44 +++++++++++++++++++ stargate-paper/src/main/resources/config.yml | 6 +++ 4 files changed, 60 insertions(+) diff --git a/README.md b/README.md index f2e8b63..d215f5c 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,13 @@ proxy (over the `stargate:teleport` plugin channel) to connect the player to tha server; once they land, the proxy forwards a delivery message so the destination server's Stargate instance teleports them to the gate's exit point. +Each backend only loads gates from the shared database at startup (and on +`/sg reload`), then polls for what other servers have added, changed, or removed +every `cross-server.refresh-seconds` (default 30) while `cross-server.enabled: true`. +So a gate built on one server shows up as a destination on another within that +window automatically - no restart or manual reload needed, though `/sg reload` +still works if you want it immediately. + ## Permissions - `stargate.use` (default: true) — dial/cycle gates diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/StargatePlugin.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/StargatePlugin.java index b4ec921..2c479e5 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/StargatePlugin.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/StargatePlugin.java @@ -41,6 +41,9 @@ public class StargatePlugin extends JavaPlugin { this.crossServerBridge = new CrossServerBridge(this, gateManager); if (crossServer) { this.crossServerBridge.register(); + int refreshSeconds = Math.max(5, getConfig().getInt("cross-server.refresh-seconds", 30)); + getServer().getScheduler().runTaskTimerAsynchronously(this, gateManager::refreshRemoteGates, + 20L * refreshSeconds, 20L * refreshSeconds); } getServer().getPluginManager().registerEvents(new SignCreateListener(this, gateManager), this); diff --git a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java index 4c9a68d..6133ff9 100644 --- a/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java +++ b/stargate-paper/src/main/java/dev/skywalker3200/stargate/paper/gate/GateManager.java @@ -80,6 +80,50 @@ public class GateManager { plugin.getLogger().info("[Stargate] Loaded " + gatesById.size() + " gate(s), " + gatesBySignBlock.size() + " local."); } + /** + * Picks up gates created/removed on OTHER servers since the last load, without touching + * anything local (structures, open/connected/outgoing runtime state). loadAll() only ever + * runs at startup or /sg reload, so without this, a server that's already running never + * finds out about a gate another backend created after it booted - the shared MySQL row + * exists, but nothing tells this JVM to look. Safe to call from any thread; the actual + * gatesById mutation is scheduled back onto the main thread. + */ + public void refreshRemoteGates() { + List all; + try { + all = storage.loadAll(); + } catch (Exception e) { + plugin.getLogger().warning("[Stargate] Failed to refresh remote gates: " + e.getMessage()); + return; + } + Bukkit.getScheduler().runTask(plugin, () -> mergeRemoteGates(all)); + } + + private void mergeRemoteGates(List all) { + Set currentRemoteIds = new HashSet<>(); + int added = 0; + for (Gate gate : all) { + if (gate.getServerId().equals(plugin.getServerId())) continue; // local gates are handled by loadAll()/live state + currentRemoteIds.add(gate.getId()); + if (!gatesById.containsKey(gate.getId())) { + gatesById.put(gate.getId(), new RuntimeGate(gate, null)); + added++; + } + } + int removed = 0; + var it = gatesById.entrySet().iterator(); + while (it.hasNext()) { + RuntimeGate rg = it.next().getValue(); + if (!rg.getGate().getServerId().equals(plugin.getServerId()) && !currentRemoteIds.contains(rg.getGate().getId())) { + it.remove(); + removed++; + } + } + if (added > 0 || removed > 0) { + plugin.getLogger().info("[Stargate] Remote gate sync: +" + added + " -" + removed); + } + } + private String signKey(String world, int x, int y, int z) { return world + "," + x + "," + y + "," + z; } diff --git a/stargate-paper/src/main/resources/config.yml b/stargate-paper/src/main/resources/config.yml index 63b2c15..d23beac 100644 --- a/stargate-paper/src/main/resources/config.yml +++ b/stargate-paper/src/main/resources/config.yml @@ -33,6 +33,12 @@ storage: # share gate data. cross-server: enabled: false + # How often (in seconds) this server checks the shared database for gates other + # servers have created, updated, or removed since it last looked. Without this, a + # server that's already running never finds out about a gate another backend made + # after it booted - the row exists, but nothing prompts a reload. Doesn't touch + # this server's own gates or any live dial/open state. + refresh-seconds: 30 gate: # Any of these materials count as the ring's frame (and count as a valid chevron