From a57e9c9e8c8b0f1acea910d6c45f3081c883c62f Mon Sep 17 00:00:00 2001 From: sruon Date: Wed, 6 May 2026 22:42:56 -0600 Subject: [PATCH] Lazy load instances --- scripts/tests/systems/instances.lua | 19 ++++ src/map/map_engine.cpp | 2 +- src/map/time_server.cpp | 2 +- src/map/utils/instanceutils.cpp | 131 +++++++++++++++++++++++----- src/map/utils/instanceutils.h | 23 ++++- 5 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 scripts/tests/systems/instances.lua diff --git a/scripts/tests/systems/instances.lua b/scripts/tests/systems/instances.lua new file mode 100644 index 00000000000..7d9bf910ac6 --- /dev/null +++ b/scripts/tests/systems/instances.lua @@ -0,0 +1,19 @@ +describe('Instances', function() + it('Waking the Colossus spawns Alexander', function() + local player = xi.test.world:spawnPlayer({ zone = xi.zone.ALZADAAL_UNDERSEA_RUINS }) + + player:createInstance(7702) + xi.test.world:tick(xi.tick.TIME) + + local instance = player:getInstance() + assert(instance and instance:getID() == 7702) + + for _, mob in pairs(instance:getMobs()) do + if mob:getName() == 'Alexander_WTC' then + return + end + end + + assert(false, 'Alexander_WTC not spawned') + end) +end) diff --git a/src/map/map_engine.cpp b/src/map/map_engine.cpp index 7bbb000d0bb..36705b5b510 100644 --- a/src/map/map_engine.cpp +++ b/src/map/map_engine.cpp @@ -203,10 +203,10 @@ auto MapEngine::init() -> Task } co_await zoneutils::Initialize(scheduler_, config_); + instanceutils::Initialize(config_); if (!config_.lazyZones) { - instanceutils::LoadInstanceList(mapIPP); CTransportHandler::getInstance()->InitializeTransport(mapIPP); } diff --git a/src/map/time_server.cpp b/src/map/time_server.cpp index f206cbebff3..eca794842d3 100644 --- a/src/map/time_server.cpp +++ b/src/map/time_server.cpp @@ -173,7 +173,7 @@ auto time_server(Scheduler& scheduler, MapConfig config) -> Task CTriggerHandler::getInstance()->triggerTimer(); CTransportHandler::getInstance()->TransportTimer(); - instanceutils::CheckInstance(); + co_await instanceutils::CheckInstance(scheduler, config); co_await zoneutils::ProcessLoadQueue(scheduler, config); luautils::OnTimeServerTick(); luautils::TryReloadFilewatchList(); diff --git a/src/map/utils/instanceutils.cpp b/src/map/utils/instanceutils.cpp index 5459ded768d..e825a4eade0 100644 --- a/src/map/utils/instanceutils.cpp +++ b/src/map/utils/instanceutils.cpp @@ -30,6 +30,7 @@ #include "map_engine.h" #include "zoneutils.h" +#include #include namespace instanceutils @@ -37,19 +38,46 @@ namespace instanceutils std::unordered_map InstanceData; std::queue> LoadQueue; // player id, instance id +detail::LazyLoadState lazyLoad; -void LoadInstanceList(IPP mapIPP) +namespace { - const auto rset = db::preparedStmt("SELECT instanceid,instance_name,instance_zone,entrance_zone," - "time_limit,start_x,start_y,start_z," - "start_rot,instance_list.music_day,instance_list.music_night,instance_list.battlesolo," - "instance_list.battlemulti,zone_settings.name AS zone_name " - "FROM instance_list INNER JOIN zone_settings " + +auto GetInstancesAssignedToThisProcess(IPP mapIPP) -> std::vector +{ + std::vector result; + + const auto rset = db::preparedStmt("SELECT instanceid FROM instance_list INNER JOIN zone_settings " "ON instance_zone = zone_settings.zoneid " "WHERE IF(? <> 0, ? = zoneip AND ? = zoneport, TRUE)", mapIPP.getIP(), mapIPP.getIPString(), mapIPP.getPort()); + FOR_DB_MULTIPLE_RESULTS(rset) + { + result.emplace_back(rset->get("instanceid")); + } + + return result; +} + +auto LoadInstances(const std::vector& instanceIds) -> void +{ + if (instanceIds.empty()) + { + return; + } + + const auto query = fmt::format("SELECT instanceid,instance_name,instance_zone,entrance_zone," + "time_limit,start_x,start_y,start_z," + "start_rot,instance_list.music_day,instance_list.music_night,instance_list.battlesolo," + "instance_list.battlemulti,zone_settings.name AS zone_name " + "FROM instance_list INNER JOIN zone_settings " + "ON instance_zone = zone_settings.zoneid " + "WHERE instanceid IN ({})", + fmt::join(instanceIds, ",")); + const auto rset = db::preparedStmt(query); + FOR_DB_MULTIPLE_RESULTS(rset) { InstanceData_t data; @@ -85,7 +113,7 @@ void LoadInstanceList(IPP mapIPP) } // Meta data - data.instance_zone_name = zoneutils::GetZone(data.instance_zone)->getName(); + data.instance_zone_name = rset->get("zone_name"); data.entrance_zone_name = rset->get("zone_name"); data.filename = fmt::format("./scripts/zones/{}/instances/{}.lua", data.instance_zone_name, data.instance_name); @@ -97,43 +125,98 @@ void LoadInstanceList(IPP mapIPP) } } +} // namespace + +// Initialize instance loading: immediate (load all now) or lazy (load on first access) +auto Initialize(MapConfig config) -> void +{ + const auto instanceIds = GetInstancesAssignedToThisProcess(config.ipp); + + if (!config.lazyZones) + { + LoadInstances(instanceIds); + return; + } + + lazyLoad.enabled = true; + lazyLoad.managedInstances = std::set(instanceIds.begin(), instanceIds.end()); +} + // NOTE: This used to be multithreaded, but was starting to cause problems with repeated loading // and loading in quick succession, so we've swapped it out for a queue which services a // single request at the end of every tick. // TODO: Make this multithreaded and not blocking the main tick loop -void CheckInstance() +auto CheckInstance(Scheduler& scheduler, MapConfig config) -> Task { - if (!LoadQueue.empty()) + if (LoadQueue.empty()) + { + co_return; + } + + const auto requestPair = LoadQueue.front(); + auto* PRequester = zoneutils::GetChar(requestPair.first); + if (!PRequester) { - auto requestPair = LoadQueue.front(); + ShowError("Encountered invalid requester id when loading instance!"); LoadQueue.pop(); + co_return; + } - auto* PRequester = zoneutils::GetChar(requestPair.first); - if (!PRequester) - { - ShowError("Encountered invalid requester id when loading instance!"); - return; - } - auto instanceId = requestPair.second; + const auto instanceId = requestPair.second; + const auto data = GetInstanceData(instanceId); - auto loader = std::make_unique(instanceId, PRequester); - loader->LoadInstance(); + // CInstanceLoader requires the instance template zone to be loaded. + const bool zoneReady = co_await zoneutils::IsZoneReady(scheduler, config, data.instance_zone); + if (!zoneReady) + { + co_return; } + + LoadQueue.pop(); + + auto loader = std::make_unique(instanceId, PRequester); + loader->LoadInstance(); } -void LoadInstance(uint32 instanceid, CCharEntity* PRequester) +auto LoadInstance(uint32 instanceid, CCharEntity* PRequester) -> void { LoadQueue.emplace(PRequester->id, instanceid); } -InstanceData_t GetInstanceData(uint32 instanceid) +auto GetInstanceData(uint32 instanceid) -> InstanceData_t { - return InstanceData[instanceid]; + const auto key = static_cast(instanceid); + + if (auto it = InstanceData.find(key); it != InstanceData.end()) + { + return it->second; + } + + if (lazyLoad.enabled && lazyLoad.managedInstances.contains(key)) + { + LoadInstances({ key }); + + if (auto it = InstanceData.find(key); it != InstanceData.end()) + { + return it->second; + } + + ShowWarning("instanceutils::GetInstanceData: managed instanceid %u has no row in instance_list", instanceid); + } + + return {}; } -bool IsValidInstanceID(uint32 instanceid) +auto IsValidInstanceID(uint32 instanceid) -> bool { - return InstanceData.find(instanceid) != InstanceData.end(); + const auto key = static_cast(instanceid); + + if (InstanceData.contains(key)) + { + return true; + } + + return lazyLoad.enabled && lazyLoad.managedInstances.contains(key); } }; // namespace instanceutils diff --git a/src/map/utils/instanceutils.h b/src/map/utils/instanceutils.h index 4e55012eb3a..208252229a3 100644 --- a/src/map/utils/instanceutils.h +++ b/src/map/utils/instanceutils.h @@ -24,6 +24,11 @@ #include "common/cbasetypes.h" #include "common/database.h" #include "common/ipp.h" +#include "common/scheduler.h" + +#include "map_config.h" + +#include class CInstanceLoader; class CCharEntity; @@ -66,11 +71,21 @@ struct InstanceData_t namespace instanceutils { +namespace detail +{ + +struct LazyLoadState +{ + bool enabled{ false }; + std::set managedInstances{}; +}; + +} // namespace detail -void LoadInstanceList(IPP mapIPP); -void CheckInstance(); // Called at the end of every tick by time_server -void LoadInstance(uint32 instanceid, CCharEntity* PRequester); +auto Initialize(MapConfig config) -> void; +auto CheckInstance(Scheduler& scheduler, MapConfig config) -> Task; // Called at the end of every tick by time_server +auto LoadInstance(uint32 instanceid, CCharEntity* PRequester) -> void; auto GetInstanceData(uint32 instanceid) -> InstanceData_t; -bool IsValidInstanceID(uint32 instanceid); +auto IsValidInstanceID(uint32 instanceid) -> bool; }; // namespace instanceutils