Skip to content
Merged
2 changes: 1 addition & 1 deletion gamedata/modrules.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ local modrules = {
los = {
losMipLevel = 3, -- Controls the resolution of the LOS calculations. A higher value means lower resolution but increased performance. An increase by one level means half the resolution of the LOS map in both x and y direction. Must be between 0 and 6 inclusive.
airMipLevel = 4, -- Controls the resolution of the LOS vs. aircraft calculations. A higher value means lower resolution but increased performance. An increase by one level means half the resolution of the air-LOS map in both x and y direction. Must be between 0 and 30 inclusive. [1] - jK describe for you what the value means.
radarMipLevel = 3, -- Controls the resolution of the radar. See description of airMipLevel for details.
radarMipLevel = 2, -- Controls the resolution of the radar. See description of airMipLevel for details.
},
},

Expand Down
167 changes: 136 additions & 31 deletions luarules/callins/synthetic_callins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@
-- event system, in many cases, so code built on top of these foundations needs
-- to keep up with the times and adapt as the gaps fill in from the other side.
--
-- This also produces two types of summary view of a base callin:
-- 1. *Post.* This is a mark-and-sweep pattern to update dirtied IDs.
-- 2. *Total.* This is an accumulator with a hook for custom results.
-- This also produces a few types of "summary views" over a base callin:
-- - *Post.* This is a mark-and-sweep pattern to update dirtied IDs.
-- - *Total.* This is an accumulator with a hook for custom results.
-- - *Transition.* Tracks state changes, holding a latch and ignoring loops.
--
-- Adding a new callin:
-- 1. Add the callin's envs and subscriptions to syntheticCallins.
-- 2. If producing a summary view, add to syntheticCallinSummaries.
-- 2. If producing a summary view, add to one of the following sets:
-- `syntheticCallinSummaries`:
-- This produces both the <Base>Post and <Base>Total callins and
-- adds the custom GG.Accumulate<Base> hook later during install.
-- `syntheticCallinTransitions`:
-- This produces only the <Base>Post callin and collects state changes
-- to be raised later only if they do not resolve subframe, eg A->B->A.
-- 3. If the callin tracks more state, add it to syntheticCallinUpdate.
-- 4. Add the callin's implementation (and locals) to the Dispatch section.
-- 5. Add the callin to the handler in the Install section.
Expand All @@ -33,26 +38,31 @@
-- can become unhooked whenever no addon happens to subscribe to their base.
local syntheticCallins = {
shared = {
MetaUnitAdded = { 'UnitGiven', 'UnitCreated' },
MetaUnitRemoved = { 'UnitTaken', 'UnitDestroyed' },
MetaUnitAdded = { "UnitGiven", "UnitCreated" },
MetaUnitRemoved = { "UnitTaken", "UnitDestroyed" },
},

synced = {
UnitAutoTargetRange = { 'AllowWeaponTarget' },
UnitBuildStepPost = { 'GameFramePost', 'AllowUnitBuildStep' },
FeatureBuildStepPost = { 'GameFramePost', 'AllowFeatureBuildStep' },
UnitBuildStepTotal = { 'GameFramePost', 'AllowUnitBuildStep' },
FeatureBuildStepTotal = { 'GameFramePost', 'AllowFeatureBuildStep' },
UnitAutoTargetRange = { "AllowWeaponTarget" },
UnitBuildStepPost = { "GameFramePost", "AllowUnitBuildStep" },
FeatureBuildStepPost = { "GameFramePost", "AllowFeatureBuildStep" },
UnitBuildStepTotal = { "GameFramePost", "AllowUnitBuildStep" },
FeatureBuildStepTotal = { "GameFramePost", "AllowFeatureBuildStep" },
UnitIdlePost = { "GameFramePost", "UnitIdle", "UnitCommand", "UnitTaken", "UnitDestroyed" }, -- See modules/unit_idle_states.lua.
},

unsynced = {},
}

local syntheticCallinSummaries = {
UnitBuildStep = true,
UnitBuildStep = true,
FeatureBuildStep = true,
}

local syntheticCallinTransitions = {
UnitIdle = true,
}

-- The engine does not know these names, so `Script.UpdateCallIn` is a no-op.
-- We have to handle dropping tracked state, etc., during updates on our own.
-- Update handlers receive the gadgetHandler to read their subscriber lists.
Expand All @@ -67,13 +77,13 @@
local callinHoldSummary = {}
for name, callinHolds in pairs(syntheticCallinHold) do
for _, callin in ipairs(callinHolds) do
local listName = callin .. 'List'
local listName = callin .. "List"
local holders = callinHoldSummary[listName]
if not holders then
holders = {}
callinHoldSummary[listName] = holders
end
holders[#holders + 1] = name .. 'List'
holders[#holders + 1] = name .. "List"
end
end

Expand Down Expand Up @@ -121,8 +131,11 @@
---@class SummaryActive
---@field [1] true? whether accumulating totals

---Sticky-state per marked ID, kept while active.
---@alias SummaryLatched table<integer, true>

local function createSummary(callinName)
if not syntheticCallinSummaries[callinName] then

Check warning on line 138 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

unnecessary-if

Impossible `if` statement: this condition is always falsy
return
end

Expand All @@ -134,7 +147,7 @@

marks[callinName] = { marked = marked, list = list, count = count, totals = totals, active = active, stop = stop }

accumulate['Accumulate' .. callinName] = function(id, amount)
accumulate["Accumulate" .. callinName] = function(id, amount)
-- Call sites must not accumulate when not subscribed.
local n = count[1]
if not n then
Expand All @@ -154,8 +167,8 @@
end

-- Both summary views share updates so must handle updating together.
local postList = callinName .. 'PostList'
local totalList = callinName .. 'TotalList'
local postList = callinName .. "PostList"
local totalList = callinName .. "TotalList"
local function update(gh)
if #gh[totalList] > 0 then
active[1] = true
Expand All @@ -171,8 +184,36 @@
stop()
end
end
syntheticCallinUpdate[callinName .. 'Post'] = update
syntheticCallinUpdate[callinName .. 'Total'] = update
syntheticCallinUpdate[callinName .. "Post"] = update
syntheticCallinUpdate[callinName .. "Total"] = update
end

local function createTransition(callinName)
if not syntheticCallinTransitions[callinName] then

Check warning on line 192 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

unnecessary-if

Impossible `if` statement: this condition is always falsy
return
end

---@type SummaryMarked, SummaryList, SummaryCount
local marked, list, count = {}, {}, table.new(1, 0)
---@type SummaryLatched
local latched = {}
local stop = makeStopMarking(marked, list, count)

marks[callinName] = { marked = marked, list = list, count = count, latched = latched, stop = stop }

local postList = callinName .. "PostList"
syntheticCallinUpdate[callinName .. "Post"] = function(gh)
if #gh[postList] > 0 then
count[1] = count[1] or 0
else
stop()
-- After the last subscriber drops, any late-subscribers cannot trust
-- the latched states anymore, so the states also must be dropped.
for id in pairs(latched) do
latched[id] = nil
end
end
end
end

---A summary's marking state. Callins from non-matching envs get empty tables.
Expand All @@ -185,8 +226,8 @@
local function getMarks(baseName)
local mark = marks[baseName]
if not mark then
if not syntheticCallinSummaries[baseName] then
error('synthetic_callins: no such summary: ' .. tostring(baseName))
if not syntheticCallinSummaries[baseName] and not syntheticCallinTransitions[baseName] then
error("synthetic_callins: no such summary: " .. tostring(baseName))
end
return {}, {}, {}, {}, {}
end
Expand All @@ -195,14 +236,19 @@

local function getMarksUnsafe(baseName)
local mark = marks[baseName]
return mark.marked, mark.list, mark.count, mark.totals, mark.active

Check warning on line 239 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

mark may be nil

Check warning on line 239 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

mark may be nil

Check warning on line 239 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

mark may be nil

Check warning on line 239 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

mark may be nil

Check warning on line 239 in luarules/callins/synthetic_callins.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

mark may be nil
end

---@return SummaryLatched latched
local function getLatchUnsafe(baseName)
return marks[baseName].latched
end

local function createSweep(callinName)
local marked, list, countBox, totals, activeBox = getMarksUnsafe(callinName)
local values = {}
local postName, totalName = callinName .. 'Post', callinName .. 'Total'
local postListName, totalListName = postName .. 'List', totalName .. 'List'
local postName, totalName = callinName .. "Post", callinName .. "Total"
local postListName, totalListName = postName .. "List", totalName .. "List"

return function(handler)
local count = countBox[1]
Expand Down Expand Up @@ -248,11 +294,63 @@
end
end

---We distrust the engine's `:UnitIdle` callin so read the unit queue directly.
---See unit_idle_states.lua for detail on unit behaviors, namely, "idle tasks".
local function createUnitIdleSweep()
local marked, list, countBox = getMarksUnsafe("UnitIdle")
local latched = getLatchUnsafe("UnitIdle")
local transitions, states = {}, {}

local VFSMODE = Spring.IsDevLuaEnabled() and VFS.RAW_FIRST or VFS.ZIP_ONLY
local isIdle = VFS.Include("modules/unit_idle_states.lua", nil, VFSMODE).IsIdle

local spGetUnitDefID = Spring.GetUnitDefID
local spGetUnitIsDead = Spring.GetUnitIsDead

return function(handler)
local count = countBox[1]
if not count or count == 0 then
return
end
countBox[1] = 0

-- Clear marks first so subscribers that throw do not leave any marks.
-- Gathering the batch up front also lets subscribers re-mark safely.
local n = 0
for i = 1, count do
local unitID = list[i]
marked[unitID] = nil

-- Units can be finalized between mark and sweep. Ignore dying units.
local unitDefID = spGetUnitDefID(unitID)
if unitDefID and spGetUnitIsDead(unitID) == false then
local idle = isIdle(unitID, unitDefID)
if idle ~= (latched[unitID] == true) then
latched[unitID] = idle or nil
n = n + 1
transitions[n] = unitID
states[n] = idle
end
else
latched[unitID] = nil
end
end

local postList = handler.UnitIdlePostList
for i = 1, n do
local unitID, idled = transitions[i], states[i]
for _, g in ipairs(postList) do
g:UnitIdlePost(unitID, idled)
end
end
end
end

--------------------------------------------------------------------------------
-- Dispatch ------------------------------------------------------------------
--
-- Callin implementations attach to the gadgetHandler in the Install section.
--
--
-- - UnitAutoTargetRange has its base implementation in gadgets.lua, instead.

local callins = {}
Expand All @@ -274,11 +372,14 @@
-- Synced environment

if Script.GetSynced() then
createSummary('UnitBuildStep')
callins.SweepUnitBuildStep = createSweep('UnitBuildStep')
createSummary("UnitBuildStep")
callins.SweepUnitBuildStep = createSweep("UnitBuildStep")

createSummary("FeatureBuildStep")
callins.SweepFeatureBuildStep = createSweep("FeatureBuildStep")

createSummary('FeatureBuildStep')
callins.SweepFeatureBuildStep = createSweep('FeatureBuildStep')
createTransition("UnitIdle")
callins.SweepUnitIdle = createUnitIdleSweep()
end

-- Unsynced environment
Expand Down Expand Up @@ -310,7 +411,7 @@
end
end

handler.MetaUnitAdded = callins.MetaUnitAdded
handler.MetaUnitAdded = callins.MetaUnitAdded
handler.MetaUnitRemoved = callins.MetaUnitRemoved

-- Wrap multi-env dispatchers for single-env synthetic callins at install
Expand All @@ -320,11 +421,15 @@
local gameFramePost = handler.GameFramePost
local sweepUnitBuildStep = callins.SweepUnitBuildStep
local sweepFeatureBuildStep = callins.SweepFeatureBuildStep
local sweepUnitIdle = callins.SweepUnitIdle

function handler:GameFramePost(frameNum)
tracy.ZoneBeginN("G:GameFramePostSummary")
sweepUnitBuildStep(self)
sweepFeatureBuildStep(self)
sweepUnitIdle(self)
tracy.ZoneEnd()

return gameFramePost(self, frameNum)
end

Expand All @@ -344,8 +449,8 @@
---Synthetic callin registry and dispatch for gadgets.lua.
---@class SyntheticCallinsAPI
local synthetic = {
install = install,
getMarks = getMarks,
install = install,
getMarks = getMarks,
callinNames = callinNames,
}

Expand Down
25 changes: 20 additions & 5 deletions luarules/gadgets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
end
sourceRegistry[cmd] = true
if chatActionBroadcastsEnabled then
BroadcastChatActionUpdate(source, "add", cmd)

Check warning on line 69 in luarules/gadgets.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

function BroadcastChatActionUpdate may be nil
end
end

Expand All @@ -78,7 +78,7 @@
if sourceRegistry[cmd] then
sourceRegistry[cmd] = nil
if chatActionBroadcastsEnabled then
BroadcastChatActionUpdate(source, "remove", cmd)

Check warning on line 81 in luarules/gadgets.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

need-check-nil

function BroadcastChatActionUpdate may be nil
end
end
end
Expand All @@ -103,7 +103,7 @@
end

local function SendChatActionMessage(msg)
if Spring.SendLuaUIMsg then

Check warning on line 106 in luarules/gadgets.lua

View workflow job for this annotation

GitHub Actions / emmylua_check

unnecessary-if

Unnecessary `if` statement: this condition is always truthy
Spring.SendLuaUIMsg(msg)
elseif SendToUnsynced then
SendToUnsynced(msg)
Expand Down Expand Up @@ -461,10 +461,23 @@
-- Synthetic callins
--
-- The game injects some of its own callins into the engine-driven event system:
local synthetic = VFS.Include(SCRIPT_DIR .. 'callins/synthetic_callins.lua', nil, VFSMODE) ---@type SyntheticCallinsAPI
local synthetic = VFS.Include(SCRIPT_DIR .. "callins/synthetic_callins.lua", nil, VFSMODE) ---@type SyntheticCallinsAPI

local unitStepMarked, unitStepList, unitStepCount, unitStepTotals, unitStepActive = synthetic.getMarks('UnitBuildStep')
local featureStepMarked, featureStepList, featureStepCount, featureStepTotals, featureStepActive = synthetic.getMarks('FeatureBuildStep')
-- stylua: ignore start
local unitStepMarked, unitStepList, unitStepCount, unitStepTotals, unitStepActive = synthetic.getMarks("UnitBuildStep")
local featureStepMarked, featureStepList, featureStepCount, featureStepTotals, featureStepActive = synthetic.getMarks("FeatureBuildStep")
local unitIdleMarked, unitIdleList, unitIdleCount = synthetic.getMarks("UnitIdle")
-- stylua: ignore end

local function markIdle(unitID)
local idleCount = unitIdleCount[1]
if idleCount and not unitIdleMarked[unitID] then
unitIdleMarked[unitID] = true
idleCount = idleCount + 1
unitIdleCount[1] = idleCount
unitIdleList[idleCount] = unitID
end
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Expand Down Expand Up @@ -1878,7 +1891,7 @@
return true
end


Check failure on line 1894 in luarules/gadgets.lua

View workflow job for this annotation

GitHub Actions / stylua

Not formatted

2 line(s) would change. Run stylua 2.5.2 on this file and commit the result.
function gadgetHandler:AllowUnitBuildStep(builderID, builderTeam, unitID, unitDefID, part)
tracy.ZoneBeginN("G:AllowUnitBuildStep")

Expand Down Expand Up @@ -2147,10 +2160,10 @@
function gadgetHandler:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam, weaponDefID)
tracy.ZoneBeginN("G:UnitDestroyed")
self:MetaUnitRemoved(unitID, unitDefID, unitTeam)

for _, g in ipairs(self.UnitDestroyedList) do
g:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam, weaponDefID)
end
markIdle(unitID)
tracy.ZoneEnd()
return
end
Expand All @@ -2176,6 +2189,7 @@
for _, g in ipairs(self.UnitIdleList) do
g:UnitIdle(unitID, unitDefID, unitTeam)
end
markIdle(unitID)
tracy.ZoneEnd()
return
end
Expand Down Expand Up @@ -2267,10 +2281,10 @@

function gadgetHandler:UnitTaken(unitID, unitDefID, unitTeam, newTeam)
self:MetaUnitRemoved(unitID, unitDefID, unitTeam)

for _, g in ipairs(self.UnitTakenList) do
g:UnitTaken(unitID, unitDefID, unitTeam, newTeam)
end
markIdle(unitID)
return
end

Expand Down Expand Up @@ -2299,6 +2313,7 @@
for _, g in ipairs(self.UnitCommandList) do
g:UnitCommand(unitID, unitDefID, unitTeam, cmdId, cmdParams, cmdOpts, cmdTag, playerID, fromSynced, fromLua)
end
markIdle(unitID)
tracy.ZoneEnd()
return
end
Expand Down
Loading
Loading