Skip to content

Commit 77445de

Browse files
committed
refactor: Reorganize relationship tracking functions for improved clarity (and maybe catch some rarer edge cases)
1 parent 52f5d03 commit 77445de

File tree

1 file changed

+77
-42
lines changed

1 file changed

+77
-42
lines changed

script/selector.lua

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,26 @@ local function is_inside(position, region)
7474
return position.x >= a0.x and position.x <= a1.x and position.y >= a0.y and position.y <= a1.y
7575
end
7676

77-
-- Keep track of important entity relationships so their loss can be handled gracefully.
78-
local registry = {}
79-
80-
-- Dispatch notifications for the loss of an entity
81-
---@param victim id
82-
registry.lose = function (victim)
83-
local mourners = storage.mourners[victim]
84-
storage.mourners[victim] = nil
85-
if not mourners then return end
77+
-------------------------------------------------------------------------------
78+
-- Entity relationship tracking
79+
-------------------------------------------------------------------------------
8680

87-
if not registry.mourn then return end
88-
for mourner in pairs(mourners) do
89-
registry.mourn(mourner, victim)
81+
---@param id id
82+
---@param target LuaEntity # This should be an entity with a valid unit_number
83+
local function remember(id, target)
84+
if not target or not target.valid then
85+
if not DEBUG then return end
86+
error(string.format("Selector %q is trying to track an invalid entity: %s", id, serpent.line(target)))
9087
end
91-
end
88+
-- Events other than on_object_destroyed may also interact with this system, so unit_number is preferable to registration number
89+
local target_id = target.unit_number
90+
-- If an entity cannot be identified by unit_number, it shouldn't be tracked here. This should not happen in general.
91+
if not target_id then
92+
if not DEBUG then return end
93+
error(string.format("Selector %q is trying to track an entity without a unit_number: %s", id, serpent.line(target)))
94+
end
95+
script.register_on_object_destroyed(target)
9296

93-
---@param id id
94-
---@param target LuaEntity
95-
registry.remember = function (id, target)
96-
if not target or not target.valid then return end
97-
local _, target_id = script.register_on_object_destroyed(target)
98-
--local target_id = target.unit_number --[[@as id]]
9997
local mourners = storage.mourners[target_id]
10098
if not mourners then
10199
storage.mourners[target_id] = { [id] = true }
@@ -104,6 +102,63 @@ registry.remember = function (id, target)
104102
end
105103
end
106104

105+
-- Handle unexpected loss of an entity
106+
-- This will be triggered by on_object_destroyed, notifying any selectors that may depend on an entity. If a selector no longer depends on the entity, it simply ignores it.
107+
-- This is also called when "victim" is modified in some way that may render a selector invalid for some reason.
108+
-- Handling one of these events will fall into one of these four categories, in order of increasing severity:
109+
-- - No action required (e.g. victim was a stale reference to an old target)
110+
-- - The targeted inventory should be re-evaluated (e.g. the target is a proxy container whose own target may have changed)
111+
-- - The target itself may have changed (e.g. either the mourner or its target have moved)
112+
-- - The selector should be destroyed (e.g. the mourner itself is no longer valid)
113+
-- Any of these should behave in an idempotent manner, allowing for straightforward handling of events immediately as they come in,
114+
-- though possibly with some redundant work.
115+
--
116+
-- A smarter implementation might batch these actions together, applying only the most severe resolution necessary (as each also
117+
-- ensures that any "lesser" issues would be resolved if present). The tricky part of this approach would be ensuring that batched
118+
-- actions were still performed on same tick, as there is no guarantee of event order relative to `on_tick`.
119+
---@param mourner id # Selector's id
120+
---@param victim id # Generally this should be a valid unit_number
121+
local function mourn(mourner, victim)
122+
local data = storage.selectors[mourner]
123+
124+
-- If the mourner is already gone, then there's nothing to do.
125+
if not data then return end
126+
127+
-- Regardless of the victim's relationship to the mourner, validate its entity references.
128+
129+
-- If the mourner's entity is no longer valid, it should be cleaned up.
130+
if not data.entity or not data.entity.valid then return data:destroy() end
131+
-- If the mourner's target is no longer valid, it should attempt to find a new one.
132+
if data.target and not data.target.valid then return data:update_proxy_target() end
133+
134+
-- Determine the relationship to the mourner
135+
if victim == mourner or victim == -mourner or victim == data.target_id then
136+
-- A change to the mourner itself or its direct target requires checking that the target is still within reach.
137+
return data:update_proxy_target()
138+
elseif victim == data.chained_id then
139+
-- A change to the mourner's indirect (chained) target requires simply updating the proxy configuration.
140+
return data:update_proxy_inventory()
141+
end
142+
-- No longer relevant, so just ignore it
143+
end
144+
145+
-- Dispatch notifications for the loss of an entity
146+
---@param victim id
147+
local function notify(victim)
148+
-- Technically, victim could be anything. It's exposed to the external API to allow other mods to attempt to work
149+
-- cooperatively, but in general this should be a unit_number. It is possible for unit_number to be nil for certain
150+
-- entities, but none of those should matter to this mod.
151+
if not victim then return end
152+
153+
local mourners = storage.mourners[victim]
154+
storage.mourners[victim] = nil
155+
if not mourners then return end
156+
157+
for mourner in pairs(mourners) do
158+
mourn(mourner, victim)
159+
end
160+
end
161+
107162
-------------------------------------------------------------------------------
108163
-- Selector state
109164
-------------------------------------------------------------------------------
@@ -284,7 +339,7 @@ function selector:update_proxy_inventory()
284339
index = chained_index
285340

286341
-- Track the secondary target as well
287-
registry.remember(self.id, chained)
342+
remember(self.id, chained)
288343
self.chained_id = chained.unit_number--[[@as id]]
289344
end
290345

@@ -327,7 +382,7 @@ function selector:update_proxy_target(target)
327382
local entity = self.entity
328383
local mode = self.mode
329384

330-
registry.remember(id, entity)
385+
remember(id, entity)
331386

332387
target = target or self.target
333388

@@ -384,7 +439,7 @@ function selector:update_proxy_target(target)
384439

385440
self.target = target
386441
self.target_id = target and target.unit_number
387-
if target then registry.remember(id, target) end
442+
if target then remember(id, target) end
388443

389444
return self:update_proxy_inventory()
390445
end
@@ -729,28 +784,8 @@ local function from_tag(entity, tag)
729784
end
730785
end
731786

732-
-- Handle unexpected loss of an entity
733-
-- This will be triggered by on_object_destroyed, notifying any selectors that may depend on an entity. If a selector no longer depends on the entity, it simply ignores it.
734-
registry.mourn = function (id, victim)
735-
local data = storage.selectors[id]
736-
if not data then return end
737-
if not data.entity or not data.entity.valid then
738-
return data:destroy()
739-
end
740-
-- Determine the relationship
741-
if victim == id or victim == -id or victim == data.target_id or victim == data.chained_id then
742-
-- Victim was the entity controlled by this selector, but is still valid; or
743-
-- Victim was the selector's direct target, so try to find a new one; or
744-
-- Victim was the selector's chained target, so try to reconnect to an inventory through the proxy
745-
return data:update_proxy_target()
746-
end
747-
-- No longer relevant, so just ignore it
748-
end
749-
750787
-- Handle relevant events
751788

752-
local notify = registry.lose
753-
754789
---@param from LuaEntity
755790
---@param to LuaEntity
756791
local function copy_settings(from, to)

0 commit comments

Comments
 (0)