Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Data/Base.rte/Activities/AIBattle.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ Human players can just watch, by leaving every team to the CPU, or take a team f

function AIBattle:StartActivity()
-- How long a team waits between its reinforcements, and how many units it can have before it stops getting more.
self.reinforcementInterval = 20000;
self.maxUnitsPerTeam = 8;
-- Setting the environment variable CCCP_AI_BATTLE_SCALE to a number multiplies both how often reinforcements come and how many units each team can have, for battles big enough to put the game under load.
local scale = tonumber(os.getenv("CCCP_AI_BATTLE_SCALE") or "") or 1;
self.reinforcementInterval = 20000 / scale;
self.maxUnitsPerTeam = math.floor(8 * scale);

self.deliveryCreationHandler = require("Activities/Utility/DeliveryCreationHandler");
self.deliveryCreationHandler:Initialize(self);
Expand Down
29 changes: 25 additions & 4 deletions Source/Managers/LuaMan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ void LuaStateWrapper::Clear() {
m_LastError.clear();
m_CurrentlyRunningScriptPath = "";
m_RunningAI = false;
m_HeapSizeAfterGarbageCollectionStep = 0;
}

void LuaStateWrapper::Initialize() {
m_State = luaL_newstate();
luabind::open(m_State);
tracy::LuaRegister(m_State);

// We do async GC, but we still keep the normal GC on so it can catch any big spikes or runaway allocs
// Lua's own garbage collector is left on until StepGarbageCollection takes over from it, so it still collects while scripts are being loaded.
//lua_gc(m_State, LUA_GCSTOP, 0);

const luaL_Reg libsToLoad[] = {
Expand Down Expand Up @@ -890,6 +891,23 @@ void LuaStateWrapper::Update() {
m_AddedRegisteredMOs.clear();
}

void LuaStateWrapper::StepGarbageCollection() {
std::lock_guard<std::recursive_mutex> lock(m_Mutex);

size_t heapSize = GetHeapSize();
if (m_HeapSizeAfterGarbageCollectionStep != 0) {
// Lua's own collector does its step multiplier's worth of collecting for each kilobyte allocated, and a step of that many kilobytes does the same, so this keeps up with what was allocated since the last step just as it would have.
// With nothing allocated it still does a single step, so that a cycle always gets finished.
// Unlike Lua's own collector, it goes straight on from one cycle into the next rather than waiting for the heap to grow first. That wait goes by an estimate of the heap that leaves out what's still to be finalized, which there's no way to get from outside Lua, and going by the whole heap instead lets it grow without end.
size_t allocatedSinceLastStep = heapSize > m_HeapSizeAfterGarbageCollectionStep ? heapSize - m_HeapSizeAfterGarbageCollectionStep : 0;
lua_gc(m_State, LUA_GCSTEP, static_cast<int>(allocatedSinceLastStep / 1024));
}

// Stop the Lua GC from triggering manually so we don't get spikes in work that the main thread may be waiting on
lua_gc(m_State, LUA_GCSTOP, 0);
m_HeapSizeAfterGarbageCollectionStep = GetHeapSize();
}

void LuaStateWrapper::ClearScriptTimings() {
m_ScriptTimings.clear();
}
Expand Down Expand Up @@ -1005,6 +1023,11 @@ std::string LuaStateWrapper::DescribeLuaStack() {
return stackDescription.str();
}

size_t LuaStateWrapper::GetHeapSize() {
// Lua only gives the heap size in whole kilobytes, plus the bytes left over from that, so they're put back together to get it in bytes.
return static_cast<size_t>(lua_gc(m_State, LUA_GCCOUNT, 0)) * 1024 + static_cast<size_t>(lua_gc(m_State, LUA_GCCOUNTB, 0));
}

LuaMan::LuaMan() {
Clear();
}
Expand Down Expand Up @@ -1317,9 +1340,7 @@ void LuaMan::StartAsyncGarbageCollection() {
m_GarbageCollectionTask.push_back(
g_ThreadMan.GetPriorityThreadPool().submit([luaState]() {
ZoneScopedN("Lua Garbage Collection");
std::lock_guard<std::recursive_mutex> lock(luaState->GetMutex());
lua_gc(luaState->GetLuaState(), LUA_GCSTEP, 100);
lua_gc(luaState->GetLuaState(), LUA_GCSTOP, 0);
luaState->StepGarbageCollection();
}));
}
}
Expand Down
10 changes: 10 additions & 0 deletions Source/Managers/LuaMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ namespace RTE {
/// Updates this Lua state.
void Update();

/// Does garbage collection on this Lua state, as much of it as Lua's own collector would have done for what was allocated since the last time this was called, and stops Lua's own collector from doing any in the meantime.
/// This way garbage collection only happens here, where it can be done alongside other work, but no more of it is done than is needed to keep up.
void StepGarbageCollection();

/// Clears m_ScriptTimings.
void ClearScriptTimings();
#pragma endregion
Expand Down Expand Up @@ -265,6 +269,10 @@ namespace RTE {
/// @return A string that describes the current state of the Lua stack.
std::string DescribeLuaStack();

/// Gets the size of this Lua state's heap.
/// @return The size of this Lua state's heap, in bytes.
size_t GetHeapSize();

/// Clears all the member variables of this LuaStateWrapper, effectively resetting the members of this abstraction level only.
void Clear();

Expand All @@ -287,6 +295,8 @@ namespace RTE {

std::unordered_map<std::string, PerformanceMan::ScriptTiming> m_ScriptTimings; //!< Internal map of script timings.

size_t m_HeapSizeAfterGarbageCollectionStep; //!< The size of this Lua state's heap, in bytes, at the end of the last StepGarbageCollection. 0 until it's first called.

// For determinism, every Lua state has it's own random number generator.
RandomGenerator m_RandomGenerator; //!< The random number generator used for this lua state.
RandomGenerator m_AIRandomGenerator; //!< The random number generator used for this lua state while it is running AI.
Expand Down
Loading