mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
Document delete flag in OnRemove hooks
This commit is contained in:
parent
e4b12f4a28
commit
d5c9abc57f
4 changed files with 69 additions and 9 deletions
|
|
@ -16,8 +16,8 @@ local jecs = require("@jecs")
|
||||||
--[[
|
--[[
|
||||||
There are two ways to create tags:
|
There are two ways to create tags:
|
||||||
|
|
||||||
1. Using jecs.tag() - preregister a tag entity which will be allocated when you create the world.
|
1. Using jecs.tag() to preregister a tag entity which will be allocated when you create the world.
|
||||||
2. Using world:entity() - creates a regular entity id
|
2. Using world:entity() to create a regular entity id
|
||||||
|
|
||||||
The first method is the "proper" way to create tags but it hinges upon that
|
The first method is the "proper" way to create tags but it hinges upon that
|
||||||
you do remember to create the world after declaring all of your
|
you do remember to create the world after declaring all of your
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,6 @@ end
|
||||||
-- Cached query (faster for repeated use)
|
-- Cached query (faster for repeated use)
|
||||||
local cached_query = world:query(Position, Velocity):cached()
|
local cached_query = world:query(Position, Velocity):cached()
|
||||||
for entity, pos, vel in cached_query do
|
for entity, pos, vel in cached_query do
|
||||||
-- Process entities - this is faster for repeated iterations
|
-- Process entities. This is faster for repeated iterations.
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ world:set(Transform, jecs.OnAdd, function(entity, id, data)
|
||||||
print(`Transform added to entity {entity}`)
|
print(`Transform added to entity {entity}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
world:set(Transform, jecs.OnRemove, function(entity, id)
|
world:set(Transform, jecs.OnRemove, function(entity, id, delete)
|
||||||
-- A transform component id has been removed from entity
|
-- A transform component id has been removed from entity
|
||||||
|
-- delete is true if the entity is being deleted, false/nil otherwise
|
||||||
print(`Transform removed from entity {entity}`)
|
print(`Transform removed from entity {entity}`)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
@ -37,9 +38,67 @@ end)
|
||||||
|
|
||||||
When an entity graph contains cycles, order is undefined. This includes cycles
|
When an entity graph contains cycles, order is undefined. This includes cycles
|
||||||
that can be formed using different relationships.
|
that can be formed using different relationships.
|
||||||
|
]]
|
||||||
However an important note to make is that structural changes are not
|
|
||||||
necessarily always safe in OnRemove hooks. For instance, when an entity is
|
--[[
|
||||||
being deleted and invokes all of the OnRemove hooks on its components. It
|
Structural changes in OnRemove hooks
|
||||||
can cause a lot of issues with moving entities
|
|
||||||
|
You can call world:add, world:remove, and world:set inside OnRemove hooks.
|
||||||
|
That's fine. But there's a catch.
|
||||||
|
|
||||||
|
When an entity is being deleted, all of its components get removed. Each
|
||||||
|
removal triggers the OnRemove hook. If you try to make structural changes
|
||||||
|
to the entity during deletion, like removing more components or adding new
|
||||||
|
ones, you're fighting against the deletion process itself. The entity is
|
||||||
|
going to lose all its components anyway, so what's the point?
|
||||||
|
|
||||||
|
This creates a conflict. On one hand, you might want to clean up related
|
||||||
|
components when a specific component is removed. On the other hand, during
|
||||||
|
deletion, you don't want to do that because the entity is already being
|
||||||
|
torn down. So you need a way to tell the difference.
|
||||||
|
|
||||||
|
The solution is the delete boolean. Every OnRemove hook receives it as the
|
||||||
|
third parameter. It's true when the entity is being deleted, and false
|
||||||
|
(or nil) when you're just removing a single component normally.
|
||||||
|
|
||||||
|
So you check it. If delete is true, you bail out early. If it's false,
|
||||||
|
you do your cleanup. Simple.
|
||||||
|
|
||||||
|
Here's what it looks like in practice:
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Health = world:component()
|
||||||
|
local Dead = world:component()
|
||||||
|
|
||||||
|
world:set(Health, jecs.OnRemove, function(entity, id, delete)
|
||||||
|
if delete then
|
||||||
|
-- Entity is being deleted, don't try to clean up
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Normal removal, do cleanup
|
||||||
|
world:remove(entity, Dead)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The ob.luau module uses this pattern extensively. When you're building
|
||||||
|
observers or monitors that track component removals, you need to distinguish
|
||||||
|
between "component removed" and "entity deleted" because they mean different
|
||||||
|
things for your tracking logic.
|
||||||
|
|
||||||
|
Now, about the DEBUG flag. If you create a world with DEBUG enabled:
|
||||||
|
|
||||||
|
local world = jecs.world(true)
|
||||||
|
|
||||||
|
Then the world will actively prevent you from calling world:add, world:remove,
|
||||||
|
or world:set inside OnRemove hooks when delete is true. It throws an error
|
||||||
|
that tells you exactly what went wrong. This is useful during development
|
||||||
|
to catch cases where you forgot to check the delete flag.
|
||||||
|
|
||||||
|
But here's the important part: even with DEBUG enabled, you're still allowed
|
||||||
|
to call these functions when delete is false. The DEBUG mode only prevents
|
||||||
|
structural changes during deletion, not during normal component removal.
|
||||||
|
|
||||||
|
So the pattern is always the same: check delete, bail if true, proceed if false.
|
||||||
|
The DEBUG flag just makes sure you don't forget to do the check.
|
||||||
]]
|
]]
|
||||||
|
|
|
||||||
|
|
@ -3748,6 +3748,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
DEBUG_DELETING_ENTITY = entity
|
DEBUG_DELETING_ENTITY = entity
|
||||||
DEBUG_IS_INVALID_ENTITY(entity)
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
canonical_world_delete(world, entity, id)
|
canonical_world_delete(world, entity, id)
|
||||||
|
DEBUG_DELETING_ENTITY = nil
|
||||||
end
|
end
|
||||||
world_delete = world_delete_checked
|
world_delete = world_delete_checked
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue