diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c5b728990..8d2cf39a9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ https://github.com/nwnxee/unified/compare/build8193.34...HEAD - Optimizations: added `NWNX_OPTIMIZATIONS_CACHE_SCRIPT_CHUNKS` to cache script chunks after first execution. - Optimizations: added `NWNX_OPTIMIZATIONS_CLIENT_GAMEOBJECT_UPDATE_TIME` to change the global client gameobject update time. - Optimizations: added `NWNX_OPTIMIZATIONS_CLIENT_GAMEOBJECT_UPDATE_TIME_LOADING` to change the client gameobject update time for players loading an area. +- Optimizations: added `NWNX_OPTIMIZATIONS_EVENT_MASTER_PRIORITY_QUEUE` to replace AI event linked list with a priority queue, improving performance in some circumstances. - Events: added skippable event `NWNX_ON_INPUT_DROP_ITEM_{BEFORE|AFTER}` which fires when a player attempts to drop an item. - Events: added skippable event `NWNX_ON_DECREMENT_SPELL_COUNT_{BEFORE|AFTER}` which fires when spell count (Memorized, non-memorized, or spell-like ability) decreases. - Events: added skippable event `NWNX_ON_DEBUG_PLAY_VISUAL_EFFECT_{BEFORE|AFTER}` which fires when the dm_visualeffect console command is used. diff --git a/Plugins/Optimizations/CMakeLists.txt b/Plugins/Optimizations/CMakeLists.txt index 7139f9774c6..532f3e9c3f4 100644 --- a/Plugins/Optimizations/CMakeLists.txt +++ b/Plugins/Optimizations/CMakeLists.txt @@ -6,4 +6,5 @@ add_plugin(Optimizations "ReconcileAutoMap.cpp" "CacheScriptChunks.cpp" "ClientGameObjectUpdateTime.cpp" + "EventMasterPriorityQueue.cpp" ) diff --git a/Plugins/Optimizations/EventMasterPriorityQueue.cpp b/Plugins/Optimizations/EventMasterPriorityQueue.cpp new file mode 100644 index 00000000000..fe525d439ce --- /dev/null +++ b/Plugins/Optimizations/EventMasterPriorityQueue.cpp @@ -0,0 +1,95 @@ +#include "nwnx.hpp" + +#include "API/CAppManager.hpp" +#include "API/CGameObjectArray.hpp" +#include "API/CServerAIMaster.hpp" +#include "API/CServerExoApp.hpp" +#include "API/CServerExoAppInternal.hpp" +#include "API/CWorldTimer.hpp" +#include "API/CNWSPlayer.hpp" +#include "API/CNWSObject.hpp" +#include "API/CNWSCreature.hpp" +#include "API/CExoBase.hpp" +#include "API/CExoTimers.hpp" +#include "API/CNetLayer.hpp" +#include "API/CNWSMessage.hpp" + +#include + +namespace Optimizations +{ + +using namespace NWNXLib; +using namespace NWNXLib::API; + +struct CServerAIEventNodeComparator +{ + bool operator()(const CServerAIEventNode& lhs, const CServerAIEventNode& rhs) const + { + return (lhs.m_nCalendarDay == rhs.m_nCalendarDay) ? (lhs.m_nTimeOfDay > rhs.m_nTimeOfDay) : (lhs.m_nCalendarDay > rhs.m_nCalendarDay); + } +}; + +static std::priority_queue, CServerAIEventNodeComparator> s_event_queue; + +static void ClearEventQueue(CServerAIMaster*) +{ + s_event_queue = {}; +} + +static BOOL AddEventAbsoluteTime(CServerAIMaster*, uint32_t nCalendarDay, uint32_t nTimeOfDay, OBJECT_ID nCallerObjectId, OBJECT_ID nObjectId, uint32_t nEventId, void *pEventData) +{ + s_event_queue.emplace(CServerAIEventNode { nCalendarDay, nTimeOfDay, nCallerObjectId, nObjectId, nEventId, pEventData }); + return true; +} + +static BOOL EventPending(CServerAIMaster* ai, uint32_t nCalendarDay, uint32_t nTimeOfDay) +{ + if (s_event_queue.empty()) return false; + + const CServerAIEventNode& eventNode = s_event_queue.top(); + return ai->m_pExoAppInternal->m_pWorldTimer->CompareWorldTimes(eventNode.m_nCalendarDay, eventNode.m_nTimeOfDay, nCalendarDay, nTimeOfDay) <= 0; +} + +static BOOL GetPendingEvent(CServerAIMaster*, uint32_t *nCalendarDay, uint32_t *nTimeOfDay, OBJECT_ID *nCallerObjectId, OBJECT_ID *nObjectId, uint32_t *nEventId, void **pEventData) +{ + if (s_event_queue.empty()) return false; + + const CServerAIEventNode& eventNode = s_event_queue.top(); + + *nCalendarDay = eventNode.m_nCalendarDay; + *nTimeOfDay = eventNode.m_nTimeOfDay; + *nCallerObjectId = eventNode.m_nCallerObjectId; + *nObjectId = eventNode.m_nObjectId; + *nEventId = eventNode.m_nEventId; + *pEventData = eventNode.m_pEventData; + + s_event_queue.pop(); + return true; +} + +void EventMasterPriorityQueue() __attribute__((constructor)); + +void EventMasterPriorityQueue() +{ + // Replace: + // + // * CServerAIMaster::ClearEventQueue() + // * CServerAIMaster::AddEventAbsoluteTime() + // * CServerAIMaster::AddEventAbsoluteTimeViaTail() + // * CServerAIMaster::EventPending() + // * CServerAIMaster::GetPendingEvent() + // + // Don't care about save/load event queue. + + if (Config::Get("EVENT_MASTER_PRIORITY_QUEUE", false)) + { + static Hooks::Hook _0 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster15ClearEventQueueEv, (void*)&ClearEventQueue, Hooks::Order::Final); + static Hooks::Hook _1 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster20AddEventAbsoluteTimeEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Final); + static Hooks::Hook _2 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster27AddEventAbsoluteTimeViaTailEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Final); + static Hooks::Hook _3 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster12EventPendingEjj, (void*)&EventPending, Hooks::Order::Final); + static Hooks::Hook _4 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster15GetPendingEventEPjS0_S0_S0_S0_PPv, (void*)&GetPendingEvent, Hooks::Order::Final); + } +} + +} diff --git a/Plugins/Optimizations/README.md b/Plugins/Optimizations/README.md index 84ce71abc14..146a0265c37 100644 --- a/Plugins/Optimizations/README.md +++ b/Plugins/Optimizations/README.md @@ -18,3 +18,4 @@ Game optimizations. Improves performance of various game elements. | `NWNX_OPTIMIZATIONS_CACHE_SCRIPT_CHUNKS` | true/false | Caches all script chunks, improving performance | | `NWNX_OPTIMIZATIONS_CLIENT_GAMEOBJECT_UPDATE_TIME` | int | The global client gameobject update time in microseconds, default 200000 (200 milliseconds) | | `NWNX_OPTIMIZATIONS_CLIENT_GAMEOBJECT_UPDATE_TIME_LOADING` | int | The client gameobject update time in microseconds for players loading an area, default 200000 (200 milliseconds) | +| `NWNX_OPTIMIZATIONS_EVENT_MASTER_PRIORITY_QUEUE` | bool | Replaces the AI event linked list with a priority queue, improving event insert speed from o(n) to o(log n), and eliminating many allocations |