From 7b870e8f3c925561e4cbec3f5bd98170e9f54b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denizhan=20Dak=C4=B1l=C4=B1r?= Date: Tue, 25 Feb 2025 05:04:54 +0300 Subject: [PATCH] Add comprehensive documentation for jecs library --- README.md | 29 ++++ docs/api/jecs.md | 54 ++++++ docs/api/name.md | 130 +++++++++++++++ docs/api/world-entity-management.md | 215 ++++++++++++++++++++++++ docs/learn/faq/contributing.md | 85 +++++++++- docs/learn/faq/index.md | 94 +++++++++++ docs/learn/migration-from-matter.md | 218 ++++++++++++++++++++++++ docs/learn/observers.md | 246 ++++++++++++++++++++++++++++ mkdocs.yml | 56 +++++++ 9 files changed, 1125 insertions(+), 2 deletions(-) create mode 100644 docs/api/name.md create mode 100644 docs/api/world-entity-management.md create mode 100644 docs/learn/faq/index.md create mode 100644 docs/learn/migration-from-matter.md create mode 100644 docs/learn/observers.md diff --git a/README.md b/README.md index d44797c..4b3ccf5 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,32 @@ Can be found under /benches/visual/query.luau Inserting 8 components to an entity and updating them over 50 times. ![Insertions](assets/image-4.png) 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. diff --git a/docs/api/jecs.md b/docs/api/jecs.md index 75012f7..05e610c 100644 --- a/docs/api/jecs.md +++ b/docs/api/jecs.md @@ -16,6 +16,12 @@ jecs.Wildcard: Entity ``` 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 ```luau jecs.Component: Entity @@ -28,6 +34,54 @@ jecs.ChildOf: Entity ``` 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 ```luau jecs.Rest: Entity diff --git a/docs/api/name.md b/docs/api/name.md new file mode 100644 index 0000000..a02e62a --- /dev/null +++ b/docs/api/name.md @@ -0,0 +1,130 @@ +# Name Component + +The `Name` component allows you to associate a string identifier with an entity. + +## Overview + +```luau +jecs.Name: Entity +``` + +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. \ No newline at end of file diff --git a/docs/api/world-entity-management.md b/docs/api/world-entity-management.md new file mode 100644 index 0000000..96eea7d --- /dev/null +++ b/docs/api/world-entity-management.md @@ -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. \ No newline at end of file diff --git a/docs/learn/faq/contributing.md b/docs/learn/faq/contributing.md index 1346d9f..7783766 100644 --- a/docs/learn/faq/contributing.md +++ b/docs/learn/faq/contributing.md @@ -1,3 +1,84 @@ -## TODO +# Contributing to jecs -This is a TODO stub. \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/learn/faq/index.md b/docs/learn/faq/index.md new file mode 100644 index 0000000..cb32ed8 --- /dev/null +++ b/docs/learn/faq/index.md @@ -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. \ No newline at end of file diff --git a/docs/learn/migration-from-matter.md b/docs/learn/migration-from-matter.md new file mode 100644 index 0000000..cdcb7a9 --- /dev/null +++ b/docs/learn/migration-from-matter.md @@ -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. \ No newline at end of file diff --git a/docs/learn/observers.md b/docs/learn/observers.md new file mode 100644 index 0000000..88224cf --- /dev/null +++ b/docs/learn/observers.md @@ -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 = (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. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 1a47333..88a8029 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,8 +51,64 @@ extra_css: extra_javascript: - 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: - 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: - Get Started: tutorials/index.md - Installing Fusion: tutorials/get-started/installing-fusion.md