#define SOL_ALL_SAFETIES_ON 1 #include #include #include class memory_tracker { public: // 10 MB or something? // idk whatever inline static constexpr std::size_t arbitrary_default_limit = 1024 * 1024 * 10; memory_tracker() : memory_tracker(arbitrary_default_limit) { } memory_tracker(std::size_t maximum_memory) : used(0) , limit(maximum_memory) , n_threads(0) , n_tables(0) , n_functions(0) , n_userdata(0) , n_strings(0) { } std::size_t currently_used() const { return used; } std::size_t memory_limit() const { return limit; } static void* allocate(void* memory_tracker_ud, void* ptr, size_t object_code, size_t nsize) { memory_tracker& self = (*static_cast( memory_tracker_ud)); return self.alloc(ptr, object_code, nsize); } private: void* alloc(void* ptr, size_t original_block_size_or_code, size_t new_block_size) { std::size_t original_block_size = original_block_size_or_code; if (ptr == nullptr) { // object code! sol::type object_type = static_cast( original_block_size_or_code); switch (object_type) { case sol::type::function: ++n_functions; break; case sol::type::string: ++n_strings; break; case sol::type::userdata: ++n_userdata; break; case sol::type::table: ++n_tables; break; case sol::type::thread: ++n_threads; break; default: // not a clue, fam break; } // because it is an object code, // it tells us literally nothing about // the old block size, // so set that to 0 original_block_size = 0; } if (new_block_size == 0) { // Lua expects us to act like a "free" // when the new block size is 0 std::free(ptr); used -= original_block_size; return nullptr; } // did we hit the limit? std::size_t memory_differntial = new_block_size - original_block_size; std::size_t desired_use = used + memory_differntial; if (desired_use > limit) { // tell the Lua Virtual Machine // to toss off (by returning nullptr) return nullptr; } // alright now we have to expand this shit // guess we use C's realloc ptr = std::realloc(ptr, new_block_size); if (ptr != nullptr) { // alright, we successfully allocated some space // track it used = desired_use; } // even if we're null, let Lua crash and burn for us return ptr; } std::size_t used; std::size_t limit; std::size_t n_threads; std::size_t n_tables; std::size_t n_functions; std::size_t n_userdata; std::size_t n_strings; }; struct my_type { int a = 24; bool b = false; double d = 3.5; }; #include int main() { std::cout << "=== memory tracker ===" << std::endl; #if SOL_LUAJIT_VERSION < 20100 && (UINTPTR_MAX > 0xFFFFFFFF) std::cout << "LuaJIT in x64 mode on LuaJIT 2.0.X versions " "does not support using a custom allocator!" << std::endl; #else memory_tracker box; std::cout << "memory at start: " << box.currently_used() << " bytes / " << box.memory_limit() << " bytes" << std::endl; sol::state lua(&sol::default_at_panic, &memory_tracker::allocate, &box); lua.open_libraries(sol::lib::base); std::cout << "memory after state creation: " << box.currently_used() << " bytes / " << box.memory_limit() << " bytes" << std::endl; // trigger some allocations lua.new_usertype("my_type", "a", &my_type::a, "b", &my_type::b, "d", &my_type::d); lua["f"] = [](std::string s) { return s + " woof!"; }; std::cout << "memory after function and usertype setup: " << box.currently_used() << " bytes / " << box.memory_limit() << " bytes" << std::endl; lua.safe_script("print(f('bark'))"); lua.safe_script( "local obj = my_type.new() print(obj.a, obj.b, obj.c) " "obj.b = true print(obj.a, obj.b, obj.c)"); std::cout << "memory at end: " << box.currently_used() << " bytes / " << box.memory_limit() << " bytes" << std::endl; #endif return 0; }