Files
ClassicAPI/src/frame/ExtraScripts.cpp
T
Brues 6c5269e3e6 feat(profiling): add per-addon memory and CPU usage
Backport UpdateAddOnMemoryUsage / GetAddOnMemoryUsage and the script
profiling family (scriptProfile, GetFunctionCPUUsage, UpdateAddOnCPUUsage,
GetAddOnCPUUsage, GetFrameCPUUsage, GetEventCPUUsage, GetScriptCPUUsage,
ResetCPUUsage), mirroring 3.3.5's engine mechanism.

- Gc::Hooks owns the GC lifecycle hooks (luaC_link, newlstr,
  luaS_newudata, freeobj); Gc::Tag stamps each new object with the
  engine's context word (3.3.5's lua_tainted rule) and sums sizes with
  the engine's own free sizes.
- lua_gctag pins: the addon load window (two call patches in
  FUN_ADDON_LOAD_ONE) and template application (engine single-name
  sites + Xml::Inherits lists).
- Profile::Cpu keeps 3.3.5's per-closure Profile records through Lua's
  own call/return hook, installed only when scriptProfile is 1; tail
  calls, error unwinds and coroutines handled.
- ScriptArgs observers time every handler run; signals counted at each
  dispatcher (call-site redirect inside FUN_FIRE_EVENT, not a MinHook).
- Frame::ScriptSlots enumerates a frame's script slots without creating
  them, for GetFrameCPUUsage.
- Add the unordered_dense submodule (v5.3.2); share ResolveRecord via
  addons/Registry; fix the AddOnEntry inline-name comments.
2026-10-23 15:31:39 -05:00

79 lines
2.7 KiB
C++

// This file is part of ClassicAPI.
//
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
#include "frame/ExtraScripts.h"
#include "Game.h"
#include "Offsets.h"
#include "baselib/Ascii.h"
#include "frame/ScriptSlots.h"
namespace Frame::ExtraScripts {
namespace {
// Zero-initialized before any Script's dynamic initializer runs.
Script *g_head = nullptr;
// The engine's __thiscall(frame, name) -> slot, modelled as __fastcall with a
// dummy edx so `name` lands on the first stack slot.
using Resolver_t = int(__fastcall *)(void *frame, void *edx, const char *name);
Resolver_t g_origResolver = nullptr;
int __fastcall Resolver_h(void *frame, void *edx, const char *name) {
const int slot = g_origResolver(frame, edx, name);
if (slot != 0) // a script the engine knows - leave it
return slot;
for (const Script *s = g_head; s != nullptr; s = s->next) {
if (!Ascii::EqualCI(name, s->nameLower))
continue;
if (!Game::Lua::IsA(frame, s->typeIdVar)) // another class may have the name
continue;
Script::Cell &cell = s->cells[frame];
return reinterpret_cast<int>(cell.v);
}
return 0;
}
const Game::HookAutoRegister _hook{Offsets::FUN_FRAME_SCRIPT_RESOLVER, reinterpret_cast<void *>(&Resolver_h),
reinterpret_cast<void **>(&g_origResolver)};
void PrepareForReload() {
for (Script *s = g_head; s != nullptr; s = s->next)
s->cells.clear();
}
const Game::ReloadAutoRegister _reload{&PrepareForReload};
void ForEachSlot(const void *frame, Frame::ScriptSlots::Visit visit, void *ctx) {
for (const Script *s = g_head; s != nullptr; s = s->next)
if (const uint32_t *slot = s->Slot(frame))
visit(slot, ctx);
}
const Frame::ScriptSlots::AutoRegister _slots{&ForEachSlot};
} // namespace
Script::Script(const char *nameLower, uintptr_t typeIdVar)
: nameLower(nameLower), typeIdVar(typeIdVar), next(g_head) {
g_head = this;
}
uint32_t *Script::Slot(const void *frame) const {
const auto it = cells.find(frame);
return it != cells.end() ? it->second.v : nullptr;
}
} // namespace Frame::ExtraScripts