Compare commits

...

4 commits

Author SHA1 Message Date
Denizhan Dakılır
0760517078
Merge 7b870e8f3c into a703e2c361 2025-03-07 13:03:09 +10:00
lolmanurfunny
a703e2c361
Remove redundant check in entity_index_try_get_any (#203)
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-03-07 02:16:49 +01:00
Denizhan Dakılır
7b870e8f3c Add comprehensive documentation for jecs library 2025-02-25 05:04:54 +03:00
Denizhan Dakılır
e668371cc1 Start work on bounty 2025-02-25 04:37:22 +03:00
10 changed files with 1125 additions and 5 deletions

View file

@ -62,3 +62,32 @@ Can be found under /benches/visual/query.luau
Inserting 8 components to an entity and updating them over 50 times. Inserting 8 components to an entity and updating them over 50 times.
![Insertions](assets/image-4.png) ![Insertions](assets/image-4.png)
Can be found under /benches/visual/insertions.luau Can be found under /benches/visual/insertions.luau
## Installation
### Using Wally
Add jecs to your `wally.toml` file:
```toml
[dependencies]
jecs = "ukendio/jecs@0.1.0"
```
Then run `wally install` in your project directory.
### Manual Installation
Download the latest release from the [releases page](https://github.com/ukendio/jecs/releases).
## Documentation
For complete documentation, visit our [documentation site](https://ukendio.github.io/jecs/).
## Contributing
Contributions are welcome. Please feel free to submit a Pull Request.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

View file

@ -16,6 +16,12 @@ jecs.Wildcard: Entity
``` ```
Builtin component type. This ID is used for wildcard queries. Builtin component type. This ID is used for wildcard queries.
## w
```luau
jecs.w: Entity
```
An alias for `jecs.Wildcard`. This ID is used for wildcard queries, providing a shorter syntax in query operations.
## Component ## Component
```luau ```luau
jecs.Component: Entity jecs.Component: Entity
@ -28,6 +34,54 @@ jecs.ChildOf: Entity
``` ```
Builtin component type. This ID is for creating parent-child hierarchies. Builtin component type. This ID is for creating parent-child hierarchies.
## Name
```luau
jecs.Name: Entity
```
Builtin component type. This ID is used to associate a string name with an entity, typically for debugging or display purposes.
## OnAdd
```luau
jecs.OnAdd: Entity<(entity: Entity) -> ()>
```
Builtin component hook. When set on a component, the provided function is called whenever that component is added to an entity.
## OnRemove
```luau
jecs.OnRemove: Entity<(entity: Entity) -> ()>
```
Builtin component hook. When set on a component, the provided function is called whenever that component is removed from an entity.
## OnSet
```luau
jecs.OnSet: Entity<(entity: Entity, data: any) -> ()>
```
Builtin component hook. When set on a component, the provided function is called whenever that component's value is set or changed on an entity.
## OnDelete
```luau
jecs.OnDelete: Entity
```
Builtin component trait. Used with `pair()` to define behavior when a component or entity is deleted. Must be paired with an action component like `jecs.Delete` or `jecs.Remove`.
## OnDeleteTarget
```luau
jecs.OnDeleteTarget: Entity
```
Builtin component trait. Used with `pair()` to define behavior when a relationship target is deleted. Must be paired with an action component like `jecs.Delete` or `jecs.Remove`.
## Delete
```luau
jecs.Delete: Entity
```
Builtin action component used with `OnDelete` or `OnDeleteTarget` to specify that entities should be deleted in the cleanup process.
## Remove
```luau
jecs.Remove: Entity
```
Builtin action component used with `OnDelete` or `OnDeleteTarget` to specify that components should be removed in the cleanup process.
## Rest ## Rest
```luau ```luau
jecs.Rest: Entity jecs.Rest: Entity

130
docs/api/name.md Normal file
View file

@ -0,0 +1,130 @@
# Name Component
The `Name` component allows you to associate a string identifier with an entity.
## Overview
```luau
jecs.Name: Entity<string>
```
The `Name` component is a built-in component in jecs that stores a string value. It has no special behavior beyond storing a name.
## Usage
### Setting a Name
```luau
local world = jecs.World.new()
-- Create an entity
local entity = world:entity()
-- Assign a name to the entity
world:set(entity, jecs.Name, "Player")
```
### Getting an Entity's Name
```luau
-- Retrieve an entity's name
local name = world:get(entity, jecs.Name)
print("Entity name:", name) -- Outputs: "Entity name: Player"
```
### Finding Entities by Name
```luau
-- Iterate over all entities with the Name component
for entity, name in world:query(jecs.Name) do
if name == "Player" then
-- Found the entity named "Player"
-- Do something with entity...
end
end
```
### Using Names with Hierarchies
Names are particularly useful when working with entity hierarchies:
```luau
local world = jecs.World.new()
local pair = jecs.pair
-- Create a parent entity with a name
local parent = world:entity()
world:set(parent, jecs.Name, "Parent")
-- Create child entities with names
local child1 = world:entity()
world:set(child1, jecs.Name, "Child1")
world:add(child1, pair(jecs.ChildOf, parent))
local child2 = world:entity()
world:set(child2, jecs.Name, "Child2")
world:add(child2, pair(jecs.ChildOf, parent))
-- Print the hierarchy
print("Parent:", world:get(parent, jecs.Name))
for child in world:query(pair(jecs.ChildOf, parent)) do
print(" Child:", world:get(child, jecs.Name))
end
-- Output:
-- Parent: Parent
-- Child: Child1
-- Child: Child2
```
## Helper Function
For convenience, you can create a simple helper function to get an entity's name:
```luau
local function getName(world, entity)
return world:get(entity, jecs.Name) or tostring(entity)
end
-- Usage
local name = getName(world, entity)
```
This function will return the entity's name if it has one, or fall back to the entity ID as a string.
## Best Practices
1. **Use Consistently**: Apply names to key entities systematically, especially for important game objects.
2. **Be Descriptive**: Use meaningful names that describe the entity's role or purpose.
3. **Handle Missing Names**: Always check if an entity has a name before trying to use it, or use a helper function like the one above.
4. **Namespacing**: Consider using prefixes or namespaces for different systems (e.g., "UI:MainMenu", "Enemy:Boss").
5. **Performance**: Remember that adding a Name component to every entity adds memory overhead. Use judiciously in performance-critical applications.
## Example: Entity Debugging System
Here's a simple debugging system that uses the Name component:
```luau
local function debugEntity(world, entity)
local name = world:get(entity, jecs.Name) or "Unnamed"
print("Entity", entity, "(" .. name .. ")")
-- Print all components
for componentId in world:query(jecs.pair(jecs.Component, jecs.Wildcard)) do
if world:has(entity, componentId) then
local componentName = world:get(componentId, jecs.Name) or "Unknown"
local value = world:get(entity, componentId)
print(" Component:", componentName, "=", value)
end
end
end
-- Usage
debugEntity(world, player)
```
This debugging function prints an entity's name along with all its components, making it easier to inspect entities during development.

View file

@ -0,0 +1,215 @@
# Entity Management
This section covers methods for managing entities in the jecs World.
## delete
Deletes an entity (and its components/relationships) from the world entirely.
```luau
function World:delete(entity: Entity): void
```
### Parameters
| Name | Type | Description |
|------|------|-------------|
| entity | Entity | The entity to delete from the world |
### Behavior
1. Invokes any `OnRemove` hooks for all components on the entity
2. Triggers any cleanup actions based on `OnDelete` and `OnDeleteTarget` relationships
3. Removes the entity from all archetypes
4. Recycles the entity ID for future use
### Example
::: code-group
```luau [luau]
local world = jecs.World.new()
-- Create an entity
local entity = world:entity()
world:set(entity, Position, {x = 0, y = 0})
-- Delete the entity
world:delete(entity)
-- The entity no longer exists in the world
assert(not world:contains(entity))
```
```typescript [typescript]
import { World } from "@rbxts/jecs";
const world = new World();
// Create an entity
const entity = world.entity();
world.set(entity, Position, {x: 0, y: 0});
// Delete the entity
world.delete(entity);
// The entity no longer exists in the world
assert(!world.contains(entity));
```
:::
### Child Entity Deletion
When an entity has children (via the `ChildOf` relationship), deleting the parent can also delete the children if the appropriate cleanup policy is set:
```luau
-- Set up parent-child relationship
local ChildOf = world:component()
world:add(ChildOf, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
local parent = world:entity()
local child = world:entity()
-- Make child a child of parent
world:add(child, jecs.pair(ChildOf, parent))
-- Deleting parent will also delete child
world:delete(parent)
assert(not world:contains(child))
```
### Component Deletion
The `delete` method can also be used to delete a component definition:
```luau
local Temporary = world:component()
world:add(Temporary, jecs.pair(jecs.OnDelete, jecs.Remove))
-- Add Temporary to entities
local e1 = world:entity()
local e2 = world:entity()
world:add(e1, Temporary)
world:add(e2, Temporary)
-- Delete the component definition
world:delete(Temporary)
-- Temporary is removed from all entities
assert(not world:has(e1, Temporary))
assert(not world:has(e2, Temporary))
```
## remove
Removes a component from a given entity.
```luau
function World:remove(entity: Entity, component: Id): void
```
### Parameters
| Name | Type | Description |
|------|------|-------------|
| entity | Entity | The entity to modify |
| component | Id | The component or relationship to remove |
### Behavior
1. Invokes any `OnRemove` hook for the component being removed
2. Removes the component from the entity
3. May cause the entity to transition to another archetype
### Example
::: code-group
```luau [luau]
local world = jecs.World.new()
-- Create components
local Health = world:component()
local Shield = world:component()
-- Create an entity with both components
local entity = world:entity()
world:set(entity, Health, 100)
world:set(entity, Shield, 50)
-- Remove just the Shield component
world:remove(entity, Shield)
-- Entity still exists but no longer has Shield
assert(world:contains(entity))
assert(world:has(entity, Health))
assert(not world:has(entity, Shield))
```
```typescript [typescript]
import { World } from "@rbxts/jecs";
const world = new World();
// Create components
const Health = world.component();
const Shield = world.component();
// Create an entity with both components
const entity = world.entity();
world.set(entity, Health, 100);
world.set(entity, Shield, 50);
// Remove just the Shield component
world.remove(entity, Shield);
// Entity still exists but no longer has Shield
assert(world.contains(entity));
assert(world.has(entity, Health));
assert(!world.has(entity, Shield));
```
:::
### Removing Relationships
The `remove` method can also be used to remove relationship pairs:
```luau
local ChildOf = world:component()
local parent = world:entity()
local child = world:entity()
-- Establish parent-child relationship
world:add(child, jecs.pair(ChildOf, parent))
-- Remove the relationship
world:remove(child, jecs.pair(ChildOf, parent))
-- Child is no longer related to parent
assert(not world:has(child, jecs.pair(ChildOf, parent)))
```
## Differences Between delete and remove
| Method | What It Affects | Entity Existence | Cleanup Policies |
|--------|-----------------|------------------|------------------|
| delete | Entity and all its components | Entity no longer exists | Triggers all cleanup policies |
| remove | Only the specified component | Entity still exists | Only triggers component-specific OnRemove hook |
### When to Use Each Method
- Use `delete` when you want to completely remove an entity from the world
- Use `remove` when you want to keep the entity but remove specific components
- Use `delete` on a component definition to remove that component from all entities
### Performance Considerations
Both operations may cause archetype transitions, which have performance implications:
- `delete` typically has more overhead because it has to remove all components
- `remove` is generally faster for individual component removal
- Both methods are optimized in jecs to be as efficient as possible
For performance-critical code dealing with many entities, consider batching operations to minimize archetype transitions.

View file

@ -1,3 +1,84 @@
## TODO # Contributing to jecs
This is a TODO stub. This document provides guidelines for contributing to the project.
## Getting Started
1. Fork the repository
2. Clone your fork
3. Set up the development environment
4. Create a branch for your changes
## Development Setup
```bash
# Clone your fork
git clone https://github.com/your-username/jecs.git
cd jecs
# Install dependencies
wally install
```
## Making Changes
1. Create a branch:
```bash
git checkout -b feature/your-feature-name
```
2. Make your changes
3. Add tests if applicable
4. Run tests:
```bash
lune run test
```
5. Commit your changes:
```bash
git commit -m "Add feature: description"
```
## Submitting Changes
1. Push your changes:
```bash
git push origin feature/your-feature-name
```
2. Create a Pull Request
3. Explain the changes and reference related issues
## Code Style
- Follow the existing code style
- Use meaningful variable and function names
- Write clear comments for complex logic
- Keep functions focused on a single responsibility
## Testing
- Add tests for new features
- Ensure all tests pass before submitting
- Consider edge cases
## Documentation
- Update documentation for changed functionality
- Document new features or APIs
- Use clear and concise language
## Issue Reporting
1. Check if the issue already exists in the [Issues](https://github.com/ukendio/jecs/issues) section
2. Create a new issue with a descriptive title and detailed information
## Code of Conduct
- Be respectful in communications
- Provide constructive feedback
- Accept constructive criticism
- Focus on what is best for the community

94
docs/learn/faq/index.md Normal file
View file

@ -0,0 +1,94 @@
# Frequently Asked Questions
This section addresses common questions about jecs.
## General Questions
### What is jecs?
jecs is a high-performance Entity Component System (ECS) library for Luau/Roblox.
### How does jecs compare to other ECS libraries?
jecs uses an archetype-based storage system (SoA) which is more cache-friendly than traditional approaches. For a detailed comparison with Matter, see the [Migration from Matter](../migration-from-matter.md) guide.
### Can I use jecs outside of Roblox?
Yes, jecs can be used in any Lua/Luau environment as it has zero dependencies.
## Technical Questions
### How many entities can jecs handle?
jecs can handle up to 800,000 entities at 60 frames per second.
### What are archetypes?
Archetypes are groups of entities that have the exact same set of components. When you add or remove components from an entity, it moves to a different archetype.
### How do I optimize performance with jecs?
- Group entity operations to minimize archetype transitions
- Use cached queries for frequently accessed data
- Define components that are frequently queried together
- Use tags instead of empty tables
- Be mindful of archetype transitions
### What are relationship pairs?
Relationship pairs allow you to create connections between entities using the `pair()` function.
## Common Issues
### My entity disappeared after adding a component
Check if you have cleanup policies set up with `OnDelete` or `OnDeleteTarget`.
### Queries are returning unexpected results
Make sure you're querying for the exact components you need. Use modifiers like `.with()` or `.without()` to refine queries.
### Performance degrades over time
This could be due to:
- Memory leaks from not properly deleting entities
- Excessive archetype transitions
- Too many entities or components
- Inefficient queries
### How do I debug entity relationships?
Use the `Name` component to give entities meaningful names:
```lua
-- Print all parent-child relationships
for entity, target in world:query(jecs.pair(jecs.ChildOf, jecs.Wildcard)) do
print(world:get(entity, jecs.Name), "is a child of", world:get(target, jecs.Name))
end
```
## Best Practices
### When should I use component lifecycle hooks?
Use lifecycle hooks (`OnAdd`, `OnRemove`, `OnSet`) for:
- Initializing resources
- Cleaning up resources
- Reacting to data changes
### How should I structure my ECS code?
- Separate data (components) from behavior (systems)
- Keep components small and focused
- Design systems for specific component combinations
- Use relationships to model connections
- Document cleanup policies
### Should I use multiple worlds?
Multiple worlds are useful for:
- Separating client and server logic
- Creating isolated test environments
- Managing different game states
Component IDs may conflict between worlds if not registered in the same order.

View file

@ -0,0 +1,218 @@
# Migrating from Matter to jecs
This guide is intended to help developers migrate from the Matter ECS library to jecs.
## Key Differences
### Architectural Differences
- **Storage Implementation**: jecs uses an archetype-based storage system (SoA - Structure of Arrays). Matter uses a simpler component-based storage approach.
- **Performance**: jecs is designed with a focus on performance, particularly for large-scale systems with hundreds of thousands of entities.
- **Relationship Model**: jecs treats entity relationships as first-class citizens. Matter doesn't have built-in relationship features.
- **Memory Usage**: jecs typically uses less memory for the same number of entities and components due to its optimized storage structure.
### API Differences
#### World Creation and Management
**Matter:**
```lua
local Matter = require("Matter")
local world = Matter.World.new()
```
**jecs:**
```lua
local jecs = require("jecs")
local world = jecs.World.new()
```
#### Component Definition
**Matter:**
```lua
local Position = Matter.component("Position")
local Velocity = Matter.component("Velocity")
```
**jecs:**
```lua
local Position = world:component()
local Velocity = world:component()
```
#### Entity Creation
**Matter:**
```lua
local entity = world:spawn(
Position({x = 0, y = 0}),
Velocity({x = 10, y = 5})
)
```
**jecs:**
```lua
local entity = world:entity()
world:set(entity, Position, {x = 0, y = 0})
world:set(entity, Velocity, {x = 10, y = 5})
```
#### Queries
**Matter:**
```lua
for id, position, velocity in world:query(Position, Velocity) do
-- Update position based on velocity
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
end
```
**jecs:**
```lua
for entity, position, velocity in world:query(Position, Velocity) do
-- Update position based on velocity
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
end
```
#### System Definition
**Matter:**
```lua
local function movementSystem(world)
for id, position, velocity in world:query(Position, Velocity) do
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
end
end
```
**jecs:**
```lua
local function movementSystem(world, dt)
for entity, position, velocity in world:query(Position, Velocity) do
position.x = position.x + velocity.x * dt
position.y = position.y + velocity.y * dt
end
end
```
#### Entity Removal
**Matter:**
```lua
world:despawn(entity)
```
**jecs:**
```lua
world:delete(entity)
```
#### Component Removal
**Matter:**
```lua
world:remove(entity, Position)
```
**jecs:**
```lua
world:remove(entity, Position)
```
### jecs-Specific Features
#### Relationships
jecs has built-in support for entity relationships:
```lua
local ChildOf = world:component()
local Name = world:component()
local parent = world:entity()
world:set(parent, Name, "Parent")
local child = world:entity()
world:add(child, jecs.pair(ChildOf, parent))
world:set(child, Name, "Child")
-- Query for all children of parent
for e in world:query(jecs.pair(ChildOf, parent)) do
local name = world:get(e, Name)
print(name, "is a child of Parent")
end
```
#### Wildcards
jecs supports wildcard queries for relationships:
```lua
-- Query for all parent-child relationships
for entity, target in world:query(jecs.pair(ChildOf, jecs.Wildcard)) do
print(world:get(entity, Name), "is a child of", world:get(target, Name))
end
-- Alternative using the shorter 'w' alias
for entity, target in world:query(jecs.pair(ChildOf, jecs.w)) do
print(world:get(entity, Name), "is a child of", world:get(target, Name))
end
```
#### Observer Hooks
jecs provides component lifecycle hooks:
```lua
world:set(Position, jecs.OnAdd, function(entity)
print("Position added to entity", entity)
end)
world:set(Position, jecs.OnRemove, function(entity)
print("Position removed from entity", entity)
end)
world:set(Position, jecs.OnSet, function(entity, value)
print("Position set on entity", entity, "with value", value)
end)
```
#### Automatic Cleanup with OnDelete and OnDeleteTarget
jecs offers automated cleanup of related entities:
```lua
-- When a parent is deleted, all its children will be deleted too
world:add(ChildOf, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
-- When Health component is deleted, remove the entity's Shield component
world:add(Health, jecs.pair(jecs.OnDelete, jecs.Remove))
world:add(Shield, jecs.pair(jecs.OnDelete, jecs.Remove))
```
## Performance Considerations
When migrating from Matter to jecs, consider these performance tips:
1. **Batch Entity Operations**: Group entity operations when possible to minimize archetype transitions.
2. **Use Cached Queries**: For frequently used queries, create cached versions.
3. **Consider Component Layout**: Components frequently queried together should be defined together.
4. **Use Tags When Appropriate**: For components with no data, use tags instead of empty tables.
5. **Be Aware of Archetype Transitions**: Adding/removing components causes archetype transitions, which have performance implications for large-scale operations.
## Migration Strategy
1. **Start with World Creation**: Replace Matter's world creation with jecs.
2. **Migrate Component Definitions**: Update component definitions to use jecs's approach.
3. **Update Entity Creation**: Modify entity spawning code to use jecs's entity and set methods.
4. **Adapt Queries**: Update queries, noting that iteration patterns are similar.
5. **Implement Relationships**: Take advantage of jecs's relationship features where applicable.
6. **Add Observer Hooks**: Implement lifecycle hooks to replace custom event handling in Matter.
7. **Optimize**: Refine your implementation using jecs-specific features for better performance.
By following this guide, you should be able to migrate your Matter ECS application to jecs while taking advantage of jecs's enhanced performance and features.

246
docs/learn/observers.md Normal file
View file

@ -0,0 +1,246 @@
# 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.

View file

@ -172,9 +172,6 @@ end
local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record? local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record?
local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
if not r then
return nil
end
if not r or r.dense == 0 then if not r or r.dense == 0 then
return nil return nil

View file

@ -51,8 +51,64 @@ extra_css:
extra_javascript: extra_javascript:
- assets/scripts/smooth-scroll.js - assets/scripts/smooth-scroll.js
plugins:
- search
- mike:
version_selector: true
canonical_version: latest
- mkdocstrings:
default_handler: python
handlers:
python:
import:
- https://installer.github.io/docs/python-objects.inv
- https://docs.python.org/3/objects.inv
selection:
members: true
rendering:
show_source: true
show_submodules: true
paths: [src]
nav: nav:
- Home: index.md - Home: index.md
- Learn:
- learn/index.md
- Getting Started:
- Installation: learn/getting-started/installation.md
- Quick start: learn/getting-started/quick-start.md
- Concepts:
- Entities & Components: learn/concepts/entities-components.md
- Queries: learn/concepts/queries.md
- Component Traits: learn/concepts/component-traits.md
- Command line: learn/concepts/command-line.md
- Addons: learn/concepts/addons.md
- Guides:
- learn/guides/index.md
- Hello, World: learn/guides/hello-world.md
- Reading Queries: learn/guides/reading-queries.md
- Pair Rules: learn/guides/pair-rules.md
- Tagging: learn/guides/tagging.md
- Removing Components: learn/guides/removing-components.md
- Bulk Operations: learn/guides/bulk.md
- Bulk Queries: learn/guides/bulk-queries.md
- Debugging: learn/guides/debugging.md
- Naming: learn/guides/naming.md
- Building Applications: learn/guides/building-applications.md
- Troubleshoot: learn/guides/troubleshoot.md
- Migration from Matter: learn/migration-from-matter.md
- Observer APIs: learn/observers.md
- FAQ: learn/faq/index.md
- API:
- api/jecs.md
- World: api/world.md
- Query: api/query.md
- Entity Management: api/world-entity-management.md
- Name Component: api/name.md
- Contributing:
- contributing/index.md
- Roadmap: contributing/roadmap.md
- How to Contribute: learn/faq/contributing.md
- Tutorials: - Tutorials:
- Get Started: tutorials/index.md - Get Started: tutorials/index.md
- Installing Fusion: tutorials/get-started/installing-fusion.md - Installing Fusion: tutorials/get-started/installing-fusion.md