diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.3.8.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.3.8.miz new file mode 100644 index 0000000..f58eb94 Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.3.8.miz differ diff --git a/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.0.miz b/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.0.miz index 36095d1..8e4d14f 100644 Binary files a/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.0.miz and b/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.0.miz differ diff --git a/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.1.miz b/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.1.miz new file mode 100644 index 0000000..712f375 Binary files /dev/null and b/DCS_Normandy/Operation Thunderbolt/F99th-WWII-Operation Thunderbolt-1.0.1.miz differ diff --git a/DCS_Normandy/Operation Thunderbolt/Moose_BomberEscort.lua b/DCS_Normandy/Operation Thunderbolt/Moose_BomberEscort.lua index f6e9d9c..5584fb2 100644 --- a/DCS_Normandy/Operation Thunderbolt/Moose_BomberEscort.lua +++ b/DCS_Normandy/Operation Thunderbolt/Moose_BomberEscort.lua @@ -6,7 +6,20 @@ -- @module BOMBER_ESCORT -- @author F99th-TracerFacer -- @copyright 2025 + +--Naming Convention - Make sure these groups exist in the mission editor and are spelled EXACTLY as shown. -- +--B-17G -> Template name: BOMBER_B17G +--B-24J -> Template name: BOMBER_B24J +--B-52H -> Template name: BOMBER_B52H +--B-1B -> Template name: BOMBER_B1B +--Tu-95MS -> Template name: BOMBER_TU95 +--Tu-142 -> Template name: BOMBER_TU142 +--Tu-22M3 -> Template name: BOMBER_TU22 +--Tu-160 -> Template name: BOMBER_TU160 +-- +---@diagnostic disable: undefined-global, lowercase-global +-- MOOSE framework globals are defined at runtime by DCS World -- Global spawn counter to ensure unique MOOSE spawn indices if not _BOMBER_GLOBAL_SPAWN_COUNTER then @@ -23,17 +36,6 @@ if not _ACTIVE_MISSION_IDS then _ACTIVE_MISSION_IDS = {} end ---Naming Convention: --- ---B-17G -> Template name: BOMBER_B17G ---B-24J -> Template name: BOMBER_B24J ---B-52H -> Template name: BOMBER_B52H ---B-1B -> Template name: BOMBER_B1B ---Tu-95MS -> Template name: BOMBER_TU95 ---Tu-142 -> Template name: BOMBER_TU142 ---Tu-22M3 -> Template name: BOMBER_TU22M3 ---Tu-160 -> Template name: BOMBER_TU160 - --- -- LOGGING SYSTEM -- Central logging function with configurable log levels @@ -132,8 +134,8 @@ BOMBER_ESCORT_CONFIG = { -- Escort Requirements RequireEscort = true, -- Bombers require player escort to proceed with mission (default: true, set false to allow solo bomber missions) - EscortAirborneJoinGrace = 120, -- Seconds of grace after liftoff before escort warnings begin (default: 90) - EscortFormUpAnnouncementInterval = 60, -- Seconds between "need escort" calls during form-up (default: 60) + EscortAirborneJoinGrace = 300, -- Seconds of grace after liftoff before escort warnings begin (default: 90) + EscortFormUpAnnouncementInterval = 120, -- Seconds between "need escort" calls during form-up (default: 60) EscortFormUpMaxAnnouncements = 5, -- Number of calls before aborting form-up (default: 5) EscortLossAnnouncementInterval = 60, -- Seconds between in-flight escort loss warnings (default: 60) EscortLossMaxAnnouncements = 5, -- Number of warnings before aborting due to no escort (default: 5) @@ -150,8 +152,9 @@ BOMBER_ESCORT_CONFIG = { EscortFormationComplimentInterval = 180, -- Seconds between formation flying compliments (default: 180s = 3 minutes) -- Threat Detection - SAMThreatDistance = 100000, -- Meters - SAM detection range (default: 100km - extended for early avoidance) - FighterThreatDistance = 100000, -- Meters - Fighter detection range (default: 100km - extended for escort positioning time) + -- OPTIMIZATION: Reduced from 100km to 80km to lower pathfinding memory usage + SAMThreatDistance = 80000, -- Meters - SAM detection range (default: 80km - optimized for memory) + FighterThreatDistance = 80000, -- Meters - Fighter detection range (default: 80km - optimized for memory) ThreatCheckInterval = 10, -- Seconds between threat scans (default: 10) -- SAM Warning System @@ -163,7 +166,7 @@ BOMBER_ESCORT_CONFIG = { EnableSAMAvoidance = true, -- Enable SAM threat detection and mission abort (default: true) SAMAvoidanceBuffer = 25000, -- Meters - Buffer added to SAM range for threat assessment (default: 25km) SAMCorridorBuffer = 10000, -- Meters - Smaller buffer for corridor finding (pre-planning can be more aggressive, default: 10km) - SAMRouteLookAhead = 150000, -- Meters - Check route this far ahead for SAMs (default: 150km) + SAMRouteLookAhead = 120000, -- Meters - Check route this far ahead for SAMs (OPTIMIZED: reduced from 150km to 120km for memory) SAMAvoidOnlyIfCanEngage = true, -- Only abort for SAMs that can engage at current altitude (default: true) SAMRerouteCheckInterval = 10, -- Seconds between route threat checks during flight (default: 10s) @@ -198,6 +201,9 @@ BOMBER_ESCORT_CONFIG = { -- Debug/Instrumentation EnableRouteDebugSnapshots = true, -- Dump controller route tables after each route apply (set false to disable heavy TRACE logs) RouteSnapshotDelaySeconds = 0.75, -- Delay before sampling controller route (allows DCS AI to register the new plan) + + -- Memory Management + GarbageCollectionInterval = 600, -- Seconds between forced Lua garbage collection cycles (default: 600 = 10 minutes) } --- @@ -944,10 +950,14 @@ function BOMBER_MARKER:_ExecuteMultiMissions(missions, coalitionSide) -- Always show what was parsed if hasSpawn then feedbackMsg = feedbackMsg .. string.format("[OK] SPAWN: %s\n", mission.spawn.markerText) - feedbackMsg = feedbackMsg .. string.format(" Type: %s, Size: %s, Alt: %s, Speed: %s\n", - params.type or "[MISSING]", tostring(params.size or "[DEFAULT]"), tostring(params.altitude or "[DEFAULT]"), tostring(params.speed or "[DEFAULT]")) - if not params.type then - feedbackMsg = feedbackMsg .. " [X] Bomber type missing or malformed!\n" + if params then + feedbackMsg = feedbackMsg .. string.format(" Type: %s, Size: %s, Alt: %s, Speed: %s\n", + params.type or "[MISSING]", tostring(params.size or "[DEFAULT]"), tostring(params.altitude or "[DEFAULT]"), tostring(params.speed or "[DEFAULT]")) + if not params.type then + feedbackMsg = feedbackMsg .. " [X] Bomber type missing or malformed!\n" + end + else + feedbackMsg = feedbackMsg .. " [X] Failed to parse spawn marker parameters!\n" end else feedbackMsg = feedbackMsg .. "[X] SPAWN: NONE (required)\n" @@ -1222,10 +1232,10 @@ end function BOMBER_MARKER:_GetNextAvailableMissionNumber() local maxNum = 0 for missionId in pairs(_ACTIVE_MISSION_IDS) do - local num = string.match(missionId, "BOMBER(%d+)") - if num then - num = tonumber(num) - if num > maxNum then + local numStr = string.match(missionId, "BOMBER(%d+)") + if numStr then + local num = tonumber(numStr) + if num and num > maxNum then maxNum = num end end @@ -1356,6 +1366,10 @@ function BOMBER_ESCORT_MONITOR:Stop() self.EscortUnits = {} end + -- Force two-pass garbage collection for thorough cleanup + collectgarbage("collect") + collectgarbage("collect") + return self end @@ -2022,6 +2036,9 @@ function BOMBER_THREAT_MANAGER:Stop() self.ThreatHistory = {} end + -- Force garbage collection after clearing tables + collectgarbage("collect") + BOMBER_LOGGER:Info("THREAT", "ThreatManager:Stop() completed") return self end @@ -3239,6 +3256,10 @@ function BOMBER_MISSION_MANAGER:UnregisterMission(mission) if pruneCount > 0 then BOMBER_LOGGER:Debug("MISSION", "Pruned %d unused SPAWN objects from global cache", pruneCount) end + + -- Force two-pass garbage collection after cleanup + collectgarbage("collect") + collectgarbage("collect") end end @@ -3444,8 +3465,8 @@ function BOMBER_MISSION:_BuildRoute() end -- Waypoint 1: Start (takeoff) - local cruiseAlt = self.CruiseAlt or profile.CruiseAlt - local cruiseSpeed = self.CruiseSpeed or profile.CruiseSpeed + local cruiseAlt = self.CruiseAlt or (profile and profile.CruiseAlt) or BOMBER_ESCORT_CONFIG.DefaultAltitude + local cruiseSpeed = self.CruiseSpeed or (profile and profile.CruiseSpeed) or BOMBER_ESCORT_CONFIG.DefaultSpeed local cruiseAltMeters = cruiseAlt * 0.3048 -- Convert feet to meters local cruiseSpeedMPS = cruiseSpeed * 0.514444 -- Convert knots to m/s @@ -4384,9 +4405,10 @@ function BOMBER_MISSION:_PlayerRequestSpeedUp() local profile = self.Bomber.Profile local currentSpeed = self.Bomber.Group:GetVelocityKNOTS() - if currentSpeed < profile.MaxSpeed - 10 then + local maxSpeed = (profile and profile.MaxSpeed) or 500 -- Default max speed if not defined + if currentSpeed < maxSpeed - 10 then self.Bomber:_BroadcastMessage(string.format("%s: Increasing speed.", self.Callsign)) - local newSpeed = math.min(currentSpeed + 20, profile.MaxSpeed) + local newSpeed = math.min(currentSpeed + 20, maxSpeed) self.Bomber.Group:SetSpeed(newSpeed * 0.514444) -- Convert to m/s else self.Bomber:_BroadcastMessage(string.format("%s: Negative, already at max speed.", self.Callsign)) @@ -4400,10 +4422,11 @@ function BOMBER_MISSION:_PlayerRequestSlowDown() if self.Bomber and self.Bomber:IsAlive() then local profile = self.Bomber.Profile local currentSpeed = self.Bomber.Group:GetVelocityKNOTS() - - if currentSpeed > profile.MinSpeed + 10 then + local minSpeed = (profile and profile.MinSpeed) or 200 -- Default min speed if not defined + if currentSpeed > minSpeed + 10 then self.Bomber:_BroadcastMessage(string.format("%s: Reducing speed.", self.Callsign)) - local newSpeed = math.max(currentSpeed - 20, profile.MinSpeed) + local newSpeed = math.max(currentSpeed - 20, minSpeed) + self.Bomber.Group:SetSpeed(newSpeed * 0.514444) self.Bomber.Group:SetSpeed(newSpeed * 0.514444) else self.Bomber:_BroadcastMessage(string.format("%s: Negative, already at minimum speed.", self.Callsign)) @@ -6646,7 +6669,7 @@ function BOMBER:_StartRTBMonitor() self.LandingMonitor = nil end - self:Destroy(1) + self:Destroy() return end else @@ -8386,7 +8409,7 @@ function BOMBER:_UpdateSAMStatusSummary() end elseif threatCount > 1 and primaryThreat then local closestNm = math.floor(closestDist / 1852) - local closestBearing = math.floor(closest.Bearing) + local closestBearing = closest and closest.Bearing and math.floor(closest.Bearing) or 0 local primaryType = primaryThreat.SAMType or "Unknown" local canEngage = primaryThreat.CanEngage @@ -9960,6 +9983,16 @@ function BOMBER_ESCORT_INIT(options) BOMBER_LOGGER:Info("INIT", "==============================================") BOMBER_LOGGER:Info("INIT", "For complete documentation, see MARKER_GUIDE.md") BOMBER_LOGGER:Info("INIT", "==============================================") + + -- Schedule periodic garbage collection to prevent memory buildup + if BOMBER_ESCORT_CONFIG.GarbageCollectionInterval and BOMBER_ESCORT_CONFIG.GarbageCollectionInterval > 0 then + SCHEDULER:New(nil, function() + collectgarbage("collect") + local memKB = collectgarbage("count") + BOMBER_LOGGER:Debug("MEMORY", "Garbage collection complete. Current Lua memory: %.1f MB", memKB / 1024) + end, {}, 120, BOMBER_ESCORT_CONFIG.GarbageCollectionInterval) + BOMBER_LOGGER:Info("INIT", "Memory Management: Garbage collection scheduled every %d seconds", BOMBER_ESCORT_CONFIG.GarbageCollectionInterval) + end return _BOMBER_MARKER_SYSTEM diff --git a/DCS_Normandy/Operation Thunderbolt/Moose_DualCoalitionZoneCapture.lua b/DCS_Normandy/Operation Thunderbolt/Moose_DualCoalitionZoneCapture.lua index aed6574..3a95835 100644 --- a/DCS_Normandy/Operation Thunderbolt/Moose_DualCoalitionZoneCapture.lua +++ b/DCS_Normandy/Operation Thunderbolt/Moose_DualCoalitionZoneCapture.lua @@ -1,4 +1,9 @@ -- Refactored version with configurable zone ownership +---@diagnostic disable: undefined-global, lowercase-global +-- MOOSE framework globals are defined at runtime by DCS World +-- **Author**: F99th-TracerFacer +-- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.) + -- ========================================== -- MESSAGE AND TIMING CONFIGURATION @@ -13,7 +18,8 @@ local MESSAGE_CONFIG = { STATUS_MESSAGE_DURATION = 15, -- How long general status messages stay onscreen VICTORY_MESSAGE_DURATION = 300, -- How long victory/defeat alerts stay onscreen CAPTURE_MESSAGE_DURATION = 15, -- Duration for capture/guard/empty notices - ATTACK_MESSAGE_DURATION = 15 -- Duration for attack alerts + ATTACK_MESSAGE_DURATION = 15, -- Duration for attack alerts + GARBAGE_COLLECTION_FREQUENCY = 600 -- Lua garbage collection cadence (seconds) - helps prevent memory buildup } -- ========================================== @@ -225,7 +231,8 @@ end -- NOTE: These are exported as globals for plugin compatibility (e.g., Moose_DynamicGroundBattle_Plugin.lua) zoneCaptureObjects = {} -- Global: accessible by other scripts zoneNames = {} -- Global: accessible by other scripts -local zoneMetadata = {} -- Stores coalition ownership info +local zoneMetadata = {} -- Stores coalition ownership info +local activeTacticalMarkers = {} -- Track tactical markers to prevent memory leaks -- Function to initialize all zones from configuration local function InitializeZones() @@ -481,16 +488,16 @@ local function CreateTacticalInfoMarker(ZoneCapture) text = text .. string.format(" C:%d", forces.neutral) end - -- Append TGTS for the enemy of the viewer, capped at 10 units + -- Append TGTS for the enemy of the viewer, capped at 5 units (reduced from 10 to lower memory usage) local enemyCoalition = (viewerCoalition == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE local enemyCount = (enemyCoalition == coalition.side.RED) and (forces.red or 0) or (forces.blue or 0) - if enemyCount > 0 and enemyCount <= 10 then + if enemyCount > 0 and enemyCount <= 8 then -- Only process if 8 or fewer enemies (reduced from 10) local enemyCoords = GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition) log(string.format("[TACTICAL DEBUG] Building marker text for %s viewer: %d enemy units", (viewerCoalition==coalition.side.BLUE and "BLUE" or "RED"), #enemyCoords)) if #enemyCoords > 0 then text = text .. "\nTGTS:" for i, unit in ipairs(enemyCoords) do - if i <= 10 then + if i <= 5 then -- Reduced from 10 to 5 to save memory local shortType = (unit.type or "Unknown"):gsub("^%w+%-", ""):gsub("%s.*", "") local cleanMgrs = (unit.mgrs or ""):gsub("^MGRS%s+", ""):gsub("%s+", " ") if i == 1 then @@ -500,8 +507,8 @@ local function CreateTacticalInfoMarker(ZoneCapture) end end end - if #enemyCoords > 10 then - text = text .. string.format(" (+%d)", #enemyCoords - 10) + if #enemyCoords > 5 then -- Updated threshold + text = text .. string.format(" (+%d)", #enemyCoords - 5) end end end @@ -967,6 +974,13 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function() end, {}, MESSAGE_CONFIG.STATUS_BROADCAST_START_DELAY, MESSAGE_CONFIG.STATUS_BROADCAST_FREQUENCY ) +-- Periodic garbage collection to prevent Lua memory buildup +SCHEDULER:New( nil, function() + collectgarbage("collect") + local memKB = collectgarbage("count") + log(string.format("[MEMORY] Lua garbage collection complete. Current usage: %.1f MB", memKB / 1024)) + end, {}, 120, MESSAGE_CONFIG.GARBAGE_COLLECTION_FREQUENCY ) + -- Periodic zone color verification system (every 2 minutes) local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function() log("[ZONE COLORS] Running periodic zone color verification...") diff --git a/DCS_Normandy/Operation Thunderbolt/Moose_DynamicGroundBattle_Plugin.lua b/DCS_Normandy/Operation Thunderbolt/Moose_DynamicGroundBattle_Plugin.lua index 7a5eaf4..2d65849 100644 --- a/DCS_Normandy/Operation Thunderbolt/Moose_DynamicGroundBattle_Plugin.lua +++ b/DCS_Normandy/Operation Thunderbolt/Moose_DynamicGroundBattle_Plugin.lua @@ -1,4 +1,7 @@ --[[ +- **Author**: F99th-TracerFacer +- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.) + Script: Moose_DynamicGroundBattle_Plugin.lua Written by: [F99th-TracerFacer] Version: 1.0.0 @@ -72,6 +75,8 @@ - Spawns occur in zones controlled by the appropriate coalition - AI tasks units to patrol zones from DualCoalitionZoneCapture's ZONE_CONFIG --]] +---@diagnostic disable: undefined-global, lowercase-global +-- MOOSE framework globals are defined at runtime by DCS World ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- USER CONFIGURATION SECTION @@ -100,8 +105,8 @@ local INIT_RED_INFANTRY = 15 -- Initial number of Red Infantry groups local MAX_RED_INFANTRY = 100 -- Maximum number of Red Infantry groups local SPAWN_SCHED_RED_INFANTRY = 1200 -- Base spawn frequency for Red Infantry (seconds) -local INIT_RED_ARMOR = 25 -- Initial number of Red Armor groups -local MAX_RED_ARMOR = 100 -- Maximum number of Red Armor groups +local INIT_RED_ARMOR = 30 -- Initial number of Red Armor groups +local MAX_RED_ARMOR = 500 -- Maximum number of Red Armor groups local SPAWN_SCHED_RED_ARMOR = 200 -- Base spawn frequency for Red Armor (seconds) -- Blue Side Settings @@ -109,8 +114,8 @@ local INIT_BLUE_INFANTRY = 15 -- Initial number of Blue Infantry group local MAX_BLUE_INFANTRY = 100 -- Maximum number of Blue Infantry groups local SPAWN_SCHED_BLUE_INFANTRY = 1200 -- Base spawn frequency for Blue Infantry (seconds) -local INIT_BLUE_ARMOR = 25 -- Initial number of Blue Armor groups -local MAX_BLUE_ARMOR = 100 -- Maximum number of Blue Armor groups +local INIT_BLUE_ARMOR = 30 -- Initial number of Blue Armor groups +local MAX_BLUE_ARMOR = 500 -- Maximum number of Blue Armor groups local SPAWN_SCHED_BLUE_ARMOR = 200 -- Base spawn frequency for Blue Armor (seconds) local ASSIGN_TASKS_SCHED = 900 -- How often to reassign tasks to idle groups (seconds) @@ -133,7 +138,8 @@ local BLUE_ARMOR_SPAWN_GROUP = "BlueArmorGroup" -- AI Tasking Behavior -- Note: DCS engine can crash with "CREATING PATH MAKES TOO LONG" if units try to path too far -- Keep these values conservative to reduce pathfinding load and avoid server crashes -local MAX_ATTACK_DISTANCE = 45000 -- Maximum distance in meters for attacking enemy zones. Units won't attack zones farther than this. (25km ≈ 13.5nm) +-- OPTIMIZATION: Reduced MAX_ATTACK_DISTANCE from 25km to 20km to reduce pathfinding complexity +local MAX_ATTACK_DISTANCE = 20000 -- Maximum distance in meters for attacking enemy zones. Units won't attack zones farther than this. (20km ≈ 10.8nm) local ATTACK_RETRY_COOLDOWN = 1800 -- Seconds a group will wait before re-attempting an attack if no valid enemy zone was found (30 minutes) -- Define warehouses for each side @@ -562,7 +568,7 @@ local function TryDefenderRotation(group, zone) end end - if oldestDefender and oldestDefenderGroup:GetName() ~= group:GetName() then + if oldestDefender and oldestDefenderGroup and oldestDefenderGroup:GetName() ~= group:GetName() then -- Remove old defender for i, defenderName in ipairs(garrison.defenders) do if defenderName == oldestDefender then @@ -632,9 +638,10 @@ local function AssignTasksToGroups() if zoneInfo and zoneInfo.zone then env.info(string.format("[DGB PLUGIN] %s: Defender patrol in zone %s", groupName, zoneName)) -- Use simpler patrol method to reduce pathfinding memory + -- Reduced patrol radius from 0.5 to 0.3 to create simpler paths local zoneCoord = zoneInfo.zone:GetCoordinate() if zoneCoord then - local patrolPoint = zoneCoord:GetRandomCoordinateInRadius(zoneInfo.zone:GetRadius() * 0.5) + local patrolPoint = zoneCoord:GetRandomCoordinateInRadius(zoneInfo.zone:GetRadius() * 0.3) -- Reduced from 0.5 local speed = IsInfantryGroup(group) and 15 or 25 -- km/h - slow patrol group:RouteGroundTo(patrolPoint, speed, "Vee", 1) end @@ -682,7 +689,7 @@ local function AssignTasksToGroups() end -- 3. HANDLE GROUPS IN FRIENDLY ZONES - if currentZone and currentZoneCapture:GetCoalition() == groupCoalition then + if currentZone and currentZoneCapture and currentZoneCapture:GetCoalition() == groupCoalition then local zoneName = currentZone:GetName() -- PRIORITY 1: If the zone is under attack, all non-defenders should help defend it @@ -752,9 +759,10 @@ local function AssignTasksToGroups() -- Use simpler waypoint-based routing instead of TaskRouteToZone to reduce pathfinding memory load -- This prevents the "CREATING PATH MAKES TOO LONG" memory buildup + -- Reduced radius from 0.7 to 0.5 to create simpler, shorter paths local zoneCoord = closestEnemyZone:GetCoordinate() if zoneCoord then - local randomPoint = zoneCoord:GetRandomCoordinateInRadius(closestEnemyZone:GetRadius() * 0.7) + local randomPoint = zoneCoord:GetRandomCoordinateInRadius(closestEnemyZone:GetRadius() * 0.5) -- Reduced from 0.7 local speed = IsInfantryGroup(group) and 20 or 40 -- km/h group:RouteGroundTo(randomPoint, speed, "Vee", 1) end @@ -1189,8 +1197,10 @@ local function CleanupStaleData() end end - -- Force Lua garbage collection to reclaim memory - collectgarbage("collect") + -- Force aggressive Lua garbage collection to reclaim memory + -- Step-based collection helps ensure thorough cleanup + collectgarbage("collect") -- Full collection + collectgarbage("collect") -- Second pass to catch finalized objects if cleanedGroups > 0 or cleanedCooldowns > 0 or cleanedGarrisons > 0 then env.info(string.format("[DGB PLUGIN] Cleanup: Removed %d groups, %d cooldowns, %d garrisons", @@ -1200,10 +1210,13 @@ end -- Optional periodic memory usage logging (Lua-only; shows in dcs.log) local ENABLE_MEMORY_LOGGING = true -local MEMORY_LOG_INTERVAL = 900 -- seconds (15 minutes) -local CLEANUP_INTERVAL = 600 -- seconds (10 minutes) +local MEMORY_LOG_INTERVAL = 600 -- seconds (10 minutes) - reduced from 15 minutes +local CLEANUP_INTERVAL = 300 -- seconds (5 minutes) - reduced from 10 minutes for more aggressive cleanup local function LogMemoryUsage() + -- Force garbage collection before measuring to get accurate readings + collectgarbage("collect") + local luaMemoryKB = collectgarbage("count") local luaMemoryMB = luaMemoryKB / 1024