mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(world-reload): keep properties until empty
Busy property instances on changed files are kept (KEEP_UNTIL_EMPTY) instead of saved, frozen and moved. Every instance acted on is marked outdated; the files status carries the flag (bit 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ namespace {
|
||||
nlohmann::json instances = nlohmann::json::array();
|
||||
for (const auto& instance : zone.instances) {
|
||||
instances.push_back({ {"instance", instance.instanceId}, {"clone", instance.cloneId}, {"players", instance.players},
|
||||
{"stale", instance.stale}, {"reloading", instance.reloading} });
|
||||
{"stale", instance.stale}, {"reloading", instance.reloading}, {"outdated", instance.outdated} });
|
||||
}
|
||||
zones.push_back({ {"zone", zone.zoneId}, {"zoneName", GameText::ZoneName(zone.zoneId)}, {"changed", changed},
|
||||
{"files", files}, {"instances", instances}, {"message", zone.message} });
|
||||
|
||||
@@ -216,6 +216,9 @@ namespace WorldFileWatch {
|
||||
START_THEN_STOP, // nobody there, but the zone always has an instance: start a new one, stop this one
|
||||
STOP, // nobody there: stop it (a new instance starts when someone goes there)
|
||||
SKIP,
|
||||
// A property with players: never moved (building in progress isn't saved). It takes nobody new, its players
|
||||
// are told an update is waiting, and it stops once everyone left (OutdatedInstances.h)
|
||||
KEEP_UNTIL_EMPTY,
|
||||
};
|
||||
|
||||
inline const char* ActionName(eAction action) {
|
||||
@@ -224,6 +227,7 @@ namespace WorldFileWatch {
|
||||
case eAction::START_THEN_STOP: return "start a new one, then stop it";
|
||||
case eAction::STOP: return "stop";
|
||||
case eAction::SKIP: return "skip";
|
||||
case eAction::KEEP_UNTIL_EMPTY: return "keep until everyone left";
|
||||
}
|
||||
return "skip";
|
||||
}
|
||||
@@ -235,8 +239,8 @@ namespace WorldFileWatch {
|
||||
};
|
||||
|
||||
/**
|
||||
* What to do with each instance wanted() picks: replace it when players are there, stop it when it is empty (a zone
|
||||
* in keepZones gets one new public instance first). Character selection loads no zone; instances still starting
|
||||
* What to do with each instance wanted() picks: replace it when players are there (a property is kept until they
|
||||
* all left instead), stop it when it is empty (a zone in keepZones gets one new public instance first). Character selection loads no zone; instances still starting
|
||||
* load the files on disk now (and report them); instances shutting down or being emptied already are left alone.
|
||||
*/
|
||||
inline std::vector<Choice> Choose(const std::vector<InstanceMigration::InstanceView>& instances,
|
||||
@@ -250,7 +254,7 @@ namespace WorldFileWatch {
|
||||
else if (view.shuttingDown) choice.reason = "Already shutting down";
|
||||
else if (view.draining) choice.reason = "Its players are already being moved";
|
||||
else if (!view.ready) choice.reason = "Still starting; it loads the files on disk now";
|
||||
else if (view.players > 0) choice.action = eAction::REPLACE;
|
||||
else if (view.players > 0) choice.action = view.cloneId != 0 ? eAction::KEEP_UNTIL_EMPTY : eAction::REPLACE;
|
||||
else choice.action = eAction::STOP;
|
||||
choices.push_back(std::move(choice));
|
||||
}
|
||||
|
||||
@@ -105,11 +105,23 @@ namespace {
|
||||
for (const auto& choice : choices) {
|
||||
const auto& view = choice.view;
|
||||
auto& counts = perZone[view.zoneId];
|
||||
if (choice.action != eAction::SKIP) {
|
||||
// On the old files: nobody new goes there (a request starts an instance on the files on disk now)
|
||||
Game::im->MarkOutdated([&view](const Instance& instance) {
|
||||
return instance.GetMapID() == view.zoneId && instance.GetInstanceID() == view.instanceId;
|
||||
});
|
||||
}
|
||||
switch (choice.action) {
|
||||
case eAction::SKIP:
|
||||
LOG("World reload (%s): zone %u instance %u left alone: %s", by.c_str(), view.zoneId, view.instanceId, choice.reason.c_str());
|
||||
counts.second++;
|
||||
continue;
|
||||
case eAction::KEEP_UNTIL_EMPTY:
|
||||
LOG("World reload (%s): property zone %u clone %u instance %u keeps its %d player(s); it takes nobody new and stops once empty",
|
||||
by.c_str(), view.zoneId, view.cloneId, view.instanceId, view.players);
|
||||
counts.first++;
|
||||
acted++;
|
||||
continue;
|
||||
case eAction::STOP:
|
||||
case eAction::START_THEN_STOP: {
|
||||
const auto& instance = Game::im->FindInstanceWithPrivate(static_cast<LWOMAPID>(view.zoneId), static_cast<LWOINSTANCEID>(view.instanceId));
|
||||
@@ -141,9 +153,8 @@ namespace {
|
||||
request.requesterId = requesterId;
|
||||
request.requestedBy = ("world reload (" + by + ")").substr(0, InstanceMigrationRequest::MAX_BY);
|
||||
MigrationCoordinator::Options options;
|
||||
// Properties, private instances and activity zones are moved too; a property is saved and frozen first
|
||||
// Private instances and activity zones are moved too (properties never get here: KEEP_UNTIL_EMPTY)
|
||||
options.liveUpdate = true;
|
||||
options.prepare = view.cloneId != 0;
|
||||
g_Migrations[request.requestId] = { view.zoneId, view.instanceId };
|
||||
const auto refusal = MigrationCoordinator::Start(request, options);
|
||||
if (refusal != InstanceMigration::eRefusal::NONE) {
|
||||
@@ -159,7 +170,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
for (const auto& [zone, counts] : perZone) {
|
||||
std::string message = "Reload (" + by + "): " + std::to_string(counts.first) + " instance(s) replaced or stopped";
|
||||
std::string message = "Reload (" + by + "): " + std::to_string(counts.first) + " instance(s) replaced, stopped or left to empty";
|
||||
if (counts.second) message += ", " + std::to_string(counts.second) + " left alone";
|
||||
SetZoneMessage(zone, message);
|
||||
}
|
||||
@@ -224,6 +235,7 @@ namespace {
|
||||
row.players = instance->GetCurrentClientCount();
|
||||
row.stale = g_Tracker.IsStale({ zoneId, row.instanceId });
|
||||
row.reloading = instance->GetIsShuttingDown() || instance->GetIsDraining();
|
||||
row.outdated = instance->GetIsOutdated();
|
||||
zone.instances.push_back(row);
|
||||
}
|
||||
if (const auto message = g_ZoneMessages.find(zoneId); message != g_ZoneMessages.end()) zone.message = message->second;
|
||||
|
||||
@@ -127,6 +127,7 @@ struct WorldFilesStatus : public LUBitStream {
|
||||
int32_t players{};
|
||||
bool stale{}; // loaded a version of a file that isn't on disk any more
|
||||
bool reloading{}; // being replaced, or shutting down
|
||||
bool outdated{}; // takes nobody new and stops once empty (a property kept until its players left)
|
||||
|
||||
bool operator==(const Instance& other) const = default;
|
||||
};
|
||||
@@ -168,7 +169,7 @@ struct WorldFilesStatus : public LUBitStream {
|
||||
stream.Write(instance.instanceId);
|
||||
stream.Write(instance.cloneId);
|
||||
stream.Write(instance.players);
|
||||
stream.Write<uint8_t>((instance.stale ? 1 : 0) | (instance.reloading ? 2 : 0));
|
||||
stream.Write<uint8_t>((instance.stale ? 1 : 0) | (instance.reloading ? 2 : 0) | (instance.outdated ? 4 : 0));
|
||||
}
|
||||
InstanceMigration::WriteText(stream, zone.message, WorldFiles::MAX_MESSAGE);
|
||||
}
|
||||
@@ -201,6 +202,7 @@ struct WorldFilesStatus : public LUBitStream {
|
||||
if (!stream.Read(instance.instanceId) || !stream.Read(instance.cloneId) || !stream.Read(instance.players) || !stream.Read(flags)) return false;
|
||||
instance.stale = flags & 1;
|
||||
instance.reloading = flags & 2;
|
||||
instance.outdated = flags & 4;
|
||||
}
|
||||
if (!InstanceMigration::ReadText(stream, zone.message, WorldFiles::MAX_MESSAGE)) return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ TEST(WorldFileWatchTests, ChooseReplacesBusyAndStopsEmptyInstances) {
|
||||
View(1100, 1, 5),
|
||||
View(1100, 2, 0),
|
||||
View(1200, 3, 2),
|
||||
View(1150, 4, 1, 77), // a property keeps its clone: replaced like any other
|
||||
View(1150, 4, 1, 77), // a property with players: never moved, kept until they left
|
||||
View(0, 5, 3), // character selection
|
||||
};
|
||||
auto starting = View(1100, 6, 0);
|
||||
@@ -212,7 +212,7 @@ TEST(WorldFileWatchTests, ChooseReplacesBusyAndStopsEmptyInstances) {
|
||||
EXPECT_EQ(byInstance.size(), 7u); // 1200 wasn't wanted
|
||||
EXPECT_EQ(byInstance[1], eAction::REPLACE);
|
||||
EXPECT_EQ(byInstance[2], eAction::STOP);
|
||||
EXPECT_EQ(byInstance[4], eAction::REPLACE);
|
||||
EXPECT_EQ(byInstance[4], eAction::KEEP_UNTIL_EMPTY);
|
||||
EXPECT_EQ(byInstance[5], eAction::SKIP);
|
||||
EXPECT_EQ(byInstance[6], eAction::SKIP);
|
||||
EXPECT_EQ(byInstance[7], eAction::SKIP);
|
||||
|
||||
Reference in New Issue
Block a user