mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Compare commits
5 commits
4336e65633
...
a3939cf083
Author | SHA1 | Date | |
---|---|---|---|
|
a3939cf083 | ||
|
9d83c3bc13 | ||
|
e073a570b7 | ||
|
7b870e8f3c | ||
|
e668371cc1 |
15 changed files with 3656 additions and 598 deletions
29
README.md
29
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.
|
||||

|
||||
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.
|
||||
|
|
|
@ -28,22 +28,22 @@ local B6 = ecr.component()
|
|||
local B7 = ecr.component()
|
||||
local B8 = ecr.component()
|
||||
|
||||
local C1 = ecs:entity()
|
||||
local C2 = ecs:entity()
|
||||
local C3 = ecs:entity()
|
||||
local C4 = ecs:entity()
|
||||
local C5 = ecs:entity()
|
||||
local C6 = ecs:entity()
|
||||
local C7 = ecs:entity()
|
||||
local C8 = ecs:entity()
|
||||
local E1 = mcs:entity()
|
||||
local E2 = mcs:entity()
|
||||
local E3 = mcs:entity()
|
||||
local E4 = mcs:entity()
|
||||
local E5 = mcs:entity()
|
||||
local E6 = mcs:entity()
|
||||
local E7 = mcs:entity()
|
||||
local E8 = mcs:entity()
|
||||
local C1 = ecs:component()
|
||||
local C2 = ecs:component()
|
||||
local C3 = ecs:component()
|
||||
local C4 = ecs:component()
|
||||
local C5 = ecs:component()
|
||||
local C6 = ecs:component()
|
||||
local C7 = ecs:component()
|
||||
local C8 = ecs:component()
|
||||
local E1 = mcs:component()
|
||||
local E2 = mcs:component()
|
||||
local E3 = mcs:component()
|
||||
local E4 = mcs:component()
|
||||
local E5 = mcs:component()
|
||||
local E6 = mcs:component()
|
||||
local E7 = mcs:component()
|
||||
local E8 = mcs:component()
|
||||
|
||||
local registry2 = ecr.registry()
|
||||
return {
|
||||
|
@ -52,48 +52,30 @@ return {
|
|||
end,
|
||||
|
||||
Functions = {
|
||||
Matter = function()
|
||||
local e = newWorld:spawn()
|
||||
Mirror = function()
|
||||
local e = mcs:entity()
|
||||
for i = 1, 5000 do
|
||||
newWorld:insert(
|
||||
e,
|
||||
A1({ value = true }),
|
||||
A2({ value = true }),
|
||||
A3({ value = true }),
|
||||
A4({ value = true }),
|
||||
A5({ value = true }),
|
||||
A6({ value = true }),
|
||||
A7({ value = true }),
|
||||
A8({ value = true })
|
||||
)
|
||||
mcs:set(e, E1, false)
|
||||
mcs:set(e, E2, false)
|
||||
mcs:set(e, E3, false)
|
||||
mcs:set(e, E4, false)
|
||||
mcs:set(e, E5, false)
|
||||
mcs:set(e, E6, false)
|
||||
mcs:set(e, E7, false)
|
||||
mcs:set(e, E8, false)
|
||||
end
|
||||
end,
|
||||
|
||||
ECR = function()
|
||||
local e = registry2.create()
|
||||
for i = 1, 5000 do
|
||||
registry2:set(e, B1, { value = false })
|
||||
registry2:set(e, B2, { value = false })
|
||||
registry2:set(e, B3, { value = false })
|
||||
registry2:set(e, B4, { value = false })
|
||||
registry2:set(e, B5, { value = false })
|
||||
registry2:set(e, B6, { value = false })
|
||||
registry2:set(e, B7, { value = false })
|
||||
registry2:set(e, B8, { value = false })
|
||||
end
|
||||
end,
|
||||
|
||||
Jecs = function()
|
||||
local e = ecs:entity()
|
||||
for i = 1, 5000 do
|
||||
ecs:set(e, C1, { value = false })
|
||||
ecs:set(e, C2, { value = false })
|
||||
ecs:set(e, C3, { value = false })
|
||||
ecs:set(e, C4, { value = false })
|
||||
ecs:set(e, C5, { value = false })
|
||||
ecs:set(e, C6, { value = false })
|
||||
ecs:set(e, C7, { value = false })
|
||||
ecs:set(e, C8, { value = false })
|
||||
ecs:set(e, C1, false)
|
||||
ecs:set(e, C2, false)
|
||||
ecs:set(e, C3, false)
|
||||
ecs:set(e, C4, false)
|
||||
ecs:set(e, C5, false)
|
||||
ecs:set(e, C6, false)
|
||||
ecs:set(e, C7, false)
|
||||
ecs:set(e, C8, false)
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
|
49
benches/visual/remove.bench.luau
Normal file
49
benches/visual/remove.bench.luau
Normal file
|
@ -0,0 +1,49 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||
local jecs = require(ReplicatedStorage.Lib)
|
||||
local pair = jecs.pair
|
||||
local ecs = jecs.World.new()
|
||||
local mirror = require(ReplicatedStorage.mirror)
|
||||
local mcs = mirror.World.new()
|
||||
|
||||
local C1 = ecs:component()
|
||||
local C2 = ecs:entity()
|
||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
local C3 = ecs:entity()
|
||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
local C4 = ecs:entity()
|
||||
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
local E1 = mcs:component()
|
||||
local E2 = mcs:entity()
|
||||
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
local E3 = mcs:entity()
|
||||
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
local E4 = mcs:entity()
|
||||
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||
|
||||
return {
|
||||
ParameterGenerator = function()
|
||||
end,
|
||||
|
||||
Functions = {
|
||||
Mirror = function()
|
||||
local m = mcs:entity()
|
||||
for i = 1, 100 do
|
||||
mcs:add(m, E3)
|
||||
mcs:remove(m, E3)
|
||||
end
|
||||
end,
|
||||
|
||||
Jecs = function()
|
||||
local j = ecs:entity()
|
||||
for i = 1, 100 do
|
||||
ecs:add(j, C3)
|
||||
ecs:remove(j, C3)
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
|
@ -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
|
||||
|
|
130
docs/api/name.md
Normal file
130
docs/api/name.md
Normal 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.
|
215
docs/api/world-entity-management.md
Normal file
215
docs/api/world-entity-management.md
Normal 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.
|
|
@ -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
94
docs/learn/faq/index.md
Normal 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.
|
218
docs/learn/migration-from-matter.md
Normal file
218
docs/learn/migration-from-matter.md
Normal 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
246
docs/learn/observers.md
Normal 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.
|
134
jecs.luau
134
jecs.luau
|
@ -90,60 +90,35 @@ local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
|
|||
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
|
||||
local EcsRest = HI_COMPONENT_ID + 14
|
||||
|
||||
local ECS_PAIR_FLAG = 0x8
|
||||
local ECS_ID_FLAGS_MASK = 0x10
|
||||
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||
|
||||
local ECS_ID_DELETE = 0b0000_0001
|
||||
local ECS_ID_IS_TAG = 0b0000_0010
|
||||
local ECS_ID_HAS_ON_ADD = 0b0000_0100
|
||||
local ECS_ID_HAS_ON_SET = 0b0000_1000
|
||||
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
|
||||
local ECS_ID_MASK = 0b0000_0000
|
||||
-- stylua: ignore end
|
||||
local NULL_ARRAY = table.freeze({}) :: Column
|
||||
|
||||
local function FLAGS_ADD(is_pair: boolean): number
|
||||
local flags = 0x0
|
||||
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||
|
||||
if is_pair then
|
||||
flags = bit32.bor(flags, ECS_PAIR_FLAG) -- HIGHEST bit in the ID.
|
||||
end
|
||||
if false then
|
||||
flags = bit32.bor(flags, 0x4) -- Set the second flag to true
|
||||
end
|
||||
if false then
|
||||
flags = bit32.bor(flags, 0x2) -- Set the third flag to true
|
||||
end
|
||||
if false then
|
||||
flags = bit32.bor(flags, 0x1) -- LAST BIT in the ID.
|
||||
end
|
||||
local NULL_ARRAY = table.freeze({})
|
||||
|
||||
return flags
|
||||
end
|
||||
|
||||
local function ECS_COMBINE(source: number, target: number): i53
|
||||
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
|
||||
|
||||
local function ECS_COMBINE(id: number, generation: number): i53
|
||||
return id + (generation * ECS_ENTITY_MASK)
|
||||
end
|
||||
local ECS_PAIR_OFFSET = 2^48
|
||||
|
||||
local function ECS_IS_PAIR(e: number): boolean
|
||||
return if e > ECS_ENTITY_MASK then (e % ECS_ID_FLAGS_MASK) // ECS_PAIR_FLAG ~= 0 else false
|
||||
end
|
||||
|
||||
-- HIGH 24 bits LOW 24 bits
|
||||
local function ECS_GENERATION(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
|
||||
return e > ECS_PAIR_OFFSET
|
||||
end
|
||||
|
||||
local function ECS_GENERATION_INC(e: i53)
|
||||
if e > ECS_ENTITY_MASK then
|
||||
local flags = e // ECS_ID_FLAGS_MASK
|
||||
local id = flags // ECS_ENTITY_MASK
|
||||
local generation = flags % ECS_GENERATION_MASK
|
||||
local id = e % ECS_ENTITY_MASK
|
||||
local generation = e // ECS_ENTITY_MASK
|
||||
|
||||
local next_gen = generation + 1
|
||||
if next_gen > ECS_GENERATION_MASK then
|
||||
if next_gen >= ECS_GENERATION_MASK then
|
||||
return id
|
||||
end
|
||||
|
||||
|
@ -152,22 +127,23 @@ local function ECS_GENERATION_INC(e: i53)
|
|||
return ECS_COMBINE(e, 1)
|
||||
end
|
||||
|
||||
-- FIRST gets the high ID
|
||||
local function ECS_ENTITY_T_HI(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_ENTITY_MASK else e
|
||||
end
|
||||
|
||||
-- SECOND
|
||||
local function ECS_ENTITY_T_LO(e: i53): i24
|
||||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
||||
return e % ECS_ENTITY_MASK
|
||||
end
|
||||
|
||||
local function _STRIP_GENERATION(e: i53): i24
|
||||
return ECS_ENTITY_T_LO(e)
|
||||
local function ECS_GENERATION(e: i53)
|
||||
return e // ECS_ENTITY_MASK
|
||||
end
|
||||
|
||||
local function ECS_ENTITY_T_HI(e: i53): i24
|
||||
return e // ECS_ENTITY_MASK
|
||||
end
|
||||
|
||||
local function ECS_PAIR(pred: i53, obj: i53): i53
|
||||
return ECS_COMBINE(ECS_ENTITY_T_LO(pred), ECS_ENTITY_T_LO(obj)) + FLAGS_ADD(--[[isPair]] true) :: i53
|
||||
pred %= ECS_ENTITY_MASK
|
||||
obj %= ECS_ENTITY_MASK
|
||||
|
||||
return obj + (pred * 2^24) + ECS_PAIR_OFFSET
|
||||
end
|
||||
|
||||
local function entity_index_try_get_any(entity_index: EntityIndex, entity: number): Record?
|
||||
|
@ -236,14 +212,14 @@ local function entity_index_new_id(entity_index: EntityIndex): i53
|
|||
return id
|
||||
end
|
||||
|
||||
-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits
|
||||
local function ecs_pair_first(world, e)
|
||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_LO(e))
|
||||
local pred = (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK
|
||||
return entity_index_get_alive(world.entity_index, pred)
|
||||
end
|
||||
|
||||
-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits
|
||||
local function ecs_pair_second(world, e)
|
||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_HI(e))
|
||||
local obj = (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK
|
||||
return entity_index_get_alive(world.entity_index, obj)
|
||||
end
|
||||
|
||||
local function query_match(query, archetype: Archetype)
|
||||
|
@ -476,8 +452,8 @@ local function world_target(world: World, entity: i53, relation: i24, index: num
|
|||
return nil
|
||||
end
|
||||
|
||||
if nth >= count then
|
||||
nth = nth + count + 1
|
||||
if nth > count then
|
||||
nth = nth + count
|
||||
end
|
||||
|
||||
local tr = idr.cache[archetype_id]
|
||||
|
@ -860,16 +836,16 @@ local function world_set(world: World, entity: i53, id: i53, data: unknown): ()
|
|||
end
|
||||
end
|
||||
|
||||
local on_add = idr_hooks.on_add
|
||||
if on_add then
|
||||
on_add(entity)
|
||||
end
|
||||
|
||||
local tr = to.records[id]
|
||||
local column = to.columns[tr]
|
||||
|
||||
column[record.row] = data
|
||||
|
||||
local on_add = idr_hooks.on_add
|
||||
if on_add then
|
||||
on_add(entity)
|
||||
end
|
||||
|
||||
local on_set = idr_hooks.on_set
|
||||
if on_set then
|
||||
on_set(entity, data)
|
||||
|
@ -899,15 +875,16 @@ local function world_remove(world: World, entity: i53, id: i53)
|
|||
if not from then
|
||||
return
|
||||
end
|
||||
local to = archetype_traverse_remove(world, id, from)
|
||||
|
||||
if from ~= to then
|
||||
if from.records[id] then
|
||||
local idr = world.component_index[id]
|
||||
local on_remove = idr.hooks.on_remove
|
||||
if on_remove then
|
||||
on_remove(entity)
|
||||
end
|
||||
|
||||
local to = archetype_traverse_remove(world, id, record.archetype)
|
||||
|
||||
entity_move(entity_index, entity, record, to)
|
||||
end
|
||||
end
|
||||
|
@ -2413,51 +2390,44 @@ export type World = {
|
|||
observable: any,
|
||||
|
||||
--- Creates a new entity
|
||||
entity: (self: World) -> Entity,
|
||||
entity: (self: World, id: Entity?) -> Entity,
|
||||
--- Creates a new entity located in the first 256 ids.
|
||||
--- These should be used for static components for fast access.
|
||||
component: <T>(self: World) -> Entity<T>,
|
||||
--- Gets the target of an relationship. For example, when a user calls
|
||||
--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
|
||||
target: <T, U>(self: World, id: Entity<T>, relation: Entity<U>, index: number?) -> Entity?,
|
||||
target: (self: World, id: Entity, relation: Id, index: number?) -> Entity?,
|
||||
--- Deletes an entity and all it's related components and relationships.
|
||||
delete: <T>(self: World, id: Entity<T>) -> (),
|
||||
delete: (self: World, id: Entity) -> (),
|
||||
|
||||
--- Adds a component to the entity with no value
|
||||
add: <T, U>(self: World, id: Entity<T>, component: Id<U>) -> (),
|
||||
add: <T>(self: World, id: Entity, component: Id) -> (),
|
||||
--- Assigns a value to a component on the given entity
|
||||
set: <T, U>(self: World, id: Entity<T>, component: Id<U>, data: U) -> (),
|
||||
set: <T>(self: World, id: Entity, component: Id<T>, data: T) -> (),
|
||||
|
||||
cleanup: (self: World) -> (),
|
||||
-- Clears an entity from the world
|
||||
clear: <T>(self: World, id: Entity<T>) -> (),
|
||||
clear: (self: World, id: Entity) -> (),
|
||||
--- Removes a component from the given entity
|
||||
remove: <T, U>(self: World, id: Entity<T>, component: Id<U>) -> (),
|
||||
remove: (self: World, id: Entity, component: Id) -> (),
|
||||
--- Retrieves the value of up to 4 components. These values may be nil.
|
||||
get: (<T, A>(self: World, id: Entity<T>, Id<A>) -> A?)
|
||||
& (<T, A, B>(self: World, id: Entity<T>, Id<A>, Id<B>) -> (A?, B?))
|
||||
& (<T, A, B, C>(self: World, id: Entity<T>, Id<A>, Id<B>, Id<C>) -> (A?, B?, C?))
|
||||
& <T, A, B, C, D>(self: World, id: Entity<T>, Id<A>, Id<B>, Id<C>, Id<D>) -> (A?, B?, C?, D?),
|
||||
get: (<A>(self: World, id: Entity, Id<A>) -> A?)
|
||||
& (<A, B>(self: World, id: Entity, Id<A>, Id<B>) -> (A?, B?))
|
||||
& (<A, B, C>(self: World, id: Entity, Id<A>, Id<B>, Id<C>) -> (A?, B?, C?))
|
||||
& <A, B, C, D>(self: World, id: Entity, Id<A>, Id<B>, Id<C>, Id<D>) -> (A?, B?, C?, D?),
|
||||
|
||||
--- Returns whether the entity has the ID.
|
||||
has: (<T, U>(self: World, entity: Entity<T>, ...Id<U>) -> boolean)
|
||||
& (<T, U, V>(self: World, entity: Entity<T>, Id<U>, Id<V>) -> boolean)
|
||||
& (<T, U, V, W>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>) -> boolean)
|
||||
& (<T, U, V, W, X>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>) -> boolean)
|
||||
& (<T, U, V, W, X, Y>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>) -> boolean)
|
||||
& (<T, U, V, W, X, Y, Z>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>) -> boolean)
|
||||
& (<T, U, V, W, X, Y, Z, A>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>, Id<A>) -> boolean)
|
||||
& (<T, U, V, W, X, Y, Z, A>(self: World, entity: Entity<T>, Id<U>, Id<V>, Id<W>, Id<X>, Id<Y>, Id<Z>, Id<A>, ...unknown) -> boolean),
|
||||
has: (self: World, entity: Entity, ...Id) -> boolean,
|
||||
|
||||
--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
|
||||
parent: <T>(self: World, entity: Entity<T>) -> Entity,
|
||||
parent:(self: World, entity: Entity) -> Entity,
|
||||
|
||||
--- Checks if the world contains the given entity
|
||||
contains: <T>(self: World, entity: Entity<T>) -> boolean,
|
||||
contains:(self: World, entity: Entity) -> boolean,
|
||||
|
||||
each: <T>(self: World, id: Id<T>) -> () -> Entity,
|
||||
each: (self: World, id: Id) -> () -> Entity,
|
||||
|
||||
children: <T>(self: World, id: Id<T>) -> () -> Entity,
|
||||
children: (self: World, id: Id) -> () -> Entity,
|
||||
|
||||
--- Searches the world for entities that match a given query
|
||||
query: (<A>(World, Id<A>) -> Query<A>)
|
||||
|
|
2785
mirror.luau
2785
mirror.luau
File diff suppressed because it is too large
Load diff
56
mkdocs.yml
56
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
|
||||
|
|
|
@ -148,22 +148,24 @@ TEST("#repro", function()
|
|||
local e1 = world:entity()
|
||||
local e2 = world:entity()
|
||||
local e3 = world:entity()
|
||||
print(e1, e2, e3)
|
||||
setAttacksAndEats(e3, e1)
|
||||
setAttacksAndEats(e3, e2)
|
||||
setAttacksAndEats(e1, e2)
|
||||
print("---------------- delete e2 ---------------")
|
||||
local d = dwi(world)
|
||||
for archetype_id in world.component_index[pair(jecs.Wildcard, e2)].cache do
|
||||
local archetype = world.archetypes[archetype_id].type
|
||||
testkit.print(archetype)
|
||||
end
|
||||
world:delete(e2)
|
||||
print("-----------------------------")
|
||||
testkit.print(d.tbl(e1).types)
|
||||
-- testkit.print(d.tbl(e3).types)
|
||||
-- testkit.print(getTargets(Attacks))
|
||||
-- testkit.print(getTargets(Eats))
|
||||
local types1 = { pair(Attacks, e1), pair(Eats, e1) }
|
||||
table.sort(types1)
|
||||
|
||||
|
||||
CHECK(d.tbl(e1).type == "")
|
||||
CHECK(d.tbl(e3).type == table.concat(types1, "_"))
|
||||
|
||||
for _, entity in getTargets(Attacks) do
|
||||
CHECK(entity == e1)
|
||||
end
|
||||
for _, entity in getTargets(Eats) do
|
||||
CHECK(entity == e1)
|
||||
end
|
||||
end)
|
||||
|
||||
TEST("archetype", function()
|
||||
|
@ -235,6 +237,9 @@ TEST("world:cleanup()", function()
|
|||
CHECK(#archetype_index["1_2_3"].entities == 1)
|
||||
end)
|
||||
|
||||
local pe = require("@tools/entity_visualiser").prettify
|
||||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
||||
|
||||
TEST("world:entity()", function()
|
||||
do
|
||||
CASE("unique IDs")
|
||||
|
@ -1370,10 +1375,15 @@ TEST("world:target", function()
|
|||
world:add(e, pair(C, D))
|
||||
|
||||
CHECK(pair(A, B) < pair(A, C))
|
||||
CHECK(pair(A, E) < pair(B, C))
|
||||
CHECK(pair(A, C) < pair(A, D))
|
||||
CHECK(pair(C, A) < pair(C, D))
|
||||
|
||||
local records = debug_world_inspect(world).records(e)
|
||||
CHECK(jecs.pair_first(world, pair(B, C)) == B)
|
||||
local r = jecs.entity_index_try_get(world.entity_index, e)
|
||||
local archetype = r.archetype
|
||||
local counts = archetype.counts
|
||||
CHECK(counts[pair(A, __)] == 4)
|
||||
CHECK(records[pair(B, C)] > records[pair(A, E)])
|
||||
CHECK(world:target(e, A, 0) == B)
|
||||
CHECK(world:target(e, A, 1) == C)
|
||||
|
@ -1383,6 +1393,28 @@ TEST("world:target", function()
|
|||
CHECK(world:target(e, B, 1) == D)
|
||||
CHECK(world:target(e, C, 0) == D)
|
||||
CHECK(world:target(e, C, 1) == nil)
|
||||
|
||||
-- for id in archetype.records do
|
||||
-- local f = world:get(ecs_pair_first(world, id), jecs.Name)
|
||||
-- local s = world:get(ecs_pair_second(world, id), jecs.Name)
|
||||
-- print(`({f}, {s})`)
|
||||
-- end
|
||||
--
|
||||
|
||||
CHECK(archetype.records[pair(A, B)] == 1)
|
||||
CHECK(archetype.records[pair(A, C)] == 2)
|
||||
CHECK(archetype.records[pair(A, D)] == 3)
|
||||
CHECK(archetype.records[pair(A, E)] == 4)
|
||||
-- print("(A, B)", archetype.records[pair(A, B)])
|
||||
-- print("(A, C)", archetype.records[pair(A, C)])
|
||||
-- print("(A, D)", archetype.records[pair(A, D)])
|
||||
-- print("(A, E)", archetype.records[pair(A, E)])
|
||||
|
||||
-- print(pair(A, D), pair(B, C))
|
||||
-- print("(B, C)", archetype.records[pair(B, C)])
|
||||
|
||||
CHECK(world:target(e, C, 0) == D)
|
||||
CHECK(world:target(e, C, 1) == nil)
|
||||
end
|
||||
|
||||
do
|
||||
|
@ -1498,7 +1530,7 @@ TEST("Hooks", function()
|
|||
world:set(e, A, true)
|
||||
world:remove(e, A)
|
||||
CHECK(not world:get(e, A))
|
||||
CHECK(not world:get(e, B))
|
||||
CHECK(world:get(e, B))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
@ -1566,18 +1598,18 @@ TEST("repro", function()
|
|||
do CASE "#1"
|
||||
local world = world_new()
|
||||
local reproEntity = world:component()
|
||||
local components = { Cooldown = world:component() :: jecs.Id<number> }
|
||||
local components = { Cooldown = world:component() :: jecs.Entity<number> }
|
||||
world:set(reproEntity, components.Cooldown, 2)
|
||||
|
||||
local function updateCooldowns(dt: number)
|
||||
local toRemove = {}
|
||||
|
||||
for id, cooldown in world:query(components.Cooldown):iter() do
|
||||
local it = world:query(components.Cooldown):iter()
|
||||
for id, cooldown in it do
|
||||
cooldown -= dt
|
||||
|
||||
if cooldown <= 0 then
|
||||
table.insert(toRemove, id)
|
||||
print("removing")
|
||||
-- world:remove(id, components.Cooldown)
|
||||
else
|
||||
world:set(id, components.Cooldown, cooldown)
|
||||
|
@ -1626,6 +1658,10 @@ TEST("wildcard query", function()
|
|||
|
||||
local entity = world:entity()
|
||||
|
||||
local p = pair(Relation, A)
|
||||
CHECK(jecs.pair_first(world, p) == Relation)
|
||||
CHECK(jecs.pair_second(world, p) == A)
|
||||
local w = dwi(world)
|
||||
world:add(entity, pair(Relation, A))
|
||||
|
||||
local counter = 0
|
||||
|
|
|
@ -45,7 +45,10 @@ local function lifetime_tracker_add(world: jecs.World, opt)
|
|||
padding_enabled = opt.padding_enabled
|
||||
|
||||
local world_entity = world.entity
|
||||
w.entity = function(self)
|
||||
w.entity = function(self, entity)
|
||||
if entity then
|
||||
return world_entity(world, entity)
|
||||
end
|
||||
local will_recycle = entity_index.max_id ~= entity_index.alive_count
|
||||
local e = world_entity(world)
|
||||
if will_recycle then
|
||||
|
@ -108,7 +111,7 @@ local function lifetime_tracker_add(world: jecs.World, opt)
|
|||
local entity = dense_array[i]
|
||||
local id = ECS_ID(entity)
|
||||
local status = "alive"
|
||||
if id > alive_count then
|
||||
if not world:contains(entity) then
|
||||
status = "dead"
|
||||
end
|
||||
data[id] = status
|
||||
|
|
Loading…
Reference in a new issue