mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 01:20:04 +00:00
246 lines
7.6 KiB
Markdown
246 lines
7.6 KiB
Markdown
|
# Observer APIs
|
||
|
|
||
|
jecs provides observer hooks that allow you to respond to component lifecycle events and implement cleanup policies.
|
||
|
|
||
|
## Component Lifecycle Hooks
|
||
|
|
||
|
Component lifecycle hooks let you execute code when components are added, removed, or modified.
|
||
|
|
||
|
### OnAdd
|
||
|
|
||
|
The `OnAdd` hook is called when a component is added to an entity.
|
||
|
|
||
|
```lua
|
||
|
-- Define a component
|
||
|
local Transform = world:component()
|
||
|
|
||
|
-- Set an OnAdd hook for the Transform component
|
||
|
world:set(Transform, jecs.OnAdd, function(entity)
|
||
|
print("Transform component added to entity", entity)
|
||
|
end)
|
||
|
|
||
|
-- The hook will be called when Transform is added to any entity
|
||
|
local entity = world:entity()
|
||
|
world:add(entity, Transform) -- OnAdd hook is triggered
|
||
|
```
|
||
|
|
||
|
TypeScript signature:
|
||
|
```typescript
|
||
|
type OnAddHook = (entity: Entity) => void;
|
||
|
```
|
||
|
|
||
|
### OnRemove
|
||
|
|
||
|
The `OnRemove` hook is called when a component is removed from an entity.
|
||
|
|
||
|
```lua
|
||
|
-- Set an OnRemove hook for the Transform component
|
||
|
world:set(Transform, jecs.OnRemove, function(entity)
|
||
|
print("Transform component removed from entity", entity)
|
||
|
end)
|
||
|
|
||
|
-- The hook will be called when Transform is removed from any entity
|
||
|
world:remove(entity, Transform) -- OnRemove hook is triggered
|
||
|
```
|
||
|
|
||
|
TypeScript signature:
|
||
|
```typescript
|
||
|
type OnRemoveHook = (entity: Entity) => void;
|
||
|
```
|
||
|
|
||
|
### OnSet
|
||
|
|
||
|
The `OnSet` hook is called when a component's value is set or changed on an entity.
|
||
|
|
||
|
```lua
|
||
|
-- Set an OnSet hook for the Transform component
|
||
|
world:set(Transform, jecs.OnSet, function(entity, value)
|
||
|
print("Transform component set on entity", entity, "with value", value)
|
||
|
end)
|
||
|
|
||
|
-- The hook will be called when Transform's value is set on any entity
|
||
|
world:set(entity, Transform, { position = {x = 10, y = 20} }) -- OnSet hook is triggered
|
||
|
```
|
||
|
|
||
|
TypeScript signature:
|
||
|
```typescript
|
||
|
type OnSetHook<T> = (entity: Entity, value: T) => void;
|
||
|
```
|
||
|
|
||
|
## Automatic Cleanup Policies
|
||
|
|
||
|
jecs provides automatic cleanup policies through the `OnDelete` and `OnDeleteTarget` hooks paired with action components.
|
||
|
|
||
|
### OnDelete
|
||
|
|
||
|
The `OnDelete` trait specifies what happens when a component or entity is deleted. It must be paired with an action component like `Delete` or `Remove`.
|
||
|
|
||
|
#### (OnDelete, Delete)
|
||
|
|
||
|
When paired with `Delete`, this policy deletes entities that have the component when the component itself is deleted.
|
||
|
|
||
|
```lua
|
||
|
-- Define a component
|
||
|
local Health = world:component()
|
||
|
|
||
|
-- Add the (OnDelete, Delete) cleanup policy to Health
|
||
|
world:add(Health, jecs.pair(jecs.OnDelete, jecs.Delete))
|
||
|
|
||
|
-- Create entities
|
||
|
local entity1 = world:entity()
|
||
|
local entity2 = world:entity()
|
||
|
|
||
|
-- Add Health component to entities
|
||
|
world:add(entity1, Health)
|
||
|
world:add(entity2, Health)
|
||
|
|
||
|
-- When Health component is deleted, all entities with Health will be deleted
|
||
|
world:delete(Health) -- Deletes entity1 and entity2
|
||
|
```
|
||
|
|
||
|
#### (OnDelete, Remove)
|
||
|
|
||
|
When paired with `Remove`, this policy removes a component from entities when the component itself is deleted.
|
||
|
|
||
|
```lua
|
||
|
-- Define components
|
||
|
local Poison = world:component()
|
||
|
local PoisonEffect = world:component()
|
||
|
|
||
|
-- Add the (OnDelete, Remove) cleanup policy to Poison
|
||
|
world:add(Poison, jecs.pair(jecs.OnDelete, jecs.Remove))
|
||
|
world:add(PoisonEffect, jecs.pair(jecs.OnDelete, jecs.Remove))
|
||
|
|
||
|
-- Create entity
|
||
|
local entity = world:entity()
|
||
|
world:add(entity, Poison)
|
||
|
world:add(entity, PoisonEffect)
|
||
|
|
||
|
-- When Poison component is deleted, PoisonEffect will be removed from all entities
|
||
|
world:delete(Poison) -- Removes PoisonEffect from entity
|
||
|
```
|
||
|
|
||
|
### OnDeleteTarget
|
||
|
|
||
|
The `OnDeleteTarget` trait specifies what happens when a relationship target is deleted. It must be paired with an action component like `Delete` or `Remove`.
|
||
|
|
||
|
#### (OnDeleteTarget, Delete)
|
||
|
|
||
|
When paired with `Delete`, this policy deletes entities that have a relationship with the deleted target.
|
||
|
|
||
|
```lua
|
||
|
-- Define a ChildOf component for parent-child relationships
|
||
|
local ChildOf = world:component()
|
||
|
|
||
|
-- Add the (OnDeleteTarget, Delete) cleanup policy
|
||
|
world:add(ChildOf, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
|
||
|
|
||
|
-- Create parent and child entities
|
||
|
local parent = world:entity()
|
||
|
local child1 = world:entity()
|
||
|
local child2 = world:entity()
|
||
|
|
||
|
-- Establish parent-child relationships
|
||
|
world:add(child1, jecs.pair(ChildOf, parent))
|
||
|
world:add(child2, jecs.pair(ChildOf, parent))
|
||
|
|
||
|
-- When the parent is deleted, all its children will be deleted too
|
||
|
world:delete(parent) -- Deletes child1 and child2
|
||
|
```
|
||
|
|
||
|
#### (OnDeleteTarget, Remove)
|
||
|
|
||
|
When paired with `Remove`, this policy removes a relationship component when its target is deleted.
|
||
|
|
||
|
```lua
|
||
|
-- Define a Likes component for relationships
|
||
|
local Likes = world:component()
|
||
|
|
||
|
-- Add the (OnDeleteTarget, Remove) cleanup policy
|
||
|
world:add(Likes, jecs.pair(jecs.OnDeleteTarget, jecs.Remove))
|
||
|
|
||
|
-- Create entities
|
||
|
local bob = world:entity()
|
||
|
local alice = world:entity()
|
||
|
local charlie = world:entity()
|
||
|
|
||
|
-- Establish relationships
|
||
|
world:add(bob, jecs.pair(Likes, alice))
|
||
|
world:add(charlie, jecs.pair(Likes, alice))
|
||
|
|
||
|
-- When alice is deleted, all Likes relationships targeting her will be removed
|
||
|
world:delete(alice) -- Removes Likes relationship from bob and charlie
|
||
|
```
|
||
|
|
||
|
## Best Practices
|
||
|
|
||
|
1. **Use hooks for reactive logic**: Component hooks are perfect for synchronizing game state with visual representations or external systems.
|
||
|
|
||
|
2. **Keep hook logic simple**: Hooks should be lightweight and focused on a single concern.
|
||
|
|
||
|
3. **Consider cleanup policies carefully**: The right cleanup policies can prevent memory leaks and simplify your codebase by automating entity management.
|
||
|
|
||
|
4. **Avoid infinite loops**: Be careful not to create circular dependencies with your hooks and cleanup policies.
|
||
|
|
||
|
5. **Document your policies**: When using cleanup policies, document them clearly so other developers understand the entity lifecycle in your application.
|
||
|
|
||
|
6. **Use OnAdd for initialization**: The OnAdd hook is ideal for initializing component-specific resources.
|
||
|
|
||
|
7. **Use OnRemove for cleanup**: The OnRemove hook ensures resources are properly released when components are removed.
|
||
|
|
||
|
8. **Use OnSet for synchronization**: The OnSet hook helps keep different aspects of your game in sync when component values change.
|
||
|
|
||
|
## Example: Complete Entity Lifecycle
|
||
|
|
||
|
This example shows how to use all observer hooks together to manage an entity's lifecycle:
|
||
|
|
||
|
```lua
|
||
|
local world = jecs.World.new()
|
||
|
local pair = jecs.pair
|
||
|
|
||
|
-- Define components
|
||
|
local Model = world:component()
|
||
|
local Transform = world:component()
|
||
|
local ChildOf = world:component()
|
||
|
|
||
|
-- Set up component hooks
|
||
|
world:set(Model, jecs.OnAdd, function(entity)
|
||
|
print("Model added to entity", entity)
|
||
|
-- Create visual representation
|
||
|
end)
|
||
|
|
||
|
world:set(Model, jecs.OnRemove, function(entity)
|
||
|
print("Model removed from entity", entity)
|
||
|
-- Destroy visual representation
|
||
|
end)
|
||
|
|
||
|
world:set(Model, jecs.OnSet, function(entity, model)
|
||
|
print("Model set on entity", entity, "with value", model)
|
||
|
-- Update visual representation
|
||
|
end)
|
||
|
|
||
|
-- Set up cleanup policies
|
||
|
world:add(ChildOf, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||
|
world:add(Transform, pair(jecs.OnDelete, jecs.Remove))
|
||
|
|
||
|
-- Create entities
|
||
|
local parent = world:entity()
|
||
|
local child = world:entity()
|
||
|
|
||
|
-- Set up relationships and components
|
||
|
world:add(child, pair(ChildOf, parent))
|
||
|
world:set(child, Model, "cube")
|
||
|
world:set(child, Transform, {position = {x = 0, y = 0, z = 0}})
|
||
|
|
||
|
-- Updating a component triggers the OnSet hook
|
||
|
world:set(child, Model, "sphere")
|
||
|
|
||
|
-- Removing a component triggers the OnRemove hook
|
||
|
world:remove(child, Model)
|
||
|
|
||
|
-- Deleting the parent triggers the OnDeleteTarget cleanup policy
|
||
|
-- which automatically deletes the child
|
||
|
world:delete(parent)
|
||
|
```
|
||
|
|
||
|
By effectively using observer hooks and cleanup policies, you can create more maintainable and robust ECS-based applications with less manual resource management.
|