From f2a88f85e9e2881df1bdbba4b9150bb1e51f2de9 Mon Sep 17 00:00:00 2001 From: iTracerFacer <134304944+iTracerFacer@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:51:49 -0600 Subject: [PATCH] Performance enhancments. --- .../Moose_CaptureZones.lua | 66 +++++--- .../Moose_TADC_CargoDispatcher.lua | 9 +- .../Moose_TADC_Load2nd.lua | 149 +++++++++++++++--- Moose_TADC/Moose_TADC_CargoDispatcher.lua | 2 + 4 files changed, 178 insertions(+), 48 deletions(-) diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua index 4c2f762..4d21a79 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_CaptureZones.lua @@ -236,12 +236,32 @@ log("[DEBUG] Alakurtti zone initialization complete") -- Global cached unit set - created once and maintained automatically by MOOSE local CachedUnitSet = nil +-- Utility to guard point-in-zone checks that may throw when objects despawn mid-loop +local function IsUnitInZone(unit, zone) + if not unit or not zone then return false end + + local ok, point = pcall(function() + return unit:GetPointVec3() + end) + + if not ok or not point then + return false + end + + local inZone = false + pcall(function() + inZone = zone:IsPointVec3InZone(point) + end) + + return inZone +end + -- Initialize the cached unit set once local function InitializeCachedUnitSet() if not CachedUnitSet then CachedUnitSet = SET_UNIT:New() :FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types - :FilterOnce() -- Don't filter continuously, we'll use the live set + :FilterStart() -- Keep the set updated by MOOSE without recreating it log("[PERFORMANCE] Initialized cached unit set for zone scanning") end end @@ -260,14 +280,12 @@ local function GetZoneForceStrengths(ZoneCapture) local blueCount = 0 local neutralCount = 0 - -- Get all units in the zone using MOOSE's zone scanning - local unitsInZone = SET_UNIT:New() - :FilterZones({zone}) - :FilterOnce() - - if unitsInZone then - unitsInZone:ForEachUnit(function(unit) - if unit and unit:IsAlive() then + -- Ensure the cached set exists before scanning + InitializeCachedUnitSet() + + if CachedUnitSet then + CachedUnitSet:ForEachUnit(function(unit) + if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then local unitCoalition = unit:GetCoalition() if unitCoalition == coalition.side.RED then redCount = redCount + 1 @@ -302,50 +320,48 @@ local function GetRedUnitMGRSCoords(ZoneCapture) local coords = {} - -- Get all units in the zone using MOOSE's zone scanning - local unitsInZone = SET_UNIT:New() - :FilterZones({zone}) - :FilterOnce() - + -- Ensure the cached set exists before scanning + InitializeCachedUnitSet() + local totalUnits = 0 local redUnits = 0 local unitsWithCoords = 0 - if unitsInZone then - unitsInZone:ForEachUnit(function(unit) - totalUnits = totalUnits + 1 - if unit and unit:IsAlive() then + if CachedUnitSet then + CachedUnitSet:ForEachUnit(function(unit) + if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then + totalUnits = totalUnits + 1 local unitCoalition = unit:GetCoalition() - + -- Only process RED units if unitCoalition == coalition.side.RED then redUnits = redUnits + 1 local coord = unit:GetCoordinate() - + if coord then -- Try multiple methods to get coordinates local mgrs = nil local success_mgrs = false - + -- Method 1: Try ToStringMGRS success_mgrs, mgrs = pcall(function() return coord:ToStringMGRS(5) end) - + -- Method 2: Try ToStringMGRS without precision parameter if not success_mgrs or not mgrs then success_mgrs, mgrs = pcall(function() return coord:ToStringMGRS() end) end - + -- Method 3: Try ToMGRS if not success_mgrs or not mgrs then success_mgrs, mgrs = pcall(function() return coord:ToMGRS() end) end - + -- Method 4: Fallback to Lat/Long if not success_mgrs or not mgrs then success_mgrs, mgrs = pcall(function() @@ -353,7 +369,7 @@ local function GetRedUnitMGRSCoords(ZoneCapture) return string.format("N%s E%s", lat, lon) end) end - + if success_mgrs and mgrs then unitsWithCoords = unitsWithCoords + 1 local unitType = unit:GetTypeName() or "Unknown" diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua index eae8de1..f35c7d0 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_CargoDispatcher.lua @@ -5,7 +5,8 @@ ═══════════════════════════════════════════════════════════════════════════════ DESCRIPTION: - This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them. It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system. + This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them. + It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system. CONFIGURATION: - Update static templates and airfield lists as needed for your mission. @@ -271,9 +272,9 @@ local function cleanupCargoMissions() for _, coalitionKey in ipairs({"red", "blue"}) do for i = #cargoMissions[coalitionKey], 1, -1 do local m = cargoMissions[coalitionKey][i] - if m.status == "failed" then + if m.status == "failed" or m.status == "completed" then if not (m.group and m.group:IsAlive()) then - log("Cleaning up failed cargo mission: " .. (m.group and m.group:GetName() or "nil group") .. " status: failed") + log("Cleaning up " .. m.status .. " cargo mission: " .. (m.group and m.group:GetName() or "nil group")) table.remove(cargoMissions[coalitionKey], i) end end @@ -451,6 +452,8 @@ local function dispatchCargo(squadron, coalitionKey) rat:SetDeparture(origin) rat:SetDestination(destination) rat:NoRespawn() + rat:InitUnControlled(false) -- force departing transports to spawn in a controllable state + rat:InitLateActivated(false) rat:SetSpawnLimit(1) rat:SetSpawnDelay(1) diff --git a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua index e9236f0..749b23c 100644 --- a/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua +++ b/DCS_Kola/Operation_Polar_Shield/Moose_TADC_Load2nd.lua @@ -276,6 +276,105 @@ local airbaseHealthStatus = { blue = {} } +local function coalitionKeyFromSide(side) + if side == coalition.side.RED then return "red" end + if side == coalition.side.BLUE then return "blue" end + return nil +end + +local function cleanupInterceptorEntry(interceptorName, coalitionKey) + if not interceptorName or not coalitionKey then return end + if activeInterceptors[coalitionKey] then + activeInterceptors[coalitionKey][interceptorName] = nil + end + if aircraftSpawnTracking[coalitionKey] then + aircraftSpawnTracking[coalitionKey][interceptorName] = nil + end +end + +local function destroyInterceptorGroup(interceptor, coalitionKey, delaySeconds) + if not interceptor then return end + + local name = nil + if interceptor.GetName then + local ok, value = pcall(function() return interceptor:GetName() end) + if ok then name = value end + end + + local resolvedKey = coalitionKey + if not resolvedKey and interceptor.GetCoalition then + local ok, side = pcall(function() return interceptor:GetCoalition() end) + if ok then + resolvedKey = coalitionKeyFromSide(side) + end + end + + local function doDestroy() + if interceptor and interceptor.IsAlive and interceptor:IsAlive() then + pcall(function() interceptor:Destroy() end) + end + if name and resolvedKey then + cleanupInterceptorEntry(name, resolvedKey) + end + end + + if delaySeconds and delaySeconds > 0 then + timer.scheduleFunction(function() + doDestroy() + return + end, {}, timer.getTime() + delaySeconds) + else + doDestroy() + end +end + +local function finalizeCargoMission(cargoGroup, squadron, coalitionKey) + if not cargoMissions or not coalitionKey or not squadron or not squadron.airbaseName then + return + end + + local coalitionBucket = cargoMissions[coalitionKey] + if type(coalitionBucket) ~= "table" then + return + end + + local groupName = nil + if cargoGroup and cargoGroup.GetName then + local ok, value = pcall(function() return cargoGroup:GetName() end) + if ok then groupName = value end + end + + for idx = #coalitionBucket, 1, -1 do + local mission = coalitionBucket[idx] + if mission and mission.destination == squadron.airbaseName then + local missionGroupName = nil + if mission.group and mission.group.GetName then + local ok, value = pcall(function() return mission.group:GetName() end) + if ok then missionGroupName = value end + end + + if not groupName or missionGroupName == groupName then + mission.status = "completed" + mission.completedAt = timer.getTime() + + if mission.group and mission.group.Destroy then + local targetGroup = mission.group + timer.scheduleFunction(function() + pcall(function() + if targetGroup and targetGroup.IsAlive and targetGroup:IsAlive() then + targetGroup:Destroy() + end + end) + return + end, {}, timer.getTime() + 90) + end + + table.remove(coalitionBucket, idx) + end + end + end +end + -- Logging function local function log(message, detailed) if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then @@ -709,6 +808,8 @@ local function processCargoDelivery(cargoGroup, squadron, coalitionSide, coaliti MESSAGE:New(msg, 10):ToCoalition(coalitionSide) USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide) end + + finalizeCargoMission(cargoGroup, squadron, coalitionKey) end -- Event handler for cargo aircraft landing (backup for actual landings) @@ -1271,10 +1372,9 @@ local function monitorStuckAircraft() -- Mark airbase as having stuck aircraft airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft" - -- Remove the stuck aircraft - trackingData.group:Destroy() - activeInterceptors[coalitionKey][aircraftName] = nil - aircraftSpawnTracking[coalitionKey][aircraftName] = nil + -- Remove the stuck aircraft and clear tracking + pcall(function() trackingData.group:Destroy() end) + cleanupInterceptorEntry(aircraftName, coalitionKey) -- Reassign squadron to alternative airbase reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey) @@ -1342,9 +1442,14 @@ local function sendInterceptorHome(interceptor, coalitionSide) SCHEDULER:New(nil, function() local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue" - if activeInterceptors[coalitionKey][interceptor:GetName()] then - activeInterceptors[coalitionKey][interceptor:GetName()] = nil - log("Cleaned up " .. coalitionName .. " " .. interceptor:GetName() .. " after RTB", true) + local name = nil + if interceptor and interceptor.GetName then + local ok, value = pcall(function() return interceptor:GetName() end) + if ok then name = value end + end + if name and activeInterceptors[coalitionKey][name] then + destroyInterceptorGroup(interceptor, coalitionKey, 0) + log("Cleaned up " .. coalitionName .. " " .. name .. " after RTB", true) end end, {}, flightTime) else @@ -1616,6 +1721,7 @@ local function launchInterceptor(threatGroup, coalitionSide) log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName) return end + spawn:InitCleanUp(900) local interceptors = {} @@ -1674,11 +1780,14 @@ local function launchInterceptor(threatGroup, coalitionSide) -- Emergency cleanup (safety net) SCHEDULER:New(nil, function() - if activeInterceptors[coalitionKey][interceptor:GetName()] then - log("Emergency cleanup of " .. coalitionName .. " " .. interceptor:GetName() .. " (should have RTB'd)") - activeInterceptors[coalitionKey][interceptor:GetName()] = nil - -- Also clean up spawn tracking - aircraftSpawnTracking[coalitionKey][interceptor:GetName()] = nil + local name = nil + if interceptor and interceptor.GetName then + local ok, value = pcall(function() return interceptor:GetName() end) + if ok then name = value end + end + if name and activeInterceptors[coalitionKey][name] then + log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)") + destroyInterceptorGroup(interceptor, coalitionKey, 0) end end, {}, coalitionSettings.emergencyCleanupTime) end @@ -2351,15 +2460,15 @@ local menuAdminRed = MENU_COALITION:New(coalition.side.RED, "Admin / Debug", men MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Emergency Cleanup Interceptors", menuAdminBlue, function() local cleaned = 0 - for _, interceptors in pairs(activeInterceptors.red) do + for name, interceptors in pairs(activeInterceptors.red) do if interceptors and interceptors.group and not interceptors.group:IsAlive() then - interceptors.group = nil + cleanupInterceptorEntry(name, "red") cleaned = cleaned + 1 end end - for _, interceptors in pairs(activeInterceptors.blue) do + for name, interceptors in pairs(activeInterceptors.blue) do if interceptors and interceptors.group and not interceptors.group:IsAlive() then - interceptors.group = nil + cleanupInterceptorEntry(name, "blue") cleaned = cleaned + 1 end end @@ -2368,15 +2477,15 @@ end) MENU_COALITION_COMMAND:New(coalition.side.RED, "Emergency Cleanup Interceptors", menuAdminRed, function() local cleaned = 0 - for _, interceptors in pairs(activeInterceptors.red) do + for name, interceptors in pairs(activeInterceptors.red) do if interceptors and interceptors.group and not interceptors.group:IsAlive() then - interceptors.group = nil + cleanupInterceptorEntry(name, "red") cleaned = cleaned + 1 end end - for _, interceptors in pairs(activeInterceptors.blue) do + for name, interceptors in pairs(activeInterceptors.blue) do if interceptors and interceptors.group and not interceptors.group:IsAlive() then - interceptors.group = nil + cleanupInterceptorEntry(name, "blue") cleaned = cleaned + 1 end end diff --git a/Moose_TADC/Moose_TADC_CargoDispatcher.lua b/Moose_TADC/Moose_TADC_CargoDispatcher.lua index e2577a8..e033542 100644 --- a/Moose_TADC/Moose_TADC_CargoDispatcher.lua +++ b/Moose_TADC/Moose_TADC_CargoDispatcher.lua @@ -444,6 +444,8 @@ local function dispatchCargo(squadron, coalitionKey) rat:SetDeparture(origin) rat:SetDestination(destination) rat:NoRespawn() + rat:InitUnControlled(false) -- ensure template-level 'Uncontrolled' flag does not leave transports parked + rat:InitLateActivated(false) rat:SetSpawnLimit(1) rat:SetSpawnDelay(1) -- Ensure RAT takes off immediately from the runway (hot start) instead of staying parked