fix: use composition.reg_id for entity identity in slot_of

get_children() returns fresh userdata wrappers without __eq; raw ==
compares Lua pointers (fails across separate get_children calls).
slot_of now reads composition.reg_id from each child to find the
matching slot, making contains/remove reliable across wrapper instances.
This commit is contained in:
Calic
2026-06-13 17:35:21 +02:00
parent 6822184c46
commit a814b10ef2
2 changed files with 35 additions and 6 deletions

View File

@@ -36,11 +36,26 @@ local function next_slot(container)
return string.format("item.%d", max_n + 1)
end
-- Return a stable entity identity key for comparison.
-- Uses composition.reg_id (a monotonic integer set on all composition
-- entities). Falls back to nil for raw entities.
local function entity_id(e)
local rid = e:get_property("composition.reg_id")
-- reg_id is 0 for non-composition entities (or unset); treat as nil.
if rid and rid ~= 0 then return rid end
return nil
end
-- Find the slot name under which `item` is attached to `container`.
-- Returns the slot string or nil if not found.
-- Compares by composition.reg_id because get_children() returns fresh
-- userdata wrappers (no __eq on entity userdata — raw pointer differs).
local function slot_of(container, item)
local item_rid = entity_id(item)
-- get_children() returns fresh userdata wrappers each call; no cache.
for slot_name, child in pairs(container:get_children()) do
if child == item then
local child_rid = entity_id(child)
if item_rid ~= nil and child_rid == item_rid then
return slot_name
end
end
@@ -55,14 +70,18 @@ local function validate_container(container)
error("inventory.add: entity has no container block " ..
"(template must declare container={kind='list'})")
end
-- Defensive: composition.define_template currently rejects non-list
-- kinds at template-load, so this branch is unreachable via normal flow.
-- Kept as forward-protection if composition relaxes that constraint.
if block.kind ~= "list" then
error(string.format(
"inventory.add: container.kind must be 'list' (got '%s')",
tostring(block.kind)))
end
-- Double-belt: reject any declared constraint fields (composition v0.2
-- already rejects these at template-load, but we guard here too so the
-- lib is safe against containers created outside the composition gateway).
-- Defensive: composition v0.2 rejects declared constraint fields at
-- template-load (weight_max/volume_max/grid/accepts_fluid/accepts_gas/
-- restrictions), so this branch is unreachable via normal flow. Kept
-- as forward-protection if composition relaxes that constraint.
local constraint_fields = {
"weight_max", "volume_max", "grid",
"accepts_fluid", "accepts_gas", "restrictions",
@@ -159,12 +178,12 @@ function M.contents(container)
return out
end
-- contains(container, item) -> bool
-- Returns true iff `item` is currently in `container`.
function M.contains(container, item)
return slot_of(container, item) ~= nil
end
-- count(container) -> number
-- Returns the number of items currently in `container`.
function M.count(container)
return #M.contents(container)
end