@@ -12,15 +12,9 @@ namespace Addictol
1212 // this can be set to anything, i just set it to 90% for right now
1313 constexpr auto WARNING_MIN_RATIO = 0 .90f ;
1414
15- // 21 bits for handle, 5 for age, 1 for active = 26 bit test
16- static constexpr uint32_t MAX_HANDLE_LIMIT = 1 << 21 ;
17- // cached values
18- static uint32_t lastCount = 0 ;
19- static double lastRatio = 0 .0f ;
20-
2115 static REX::TOML::Bool<> bWarningsReferenceHandleLimit{ " Warnings" sv, " bReferenceHandleLimit" sv, true };
2216
23- struct HandleManager :
17+ class HandleManager :
2418 public RE::BSPointerHandleManagerInterface<RE::TESObjectREFR>
2519 {
2620 struct Entries
@@ -34,52 +28,79 @@ namespace Addictol
3428 RE::BSReadWriteLock handleManagerLock;
3529 Entries* handleEntries;
3630 const RE::BSPointerHandle<RE::TESObjectREFR> nullHandle{};
37- };
31+ public:
32+ // 21 bits for handle, 5 for age, 1 for active = 26 bit test
33+ inline static constexpr uint32_t MAX_HANDLE_LIMIT = 1 << 21 ;
3834
39- ModuleReferenceHandleLimitWarning::ModuleReferenceHandleLimitWarning () :
40- Module (" Reference Handle Limit Warning" , &bWarningsReferenceHandleLimit)
41- {}
35+ static uint32_t GetCount () noexcept ;
36+ static RE::BSPointerHandle<RE::TESObjectREFR> HkCreateHandle (RE::TESObjectREFR* a_ptr) noexcept ;
37+ static void DebugInfo (const std::string_view& a_eventName) noexcept ;
38+
39+ inline static std::atomic_bool exceededShow{ false };
40+ inline static HandleManager* singleton{ nullptr };
41+ inline static decltype (HkCreateHandle)* CreateHandle_orig{ nullptr };
42+ };
4243
43- static uint32_t GetReferenceHandleCount () noexcept
44+ uint32_t HandleManager::GetCount () noexcept
4445 {
45- auto manager = reinterpret_cast < HandleManager*>( REL::VariantID ( 665313 , 2688741 , 4796005 ). address ()) ;
46- if (!manager || (manager->freeListHead == manager->freeListTail == MAX_HANDLE_LIMIT - 1 )) return 0 ;
46+ auto manager = HandleManager::singleton ;
47+ if (!manager || (manager->freeListHead == manager->freeListTail == ( MAX_HANDLE_LIMIT - 1 ) )) return 0 ;
4748 return manager ? manager->freeListHead : 0 ;
4849 }
4950
50- static void CheckReferenceHandleLimit (std::string_view eventName, bool cacheResults = true ) noexcept
51+ RE::BSPointerHandle<RE::TESObjectREFR> HandleManager::HkCreateHandle (RE::TESObjectREFR* a_ptr ) noexcept
5152 {
52- uint32_t count = GetReferenceHandleCount ();
53+ uint32_t count = HandleManager::GetCount ();
5354 double ratio = static_cast <double >(count) / static_cast <float >(MAX_HANDLE_LIMIT);
54-
55- if (cacheResults)
56- {
57- // we can cache it so we dont have to iterate again unless we need to measure it again
58- lastCount = count;
59- lastRatio = ratio;
60- }
61-
62- REX::INFO (" Reference Handle Count ({}): {}. Ratio: {:.1f}%" sv, eventName, count, ratio * 100 .f );
63-
64- // warn if needed
6555 if (ratio >= WARNING_MIN_RATIO)
6656 {
6757 if (ratio >= 0 .999f )
6858 {
69- REX::CRITICAL (" OUT OF HANDLE ARRAY ENTRIES!TERMINATE GAME FORCIBLY!" sv);
59+ REX::CRITICAL (" OUT OF HANDLE ARRAY ENTRIES! TERMINATE GAME FORCIBLY!" sv);
7060 REX::W32::TerminateProcess (REX::W32::GetCurrentProcess (), ERANGE);
7161 }
72- else
62+ else if (!HandleManager::exceededShow)
7363 {
74- REX::WARN (" HANDLE ARRAY ENTRIES ALMOST EXCEEDED" sv);
64+ HandleManager::exceededShow = true ;
65+ ratio *= 100 .f ;
66+
67+ REX::WARN (" HANDLE ARRAY ENTRIES ALMOST EXCEEDED {:.1f}%" sv, ratio);
68+
69+ char szBuf[REX::W32::MAX_PATH]{};
70+ sprintf_s (szBuf, " Addictol::ReferenceHandleLimitWarning: HANDLE ARRAY ENTRIES ALMOST EXCEEDED (%.1f%%)" , ratio);
7571
7672 auto * consoleLog = RE::ConsoleLog::GetSingleton ();
7773 if (consoleLog)
78- consoleLog->AddString (" Addictol::ReferenceHandleLimitWarning: HANDLE ARRAY ENTRIES ALMOST EXCEEDED" );
74+ consoleLog->AddString (szBuf);
75+
76+ std::thread th ([]{
77+ // 5 minutes in milliseconds
78+ std::chrono::milliseconds delay (5 * 60 * 1000 );
79+ std::this_thread::sleep_for (delay);
80+ // reset
81+ HandleManager::exceededShow = false ;
82+ });
83+ th.detach ();
7984 }
8085 }
86+
87+ assert (CreateHandle_orig);
88+ return CreateHandle_orig (a_ptr);
89+ }
90+
91+ void HandleManager::DebugInfo (const std::string_view& a_eventName) noexcept
92+ {
93+ uint32_t count = HandleManager::GetCount ();
94+ double ratio = static_cast <double >(count) / static_cast <float >(MAX_HANDLE_LIMIT);
95+
96+ REX::INFO (" Reference Handle Count ({}): {}. Ratio: {:.1f}%" sv, a_eventName, count, ratio * 100 .f );
8197 }
8298
99+ ModuleReferenceHandleLimitWarning::ModuleReferenceHandleLimitWarning () :
100+ Module (" Reference Handle Limit Warning" , &bWarningsReferenceHandleLimit,
101+ { F4SE::MessagingInterface::kPostLoadGame })
102+ {}
103+
83104 bool ModuleReferenceHandleLimitWarning::DoQuery () const noexcept
84105 {
85106 return true ;
@@ -88,19 +109,35 @@ namespace Addictol
88109 bool ModuleReferenceHandleLimitWarning::DoInstall ([[maybe_unused]] F4SE::MessagingInterface::Message* a_msg) noexcept
89110 {
90111 if (!a_msg)
112+ {
113+ HandleManager::singleton = reinterpret_cast <HandleManager*>(REL::VariantID (665313 , 2688741 , 4796005 ).address ());
114+
115+ *(uintptr_t *)&HandleManager::CreateHandle_orig = RELEX::DetourJump (
116+ RE::ID::BSPointerHandle::BSPointerHandleManagerInterface::CreateHandle.address (),
117+ (uintptr_t )&HandleManager::HkCreateHandle);
118+
91119 return true ;
92-
93- std::string eventName = GetMessagingInterfaceString (a_msg);
94- CheckReferenceHandleLimit (eventName);
120+ }
121+ else if (a_msg->type == F4SE::MessagingInterface::kGameLoaded )
122+ {
123+ HandleManager::DebugInfo (GetMessagingInterfaceString (a_msg));
95124
96- // FIXME: Add check to CreateHandle function
125+ return true ;
126+ }
97127
98- return true ;
128+ return false ;
99129 }
100130
101131 bool ModuleReferenceHandleLimitWarning::DoListener ([[maybe_unused]] F4SE::MessagingInterface::Message* a_msg) noexcept
102132 {
103- return true ;
133+ if (a_msg && (a_msg->type == F4SE::MessagingInterface::kPostLoadGame ))
134+ {
135+ HandleManager::DebugInfo (GetMessagingInterfaceString (a_msg));
136+
137+ return true ;
138+ }
139+
140+ return false ;
104141 }
105142
106143 bool ModuleReferenceHandleLimitWarning::DoPapyrusListener ([[maybe_unused]] RE::BSScript::IVirtualMachine* a_vm) noexcept
0 commit comments