mirror of
https://github.com/iTracerFacer/Moose_CTLD_Pure.git
synced 2025-12-03 04:11:57 +00:00
✅ Fixed _trackOneShotTimer() to store expiration timestamps instead of boolean flags
✅ Added _untrackTimer() function to immediately remove completed timers ✅ Enhanced periodic GC with timestamp-based cleanup (removes stale timers after 60s) ✅ Added collectgarbage('step') calls to all cleanup functions ✅ Added incremental GC to high-frequency schedulers (hover, ground load) ✅ Added GC to main schedulers (crate cleanup, troop cleanup, state maintenance) ✅ Emergency cleanup if pendingTimers exceeds 300 entries
This commit is contained in:
parent
7b902ff5d8
commit
02f2e2b9f1
@ -3270,27 +3270,25 @@ end
|
|||||||
-- Track one-shot timers for cleanup
|
-- Track one-shot timers for cleanup
|
||||||
local function _trackOneShotTimer(id)
|
local function _trackOneShotTimer(id)
|
||||||
if id and CTLD._pendingTimers then
|
if id and CTLD._pendingTimers then
|
||||||
CTLD._pendingTimers[id] = true
|
CTLD._pendingTimers[id] = timer.getTime() + 300 -- Store timestamp for cleanup
|
||||||
end
|
end
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Remove timer from tracking immediately when it fires
|
||||||
|
local function _untrackTimer(id)
|
||||||
|
if id and CTLD._pendingTimers then
|
||||||
|
CTLD._pendingTimers[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Clean up one-shot timers when they execute
|
-- Clean up one-shot timers when they execute
|
||||||
local function _wrapOneShotCallback(callback)
|
local function _wrapOneShotCallback(callback, timerId)
|
||||||
return function(...)
|
return function(...)
|
||||||
local result = callback(...)
|
local result = callback(...)
|
||||||
-- If callback returns a time, it's recurring - don't remove
|
-- If callback returns nil or not a number, it's one-shot - remove from tracking
|
||||||
if not result or type(result) ~= 'number' then
|
if not result or type(result) ~= 'number' then
|
||||||
local trackedId = nil
|
_untrackTimer(timerId)
|
||||||
for id, _ in pairs(CTLD._pendingTimers or {}) do
|
|
||||||
if id == callback then
|
|
||||||
trackedId = id
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if trackedId and CTLD._pendingTimers then
|
|
||||||
CTLD._pendingTimers[trackedId] = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
@ -3653,19 +3651,32 @@ function CTLD:_ensurePeriodicGC()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clean up stale pending timer references
|
-- Clean up stale pending timer references based on timestamp
|
||||||
if CTLD and CTLD._pendingTimers then
|
if CTLD and CTLD._pendingTimers then
|
||||||
|
local now = timer.getTime()
|
||||||
local timerCount = 0
|
local timerCount = 0
|
||||||
for _ in pairs(CTLD._pendingTimers) do
|
local cleaned = 0
|
||||||
|
for timerId, expireTime in pairs(CTLD._pendingTimers) do
|
||||||
timerCount = timerCount + 1
|
timerCount = timerCount + 1
|
||||||
|
-- Remove timers that should have fired more than 60 seconds ago
|
||||||
|
if type(expireTime) == 'number' and now > expireTime + 60 then
|
||||||
|
CTLD._pendingTimers[timerId] = nil
|
||||||
|
cleaned = cleaned + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if timerCount > 200 then
|
if cleaned > 0 then
|
||||||
-- If we have too many pending timers, clear old ones (they may have fired already)
|
env.info(string.format('[CTLD][GC] Cleaned %d stale timer references (total: %d)', cleaned, timerCount))
|
||||||
env.info(string.format('[CTLD][GC] Clearing %d stale timer references', timerCount))
|
end
|
||||||
|
-- Emergency cleanup if we still have too many
|
||||||
|
if timerCount > 300 then
|
||||||
|
env.info(string.format('[CTLD][GC] Emergency clearing %d timer references', timerCount))
|
||||||
CTLD._pendingTimers = {}
|
CTLD._pendingTimers = {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Force garbage collection
|
||||||
|
collectgarbage('step', 1000)
|
||||||
|
|
||||||
return timer.getTime() + 300 -- every 5 minutes
|
return timer.getTime() + 300 -- every 5 minutes
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -3688,9 +3699,16 @@ function CTLD:_startHoverScheduler()
|
|||||||
if not coachCfg.enabled or self.HoverSched then return end
|
if not coachCfg.enabled or self.HoverSched then return end
|
||||||
local interval = coachCfg.interval or 0.75
|
local interval = coachCfg.interval or 0.75
|
||||||
local startDelay = coachCfg.startDelay or interval
|
local startDelay = coachCfg.startDelay or interval
|
||||||
|
local gcCounter = 0
|
||||||
self.HoverSched = SCHEDULER:New(nil, function()
|
self.HoverSched = SCHEDULER:New(nil, function()
|
||||||
local ok, err = pcall(function() self:ScanHoverPickup() end)
|
local ok, err = pcall(function() self:ScanHoverPickup() end)
|
||||||
if not ok then _logError('HoverSched ScanHoverPickup error: '..tostring(err)) end
|
if not ok then _logError('HoverSched ScanHoverPickup error: '..tostring(err)) end
|
||||||
|
-- Incremental GC every 50 iterations (~37 seconds at 0.75s interval)
|
||||||
|
gcCounter = gcCounter + 1
|
||||||
|
if gcCounter >= 50 then
|
||||||
|
collectgarbage('step', 100)
|
||||||
|
gcCounter = 0
|
||||||
|
end
|
||||||
end, {}, startDelay, interval)
|
end, {}, startDelay, interval)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -3698,9 +3716,16 @@ function CTLD:_startGroundLoadScheduler()
|
|||||||
local groundCfg = CTLD.GroundAutoLoadConfig or {}
|
local groundCfg = CTLD.GroundAutoLoadConfig or {}
|
||||||
if not groundCfg.Enabled or self.GroundLoadSched then return end
|
if not groundCfg.Enabled or self.GroundLoadSched then return end
|
||||||
local interval = 1.0 -- check every second for ground load conditions
|
local interval = 1.0 -- check every second for ground load conditions
|
||||||
|
local gcCounter = 0
|
||||||
self.GroundLoadSched = SCHEDULER:New(nil, function()
|
self.GroundLoadSched = SCHEDULER:New(nil, function()
|
||||||
local ok, err = pcall(function() self:ScanGroundAutoLoad() end)
|
local ok, err = pcall(function() self:ScanGroundAutoLoad() end)
|
||||||
if not ok then _logError('GroundLoadSched ScanGroundAutoLoad error: '..tostring(err)) end
|
if not ok then _logError('GroundLoadSched ScanGroundAutoLoad error: '..tostring(err)) end
|
||||||
|
-- Incremental GC every 60 iterations (60 seconds)
|
||||||
|
gcCounter = gcCounter + 1
|
||||||
|
if gcCounter >= 60 then
|
||||||
|
collectgarbage('step', 100)
|
||||||
|
gcCounter = 0
|
||||||
|
end
|
||||||
end, {}, interval, interval)
|
end, {}, interval, interval)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -4990,18 +5015,21 @@ function CTLD:New(cfg)
|
|||||||
o.Sched = SCHEDULER:New(nil, function()
|
o.Sched = SCHEDULER:New(nil, function()
|
||||||
local ok, err = pcall(function() o:CleanupCrates() end)
|
local ok, err = pcall(function() o:CleanupCrates() end)
|
||||||
if not ok then _logError('CleanupCrates scheduler error: '..tostring(err)) end
|
if not ok then _logError('CleanupCrates scheduler error: '..tostring(err)) end
|
||||||
|
collectgarbage('step', 200) -- GC after cleanup
|
||||||
end, {}, 60, 60)
|
end, {}, 60, 60)
|
||||||
|
|
||||||
-- Periodic cleanup for deployed troops (remove dead/missing groups)
|
-- Periodic cleanup for deployed troops (remove dead/missing groups)
|
||||||
o.TroopCleanupSched = SCHEDULER:New(nil, function()
|
o.TroopCleanupSched = SCHEDULER:New(nil, function()
|
||||||
local ok, err = pcall(function() o:CleanupDeployedTroops() end)
|
local ok, err = pcall(function() o:CleanupDeployedTroops() end)
|
||||||
if not ok then _logError('CleanupDeployedTroops scheduler error: '..tostring(err)) end
|
if not ok then _logError('CleanupDeployedTroops scheduler error: '..tostring(err)) end
|
||||||
|
collectgarbage('step', 200) -- GC after cleanup
|
||||||
end, {}, 30, 30)
|
end, {}, 30, 30)
|
||||||
|
|
||||||
-- Periodic comprehensive state maintenance (prune orphaned entries)
|
-- Periodic comprehensive state maintenance (prune orphaned entries)
|
||||||
o.StateMaintSched = SCHEDULER:New(nil, function()
|
o.StateMaintSched = SCHEDULER:New(nil, function()
|
||||||
local ok, err = pcall(function() o:PruneOrphanedState() end)
|
local ok, err = pcall(function() o:PruneOrphanedState() end)
|
||||||
if not ok then _logError('PruneOrphanedState scheduler error: '..tostring(err)) end
|
if not ok then _logError('PruneOrphanedState scheduler error: '..tostring(err)) end
|
||||||
|
collectgarbage('step', 300) -- GC after state pruning
|
||||||
end, {}, 120, 120) -- Run every 2 minutes
|
end, {}, 120, 120) -- Run every 2 minutes
|
||||||
|
|
||||||
-- Optional: auto-build FOBs inside FOB zones when crates present
|
-- Optional: auto-build FOBs inside FOB zones when crates present
|
||||||
@ -8460,6 +8488,7 @@ end
|
|||||||
function CTLD:CleanupCrates()
|
function CTLD:CleanupCrates()
|
||||||
local now = timer.getTime()
|
local now = timer.getTime()
|
||||||
local life = self.Config.CrateLifetime
|
local life = self.Config.CrateLifetime
|
||||||
|
local cleaned = 0
|
||||||
for name,meta in pairs(CTLD._crates) do
|
for name,meta in pairs(CTLD._crates) do
|
||||||
if now - (meta.spawnTime or now) > life then
|
if now - (meta.spawnTime or now) > life then
|
||||||
local obj = StaticObject.getByName(name)
|
local obj = StaticObject.getByName(name)
|
||||||
@ -8467,6 +8496,7 @@ function CTLD:CleanupCrates()
|
|||||||
_cleanupCrateSmoke(name) -- Clean up smoke refresh schedule
|
_cleanupCrateSmoke(name) -- Clean up smoke refresh schedule
|
||||||
_removeFromSpatialGrid(name, meta.point, 'crate') -- Remove from spatial index
|
_removeFromSpatialGrid(name, meta.point, 'crate') -- Remove from spatial index
|
||||||
CTLD._crates[name] = nil
|
CTLD._crates[name] = nil
|
||||||
|
cleaned = cleaned + 1
|
||||||
_logDebug('Cleaned up crate '..name)
|
_logDebug('Cleaned up crate '..name)
|
||||||
-- Notify requester group if still around; else coalition
|
-- Notify requester group if still around; else coalition
|
||||||
local gname = meta.requester
|
local gname = meta.requester
|
||||||
@ -8478,10 +8508,15 @@ function CTLD:CleanupCrates()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Trigger garbage collection after cleanup if we removed items
|
||||||
|
if cleaned > 5 then
|
||||||
|
collectgarbage('step', 500)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function CTLD:CleanupDeployedTroops()
|
function CTLD:CleanupDeployedTroops()
|
||||||
-- Remove any deployed troop groups that are dead or no longer exist
|
-- Remove any deployed troop groups that are dead or no longer exist
|
||||||
|
local cleaned = 0
|
||||||
for troopGroupName, troopMeta in pairs(CTLD._deployedTroops) do
|
for troopGroupName, troopMeta in pairs(CTLD._deployedTroops) do
|
||||||
if troopMeta.side == self.Side then
|
if troopMeta.side == self.Side then
|
||||||
local troopGroup = GROUP:FindByName(troopGroupName)
|
local troopGroup = GROUP:FindByName(troopGroupName)
|
||||||
@ -8491,10 +8526,15 @@ function CTLD:CleanupDeployedTroops()
|
|||||||
_removeFromSpatialGrid(troopGroupName, troopMeta.point, 'troops')
|
_removeFromSpatialGrid(troopGroupName, troopMeta.point, 'troops')
|
||||||
end
|
end
|
||||||
CTLD._deployedTroops[troopGroupName] = nil
|
CTLD._deployedTroops[troopGroupName] = nil
|
||||||
|
cleaned = cleaned + 1
|
||||||
_logDebug('Cleaned up deployed troop group: '..troopGroupName)
|
_logDebug('Cleaned up deployed troop group: '..troopGroupName)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Trigger garbage collection after cleanup if we removed items
|
||||||
|
if cleaned > 3 then
|
||||||
|
collectgarbage('step', 300)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Comprehensive state pruning to prevent memory leaks
|
-- Comprehensive state pruning to prevent memory leaks
|
||||||
@ -8584,6 +8624,10 @@ function CTLD:PruneOrphanedState()
|
|||||||
|
|
||||||
if pruned > 0 then
|
if pruned > 0 then
|
||||||
_logVerbose(string.format('[StateMaint] Pruned %d orphaned state entries', pruned))
|
_logVerbose(string.format('[StateMaint] Pruned %d orphaned state entries', pruned))
|
||||||
|
-- Trigger garbage collection after significant pruning
|
||||||
|
if pruned > 10 then
|
||||||
|
collectgarbage('step', 500)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
--#endregion Crates
|
--#endregion Crates
|
||||||
|
|||||||
@ -337,8 +337,17 @@ function FAC:New(ctld, cfg)
|
|||||||
o:_wireShots()
|
o:_wireShots()
|
||||||
|
|
||||||
-- Schedulers for menus/status/lase loop/AI spotters
|
-- Schedulers for menus/status/lase loop/AI spotters
|
||||||
|
local gcCounter = 0
|
||||||
o._schedMenus = SCHEDULER:New(nil, function() o:_ensureMenus() end, {}, 5, 10)
|
o._schedMenus = SCHEDULER:New(nil, function() o:_ensureMenus() end, {}, 5, 10)
|
||||||
o._schedStatus = SCHEDULER:New(nil, function() o:_checkFacStatus() end, {}, 5, 1.0)
|
o._schedStatus = SCHEDULER:New(nil, function()
|
||||||
|
o:_checkFacStatus()
|
||||||
|
-- Incremental GC every 60 iterations (60 seconds at 1s interval)
|
||||||
|
gcCounter = gcCounter + 1
|
||||||
|
if gcCounter >= 60 then
|
||||||
|
collectgarbage('step', 100)
|
||||||
|
gcCounter = 0
|
||||||
|
end
|
||||||
|
end, {}, 5, 1.0)
|
||||||
o._schedAI = SCHEDULER:New(nil, function() o:_artyAICall() end, {}, 10, 30)
|
o._schedAI = SCHEDULER:New(nil, function() o:_artyAICall() end, {}, 10, 30)
|
||||||
|
|
||||||
-- Create placeholder menu at mission start to reserve F10 position if requested
|
-- Create placeholder menu at mission start to reserve F10 position if requested
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user