33#include < AdGameUtils.h>
44
55#include < RE/C/ConsoleLog.h>
6-
7- // the ratio at which we start warning in console log.
8- // this can be set to anything, i just set it to 80% for right now
9- #define WARNING_MIN_RATIO 0 .80f
6+ #include < RE/B/BSSpinLock.h>
7+ #include < RE/B/BSPointerHandle.h>
108
119namespace Addictol
1210{
13- // credit: Warnings::ReferenceHandleCap in Daytripper 4 by mith077 under MIT
14- // https://www.nexusmods.com/fallout4/mods/91141?tab=files
15-
16- // data
17- static inline constexpr uint32_t MAX_HANDLE_LIMIT = 1 << 21 ; // 21 bits for handle, 5 for age, 1 for active = 26 bit test
18- static const std::string WarningMessage_ReferenceHandleLimitExceeded = " Addictol::ReferenceHandleLimitWarning: "
19- " Reference Handle Limit Exceeded! This can cause CTDs. Consider trimming your load order for your next save." ;
20- static const std::string WarningMessage_ReferenceHandleLimitAlmostExceeded = " Addictol::ReferenceHandleLimitWarning: "
21- " TODO" ;
11+ // the ratio at which we start warning in console log.
12+ // this can be set to anything, i just set it to 90% for right now
13+ constexpr auto WARNING_MIN_RATIO = 0 .90f ;
2214
15+ // 21 bits for handle, 5 for age, 1 for active = 26 bit test
16+ static constexpr uint32_t MAX_HANDLE_LIMIT = 1 << 21 ;
2317 // cached values
2418 static uint32_t lastCount = 0 ;
25- static float lastRatio = 0 .0f ;
26-
27- // tested on AE, but should be fine on OG/NG since daytripper's addresses work on OG/NG
28- static const auto ReferenceHandleArray_addresses = REL::VariantID(1103816 , 2688744 ).address();
29- static const auto ReferenceHandleArray = reinterpret_cast <uint64_t *>(ReferenceHandleArray_addresses);
19+ static double lastRatio = 0 .0f ;
3020
3121 static REX::TOML::Bool<> bWarningsReferenceHandleLimit{ " Warnings" sv, " bReferenceHandleLimit" sv, true };
3222
23+ struct HandleManager :
24+ public RE::BSPointerHandleManagerInterface<RE::TESObjectREFR>
25+ {
26+ struct Entries
27+ {
28+ RE::BSPointerHandle<RE::TESObjectREFR> handle;
29+ void * addressPointerInREFR;
30+ };
31+
32+ uint32_t freeListHead;
33+ uint32_t freeListTail;
34+ RE::BSReadWriteLock handleManagerLock;
35+ Entries* handleEntries;
36+ const RE::BSPointerHandle<RE::TESObjectREFR> nullHandle{};
37+ };
38+
3339 ModuleReferenceHandleLimitWarning::ModuleReferenceHandleLimitWarning () :
3440 Module (" Reference Handle Limit Warning" , &bWarningsReferenceHandleLimit)
3541 {}
3642
37- // note: could be useful to have in AdGameUtils but compiler was being annoying
38- std::string GetMessagingInterfaceString (F4SE::MessagingInterface::Message* a_msg)
43+ static uint32_t GetReferenceHandleCount () noexcept
3944 {
40- if (!a_msg)
41- return " ERROR_NULL_MESSAGE" ;
42-
43- switch (a_msg->type ) {
44- case F4SE::MessagingInterface::kPostLoad :
45- return " kPostLoad" ;
46- case F4SE::MessagingInterface::kPostPostLoad :
47- return " kPostPostLoad" ;
48- case F4SE::MessagingInterface::kPreLoadGame :
49- return " kPreLoadGame" ;
50- case F4SE::MessagingInterface::kPostLoadGame :
51- return " kPostLoadGame" ;
52- case F4SE::MessagingInterface::kPreSaveGame :
53- return " kPreSaveGame" ;
54- case F4SE::MessagingInterface::kPostSaveGame :
55- return " kPostSaveGame" ;
56- case F4SE::MessagingInterface::kDeleteGame :
57- return " kDeleteGame" ;
58- case F4SE::MessagingInterface::kInputLoaded :
59- return " kInputLoaded" ;
60- case F4SE::MessagingInterface::kNewGame :
61- return " kNewGame" ;
62- case F4SE::MessagingInterface::kGameLoaded :
63- return " kGameLoaded" ;
64- case F4SE::MessagingInterface::kGameDataReady :
65- return " kGameDataReady" ;
66- default :
67- return " ERROR_NO_TYPE" ;
68- }
69-
70- return " ERROR_IDEK_HONESTLY" ;
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 ;
47+ return manager ? manager->freeListHead : 0 ;
7148 }
7249
73- uint32_t GetReferenceHandleCount () noexcept
74- {
75- uint32_t count = 0 ;
76- for (uint32_t i = 0 ; i < MAX_HANDLE_LIMIT; i++) {
77- if ((ReferenceHandleArray[i] >> 26 ) & 1 ) {
78- count++;
79- }
80- }
81-
82- return count;
83- }
84-
85- void CheckReferenceHandleLimit (std::string_view eventName, bool cacheResults = true ) noexcept
50+ static void CheckReferenceHandleLimit (std::string_view eventName, bool cacheResults = true ) noexcept
8651 {
8752 uint32_t count = GetReferenceHandleCount ();
88- float ratio = static_cast <float >(count) / static_cast <float >(MAX_HANDLE_LIMIT);
53+ double ratio = static_cast <double >(count) / static_cast <float >(MAX_HANDLE_LIMIT);
8954
90- if (cacheResults) {
55+ if (cacheResults)
56+ {
9157 // we can cache it so we dont have to iterate again unless we need to measure it again
9258 lastCount = count;
9359 lastRatio = ratio;
9460 }
9561
96- REX::INFO (" Reference Handle Count ({}): {}. Ratio: {:.4 }%" sv, eventName, count, ratio);
62+ REX::INFO (" Reference Handle Count ({}): {}. Ratio: {:.1f }%" sv, eventName, count, ratio * 100 . f );
9763
9864 // warn if needed
99- if (ratio >= WARNING_MIN_RATIO) {
100- REX::WARN (std::string_view (WarningMessage_ReferenceHandleLimitExceeded));
65+ if (ratio >= WARNING_MIN_RATIO)
66+ {
67+ if (ratio >= 0 .999f )
68+ {
69+ REX::CRITICAL (" OUT OF HANDLE ARRAY ENTRIES!TERMINATE GAME FORCIBLY!" sv);
70+ REX::W32::TerminateProcess (REX::W32::GetCurrentProcess (), ERANGE);
71+ }
72+ else
73+ {
74+ REX::WARN (" HANDLE ARRAY ENTRIES ALMOST EXCEEDED" sv);
10175
102- auto * consoleLog = RE::ConsoleLog::GetSingleton ();
103- if (consoleLog) {
104- consoleLog->AddString (WarningMessage_ReferenceHandleLimitExceeded. c_str () );
76+ auto * consoleLog = RE::ConsoleLog::GetSingleton ();
77+ if (consoleLog)
78+ consoleLog->AddString (" Addictol::ReferenceHandleLimitWarning: HANDLE ARRAY ENTRIES ALMOST EXCEEDED " );
10579 }
10680 }
10781 }
@@ -118,6 +92,8 @@ namespace Addictol
11892
11993 std::string eventName = GetMessagingInterfaceString (a_msg);
12094 CheckReferenceHandleLimit (eventName);
95+
96+ // FIXME: Add check to CreateHandle function
12197
12298 return true ;
12399 }
0 commit comments