From 0d4ef1be5c6a224b71a373316767b8a730460759 Mon Sep 17 00:00:00 2001 From: Liareth Date: Sun, 14 Aug 2022 20:38:59 +0100 Subject: [PATCH 1/4] Add EVENT_MASTER_PRIORITY_QUEUE optimization. --- Plugins/Optimizations/CMakeLists.txt | 1 + .../EventMasterPriorityQueue.cpp | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 Plugins/Optimizations/EventMasterPriorityQueue.cpp 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..9ddf4aa44a7 --- /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; + } +}; + +std::priority_queue, CServerAIEventNodeComparator> g_event_queue; + +void ClearEventQueue(CServerAIMaster*) +{ + g_event_queue = {}; +} + +BOOL AddEventAbsoluteTime(CServerAIMaster*, uint32_t nCalendarDay, uint32_t nTimeOfDay, OBJECT_ID nCallerObjectId, OBJECT_ID nObjectId, uint32_t nEventId, void *pEventData) +{ + g_event_queue.emplace(CServerAIEventNode { nCalendarDay, nTimeOfDay, nCallerObjectId, nObjectId, nEventId, pEventData }); + return true; +} + +BOOL EventPending(CServerAIMaster* ai, uint32_t nCalendarDay, uint32_t nTimeOfDay) +{ + if (g_event_queue.empty()) return false; + + const CServerAIEventNode& eventNode = g_event_queue.top(); + return ai->m_pExoAppInternal->m_pWorldTimer->CompareWorldTimes(eventNode.m_nCalendarDay, eventNode.m_nTimeOfDay, nCalendarDay, nTimeOfDay) <= 0; +} + +BOOL GetPendingEvent(CServerAIMaster*, uint32_t *nCalendarDay, uint32_t *nTimeOfDay, OBJECT_ID *nCallerObjectId, OBJECT_ID *nObjectId, uint32_t *nEventId, void **pEventData) +{ + if (g_event_queue.empty()) return false; + + const CServerAIEventNode& eventNode = g_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; + + g_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::Early); + static Hooks::Hook _1 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster20AddEventAbsoluteTimeEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Early); + static Hooks::Hook _2 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster27AddEventAbsoluteTimeViaTailEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Early); + static Hooks::Hook _3 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster12EventPendingEjj, (void*)&EventPending, Hooks::Order::Early); + static Hooks::Hook _4 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster15GetPendingEventEPjS0_S0_S0_S0_PPv, (void*)&GetPendingEvent, Hooks::Order::Early); + } +} + +} From 30370625d8ee84b13a4c2799f198d061cd43a3d4 Mon Sep 17 00:00:00 2001 From: Liareth Date: Sun, 14 Aug 2022 23:09:09 +0100 Subject: [PATCH 2/4] Add documentation. --- Plugins/Optimizations/README.md | 1 + 1 file changed, 1 insertion(+) 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 | From 082ce6fb57924a289b3b1f908134439471c5cb85 Mon Sep 17 00:00:00 2001 From: Liareth Date: Sun, 14 Aug 2022 23:30:39 +0100 Subject: [PATCH 3/4] Review feedback. --- .../EventMasterPriorityQueue.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Plugins/Optimizations/EventMasterPriorityQueue.cpp b/Plugins/Optimizations/EventMasterPriorityQueue.cpp index 9ddf4aa44a7..fe525d439ce 100644 --- a/Plugins/Optimizations/EventMasterPriorityQueue.cpp +++ b/Plugins/Optimizations/EventMasterPriorityQueue.cpp @@ -26,36 +26,36 @@ 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; + return (lhs.m_nCalendarDay == rhs.m_nCalendarDay) ? (lhs.m_nTimeOfDay > rhs.m_nTimeOfDay) : (lhs.m_nCalendarDay > rhs.m_nCalendarDay); } }; -std::priority_queue, CServerAIEventNodeComparator> g_event_queue; +static std::priority_queue, CServerAIEventNodeComparator> s_event_queue; -void ClearEventQueue(CServerAIMaster*) +static void ClearEventQueue(CServerAIMaster*) { - g_event_queue = {}; + s_event_queue = {}; } -BOOL AddEventAbsoluteTime(CServerAIMaster*, uint32_t nCalendarDay, uint32_t nTimeOfDay, OBJECT_ID nCallerObjectId, OBJECT_ID nObjectId, uint32_t nEventId, void *pEventData) +static BOOL AddEventAbsoluteTime(CServerAIMaster*, uint32_t nCalendarDay, uint32_t nTimeOfDay, OBJECT_ID nCallerObjectId, OBJECT_ID nObjectId, uint32_t nEventId, void *pEventData) { - g_event_queue.emplace(CServerAIEventNode { nCalendarDay, nTimeOfDay, nCallerObjectId, nObjectId, nEventId, pEventData }); + s_event_queue.emplace(CServerAIEventNode { nCalendarDay, nTimeOfDay, nCallerObjectId, nObjectId, nEventId, pEventData }); return true; } -BOOL EventPending(CServerAIMaster* ai, uint32_t nCalendarDay, uint32_t nTimeOfDay) +static BOOL EventPending(CServerAIMaster* ai, uint32_t nCalendarDay, uint32_t nTimeOfDay) { - if (g_event_queue.empty()) return false; + if (s_event_queue.empty()) return false; - const CServerAIEventNode& eventNode = g_event_queue.top(); + const CServerAIEventNode& eventNode = s_event_queue.top(); return ai->m_pExoAppInternal->m_pWorldTimer->CompareWorldTimes(eventNode.m_nCalendarDay, eventNode.m_nTimeOfDay, nCalendarDay, nTimeOfDay) <= 0; } -BOOL GetPendingEvent(CServerAIMaster*, uint32_t *nCalendarDay, uint32_t *nTimeOfDay, OBJECT_ID *nCallerObjectId, OBJECT_ID *nObjectId, uint32_t *nEventId, void **pEventData) +static BOOL GetPendingEvent(CServerAIMaster*, uint32_t *nCalendarDay, uint32_t *nTimeOfDay, OBJECT_ID *nCallerObjectId, OBJECT_ID *nObjectId, uint32_t *nEventId, void **pEventData) { - if (g_event_queue.empty()) return false; + if (s_event_queue.empty()) return false; - const CServerAIEventNode& eventNode = g_event_queue.top(); + const CServerAIEventNode& eventNode = s_event_queue.top(); *nCalendarDay = eventNode.m_nCalendarDay; *nTimeOfDay = eventNode.m_nTimeOfDay; @@ -64,7 +64,7 @@ BOOL GetPendingEvent(CServerAIMaster*, uint32_t *nCalendarDay, uint32_t *nTimeOf *nEventId = eventNode.m_nEventId; *pEventData = eventNode.m_pEventData; - g_event_queue.pop(); + s_event_queue.pop(); return true; } @@ -84,11 +84,11 @@ void EventMasterPriorityQueue() if (Config::Get("EVENT_MASTER_PRIORITY_QUEUE", false)) { - static Hooks::Hook _0 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster15ClearEventQueueEv, (void*)&ClearEventQueue, Hooks::Order::Early); - static Hooks::Hook _1 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster20AddEventAbsoluteTimeEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Early); - static Hooks::Hook _2 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster27AddEventAbsoluteTimeViaTailEjjjjjPv, (void*)&AddEventAbsoluteTime, Hooks::Order::Early); - static Hooks::Hook _3 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster12EventPendingEjj, (void*)&EventPending, Hooks::Order::Early); - static Hooks::Hook _4 = Hooks::HookFunction(API::Functions::_ZN15CServerAIMaster15GetPendingEventEPjS0_S0_S0_S0_PPv, (void*)&GetPendingEvent, Hooks::Order::Early); + 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); } } From 0f44802a533bb86ff4b33d9758befdbd0fee3f42 Mon Sep 17 00:00:00 2001 From: Liareth Date: Sun, 14 Aug 2022 23:32:14 +0100 Subject: [PATCH 4/4] Updated CHANGELOG.md. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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.