From 4c93c8fea7c2f1b2a4188fd94e32d5e55af78faa Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Wed, 30 Sep 2026 04:03:22 -0500 Subject: [PATCH] feat(dashboard): draining (old version) badge; properties update when empty Co-Authored-By: Claude Opus 5.5 --- dDashboardServer/DashboardServer.cpp | 1 + dDashboardServer/routes/LiveWorld.cpp | 4 ++-- dDashboardServer/routes/ServerState.h | 5 ++++- dDashboardServer/templates/index.jinja2 | 8 ++++++-- dDashboardServer/templates/instances.jinja2 | 7 +++++-- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dDashboardServer/DashboardServer.cpp b/dDashboardServer/DashboardServer.cpp index 3f00708ac..0710815cb 100644 --- a/dDashboardServer/DashboardServer.cpp +++ b/dDashboardServer/DashboardServer.cpp @@ -229,6 +229,7 @@ namespace { info.ip = instance.ip.string; info.port = instance.port; info.isPrivate = instance.isPrivate != 0; + info.outdated = instance.outdated != 0; info.zoneName = GameText::ZoneName(info.mapID); AddPropertyDetails(info); using eState = MasterPackets::ServerListResponse::eState; diff --git a/dDashboardServer/routes/LiveWorld.cpp b/dDashboardServer/routes/LiveWorld.cpp index 984ad7baa..4f1e550cf 100644 --- a/dDashboardServer/routes/LiveWorld.cpp +++ b/dDashboardServer/routes/LiveWorld.cpp @@ -253,13 +253,13 @@ namespace LiveWorld { for (const auto& w : ServerState::g_WorldInstances) { worlds.push_back({ {"mapID", w.mapID}, {"zoneName", GameText::ZoneName(w.mapID)}, {"instanceID", w.instanceID}, {"cloneID", w.cloneID}, {"players", w.players}, {"isPrivate", w.isPrivate}, {"ip", w.ip}, {"port", w.port}, {"propertyId", w.propertyId}, {"propertyName", w.propertyName}, - {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state} }); + {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state}, {"outdated", w.outdated} }); } // Launched but not connected yet, or shutting down for (const auto& w : ServerState::g_PendingWorlds) { worlds.push_back({ {"mapID", w.mapID}, {"zoneName", GameText::ZoneName(w.mapID)}, {"instanceID", w.instanceID}, {"cloneID", w.cloneID}, {"players", 0}, {"isPrivate", w.isPrivate}, {"ip", w.ip}, {"port", w.port}, {"propertyId", w.propertyId}, {"propertyName", w.propertyName}, - {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state} }); + {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state}, {"outdated", w.outdated} }); } JsonSuccess(reply, { {"worlds", worlds} }); }); diff --git a/dDashboardServer/routes/ServerState.h b/dDashboardServer/routes/ServerState.h index c28258208..6acdec293 100644 --- a/dDashboardServer/routes/ServerState.h +++ b/dDashboardServer/routes/ServerState.h @@ -42,6 +42,8 @@ struct WorldInstanceInfo { // "starting" (launched, not connected yet) or "stopping" (in g_PendingWorlds), or "draining" for a world that is up // but whose players are being moved to a new instance (in g_WorldInstances); empty for one that is just up std::string state{}; + // On an old binary or old zone files (a live update or world reload): takes nobody new and stops once empty + bool outdated{false}; }; // Where a server listens and the machine it runs on, from master's server list @@ -88,7 +90,8 @@ namespace ServerState { {"players", world.players}, {"isPrivate", world.isPrivate}, {"zoneName", world.zoneName}, - {"state", world.state} + {"state", world.state}, + {"outdated", world.outdated} }); } diff --git a/dDashboardServer/templates/index.jinja2 b/dDashboardServer/templates/index.jinja2 index 0161aee90..a6f443002 100644 --- a/dDashboardServer/templates/index.jinja2 +++ b/dDashboardServer/templates/index.jinja2 @@ -60,8 +60,9 @@

Moves everything onto the server binaries on disk now, without a restart: the UGC server finishes its jobs and restarts, auth restarts (logins pause for a few seconds), chat hands its teams over and restarts, every world instance is replaced by a new one and its players are moved there (the game's Mythran maintenance warning, then a short loading screen), and - the dashboard restarts last. Properties are saved before their new instance loads them; races and minigames finish first. - Master keeps running: updating master itself takes a normal restart.

+ the dashboard restarts last. Races and minigames finish first. Properties are not moved (building in progress + isn't saved): their players are told an update is waiting, and a property updates once everyone has left it. Instances on the old + build take nobody new. Master keeps running: updating master itself takes a normal restart.

Warn players @@ -214,6 +215,9 @@ $(function () { if (row.state === 'starting') return ' ' + fmt.badge('Starting', 'info'); if (row.state === 'stopping') return ' ' + fmt.badge('Shutting down', 'secondary'); if (row.state === 'draining') return ' ' + fmt.badge('Moving players', 'warning'); + // Old binary or old zone files: nobody new goes there; it stops once the players still there have left + if (row.outdated) return ' ' + fmt.badge('Draining (old version)', 'warning') + ' ' + + Number(row.players || 0).toLocaleString() + ' player(s) still there'; return ''; } // Zone names in the viewer's language (the live status carries the server's) diff --git a/dDashboardServer/templates/instances.jinja2 b/dDashboardServer/templates/instances.jinja2 index 9657801a7..8bd13c330 100644 --- a/dDashboardServer/templates/instances.jinja2 +++ b/dDashboardServer/templates/instances.jinja2 @@ -90,7 +90,9 @@

The zone files each running world loaded (.luz, .lvl, .lutriggers, terrain, navmesh). When one changes on disk, the master server replaces the instances that loaded the old version with new ones and moves their - players over. Reload does the same for every instance of a zone now. Files read from the client's packs are not watched.

+ players over. Reload does the same for every instance of a zone now. Properties are not moved (building in + progress isn't saved): an instance of a property on old files takes nobody new, its players are told an update is waiting, and it updates + once everyone has left. Files read from the client's packs are not watched.

Waiting for the master server…

@@ -257,7 +259,8 @@ (z.changed ? ' ' + fmt.badge('changed on disk', 'warning') : '') + '' + '
'; var instances = '
' + z.instances.map(function (i) { - var badges = (i.stale ? ' ' + fmt.badge('old files', 'warning') : '') + (i.reloading ? ' ' + fmt.badge('being replaced', 'info') : ''); + var badges = (i.stale ? ' ' + fmt.badge('old files', 'warning') : '') + (i.reloading ? ' ' + fmt.badge('being replaced', 'info') : + i.outdated ? ' ' + fmt.badge(i.clone ? 'updates when empty' : 'draining (old version)', 'secondary') : ''); return 'Instance ' + esc(i.instance) + (i.clone ? ' (clone ' + esc(i.clone) + ')' : '') + ', ' + nf.format(i.players) + ' player(s)' + badges + ''; }).join('') + '
';