diff --git a/README.md b/README.md index d44797ce..0c35a6e9 100644 --- a/README.md +++ b/README.md @@ -4,61 +4,84 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE) [![Wally](https://img.shields.io/github/v/tag/ukendio/jecs?&style=for-the-badge)](https://wally.run/package/ukendio/jecs) -Just a stupidly fast Entity Component System +# Jecs - Just a Stupidly Fast ECS -- [Entity Relationships](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c) as first class citizens -- Iterate 800,000 entities at 60 frames per second -- Type-safe [Luau](https://luau-lang.org/) API -- Zero-dependency package -- Optimized for column-major operations -- Cache friendly [archetype/SoA](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) storage -- Rigorously [unit tested](https://github.com/Ukendio/jecs/actions/workflows/ci.yaml) for stability +A high-performance Entity Component System (ECS) for Roblox games. -### Example +## Features + +- πŸš€ **Blazing Fast**: Iterate over 800,000 entities at 60 frames per second +- πŸ”— **Entity Relationships**: First-class support for [entity relationships](docs/learn/concepts/relationships.md) +- πŸ”’ **Type Safety**: Fully typed API for both [Luau](https://luau-lang.org/) and TypeScript +- πŸ“¦ **Zero Dependencies**: No external dependencies required +- ⚑ **Optimized Storage**: Cache-friendly [archetype/SoA](https://ajmmertens.medium.com/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) storage +- βœ… **Battle-tested**: Rigorously [unit tested](https://github.com/Ukendio/jecs/actions/workflows/ci.yaml) for stability + +## Documentation + +- [Getting Started](docs/learn/overview/get-started.md) +- [API Reference](docs/api/jecs.md) +- [Concepts](docs/learn/concepts/) +- [Examples](examples/) +- [FAQ](docs/learn/faq/common-issues.md) + +## Quick Example ```lua local world = jecs.World.new() local pair = jecs.pair --- These components and functions are actually already builtin --- but have been illustrated for demonstration purposes -local ChildOf = world:component() -local Name = world:component() +-- Define components +local Position = world:component() :: jecs.Entity +local Velocity = world:component() :: jecs.Entity -local function parent(entity) - return world:target(entity, ChildOf) -end -local function getName(entity) - return world:get(entity, Name) +-- Create an entity +local entity = world:entity() +world:set(entity, Position, Vector3.new(0, 0, 0)) +world:set(entity, Velocity, Vector3.new(1, 0, 0)) + +-- Update system +for id, position, velocity in world:query(Position, Velocity) do + world:set(id, Position, position + velocity) end +``` -local alice = world:entity() -world:set(alice, Name, "alice") +## Performance -local bob = world:entity() -world:add(bob, pair(ChildOf, alice)) -world:set(bob, Name, "bob") +### Query Performance +21,000 entities, 125 archetypes, 4 random components queried: +![Queries](assets/image-3.png) -local sara = world:entity() -world:add(sara, pair(ChildOf, alice)) -world:set(sara, Name, "sara") +### Insertion Performance +Inserting 8 components to an entity and updating them over 50 times: +![Insertions](assets/image-4.png) -print(getName(parent(sara))) +## Installation -for e in world:query(pair(ChildOf, alice)) do - print(getName(e), "is the child of alice") -end +### Using Wally +```toml +[dependencies] +jecs = "ukendio/jecs@0.2.3" +``` --- Output --- "alice" --- bob is the child of alice --- sara is the child of alice +### Using npm (roblox-ts) +```bash +npm i @rbxts/jecs ``` -21,000 entities 125 archetypes 4 random components queried. -![Queries](assets/image-3.png) -Can be found under /benches/visual/query.luau +### Standalone +Download `jecs.rbxm` from our [releases page](https://github.com/Ukendio/jecs/releases). -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 +## Contributing + +We welcome contributions! Please see our [contribution guidelines](docs/contributing/guidelines.md) for details. + +## Community & Support + +- [Discord Community](https://discord.gg/h2NV8PqhAD) +- [GitHub Issues](https://github.com/ukendio/jecs/issues) +- [API Documentation](https://ukendio.github.io/jecs/) + +## License + +Jecs is [MIT licensed](LICENSE). diff --git a/docs/.vitepress/404.md b/docs/.vitepress/404.md new file mode 100644 index 00000000..647e51e7 --- /dev/null +++ b/docs/.vitepress/404.md @@ -0,0 +1,8 @@ +# Page Not Found + +Sorry, the page you're looking for doesn't exist. + +- [Go to Home](/) +- [View Documentation](/learn/overview/get-started) +- [API Reference](/api/jecs) +- [Report an Issue](https://github.com/ukendio/jecs/issues) \ No newline at end of file diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index dcd7c9d3..62070bb7 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -4,7 +4,7 @@ import { defineConfig } from 'vitepress' export default defineConfig({ title: "Jecs", base: "/jecs/", - description: "A VitePress Site", + description: "Just a stupidly fast Entity Component System", themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ @@ -16,11 +16,11 @@ export default defineConfig({ sidebar: { "/api/": [ { - text: "API reference", + text: "API Reference", items: [ - { text: "jecs", link: "/api/jecs" }, - { text: "World", link: "/api/world" }, - { text: "Query", link: "/api/query" } + { text: 'Jecs', link: '/api/jecs' }, + { text: 'World', link: '/api/world' }, + { text: 'Query', link: '/api/query' } ] } ], @@ -38,32 +38,41 @@ export default defineConfig({ { text: 'Entities and Components', link: '/learn/concepts/entities-and-components' }, { text: 'Queries', link: '/learn/concepts/queries' }, { text: 'Relationships', link: '/learn/concepts/relationships' }, - { text: 'Component Traits', link: 'learn/concepts/component-traits' }, + { text: 'Component Traits', link: '/learn/concepts/component-traits' }, { text: 'Addons', link: '/learn/concepts/addons' } ] }, { text: "FAQ", items: [ - { text: 'How can I contribute?', link: '/learn/faq/contributing' } + { text: 'Common Issues', link: '/learn/faq/common-issues' }, + { text: 'Migrating from Matter', link: '/learn/faq/migrating-from-matter' }, + { text: 'Contributing', link: '/learn/faq/contributing' } ] - }, - + } ], "/contributing/": [ { text: 'Contributing', items: [ - { text: 'Contribution Guidelines', link: '/learn/contributing/guidelines' }, - { text: 'Submitting Issues', link: '/learn/contributing/issues' }, - { text: 'Submitting Pull Requests', link: '/learn/contributing/pull-requests' }, + { text: 'Guidelines', link: '/contributing/guidelines' }, + { text: 'Submitting Issues', link: '/contributing/issues' }, + { text: 'Pull Requests', link: '/contributing/pull-requests' } ] } ] }, socialLinks: [ - { icon: 'github', link: 'https://github.com/ukendio/jecs' } - ] + { icon: 'github', link: 'https://github.com/ukendio/jecs' }, + { icon: 'discord', link: 'https://discord.gg/h2NV8PqhAD' } + ], + + search: { + provider: 'local', + options: { + detailedView: true + } + } } }) diff --git a/docs/api/jecs.md b/docs/api/jecs.md index 75012f79..f061548c 100644 --- a/docs/api/jecs.md +++ b/docs/api/jecs.md @@ -1,50 +1,74 @@ -# Jecs +# Jecs API Reference -Jecs. Just an Entity Component System. +Jecs provides a simple but powerful API for entity component systems. This page documents the core API. -# Properties +## Core Types -## World +### World ```luau jecs.World: World ``` -A world is a container of all ECS data. Games can have multiple worlds but component IDs may conflict between worlds. Ensure to register the same component IDs in the same order for each world. +The main container for all ECS data. See [World API](world.md) for details. -## Wildcard +### Entity ```luau -jecs.Wildcard: Entity +type Entity ``` -Builtin component type. This ID is used for wildcard queries. +A unique identifier that can have components attached. The generic type `T` represents the data type of the entity when used as a component. -## Component +### Id ```luau -jecs.Component: Entity +type Id ``` -Builtin component type. Every ID created with [world:component()](world.md#component()) has this type added to it. This is meant for querying every component ID. +Represents either an Entity or a Pair that can be used to store component data of type `T`. + +## Core Functions -## ChildOf +### pair() ```luau -jecs.ChildOf: Entity +function jecs.pair( + first: Entity, -- The first element of the pair (relationship) + object: Entity -- The second element of the pair (target) +): number -- Returns the ID representing this relationship pair ``` -Builtin component type. This ID is for creating parent-child hierarchies. +Creates a relationship pair between two entities. Used for creating relationships like parent-child, ownership, etc. -## Rest -```luau -jecs.Rest: Entity +::: info +Note that while relationship pairs can be used as components (meaning you can add data with them as an ID), they cannot be used as entities. You cannot add components to a pair as the source of a binding. +::: + +Example: +```lua +local ChildOf = world:component() +local parent = world:entity() +local child = world:entity() + +-- Create parent-child relationship +world:add(child, pair(ChildOf, parent)) ``` -# Functions +## Constants -## pair() +### Wildcard ```luau -function jecs.pair( - first: Entity, -- The first element of the pair, referred to as the relationship of the relationship pair. - object: Entity, -- The second element of the pair, referred to as the target of the relationship pair. -): number -- Returns the ID with those two elements +jecs.Wildcard: Entity +``` +Special entity used for querying any entity in a relationship. See [Relationships](../learn/concepts/relationships.md). +### Component +```luau +jecs.Component: Entity ``` -::: info +Built-in component type. Every component created with `world:component()` has this added to it. -Note that while relationship pairs can be used as components, meaning you can add data with it as an ID, however they cannot be used as entities. Meaning you cannot add components to a pair as the source of a binding. +### ChildOf +```luau +jecs.ChildOf: Entity +``` +Built-in relationship type for parent-child hierarchies. -::: +### Rest +```luau +jecs.Rest: Entity +``` +Special component used in queries to match remaining components. \ No newline at end of file diff --git a/docs/api/query.md b/docs/api/query.md index 5cc5ea33..5fca161c 100644 --- a/docs/api/query.md +++ b/docs/api/query.md @@ -1,87 +1,94 @@ -# Query +# Query API -A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components. +Queries allow you to efficiently find and iterate over entities that have a specific set of components. -# Methods - -## iter - -Returns an iterator that can be used to iterate over the query. +## Methods +### iter() ```luau function Query:iter(): () -> (Entity, ...) ``` -## with +Returns an iterator that yields matching entities and their component values. -Adds components (IDs) to query with, but will not use their data. This is useful for Tags or generally just data you do not care for. +Example: +::: code-group +```luau [luau] +for id, position, velocity in world:query(Position, Velocity):iter() do + -- Do something with position and velocity +end +``` +```typescript [typescript] +for (const [id, position, velocity] of world.query(Position, Velocity)) { + // Do something with position and velocity +} +``` +::: +### with() ```luau -function Query:with( - ...: Entity -- The IDs to query with -): Query +function Query:with(...: Entity): Query ``` +Adds components to filter by, but doesn't return their values in iteration. Useful for filtering by tags. + Example: ::: code-group - ```luau [luau] +-- Only get position for entities that also have Velocity for id, position in world:query(Position):with(Velocity) do - -- Do something + -- Do something with position end ``` - -```ts [typescript] +```typescript [typescript] for (const [id, position] of world.query(Position).with(Velocity)) { - // Do something + // Do something with position } ``` - -::: - -:::info -Put the IDs inside of `world:query()` instead if you need the data. ::: -## without - -Removes entities with the provided components from the query. - +### without() ```luau -function Query:without( - ...: Entity -- The IDs to filter against. -): Query -- Returns the Query +function Query:without(...: Entity): Query ``` -Example: +Excludes entities that have any of the specified components. +Example: ::: code-group - ```luau [luau] -for entity, position in world:query(Position):without(Velocity) do - -- Do something +-- Get position for entities that don't have Velocity +for id, position in world:query(Position):without(Velocity) do + -- Do something with position end ``` - -```ts [typescript] -for (const [entity, position] of world.query(Position).without(Velocity)) { - // Do something +```typescript [typescript] +for (const [id, position] of world.query(Position).without(Velocity)) { + // Do something with position } ``` - ::: -## archetypes +### cached() +```luau +function Query:cached(): Query +``` -Returns the matching archetypes of the query. +Returns a cached version of the query for better performance when using the same query multiple times. +### archetypes() ```luau function Query:archetypes(): { Archetype } ``` -Example: +Returns the matching archetypes for low-level query customization. -```luau [luau] +:::info +This method is for advanced users who need fine-grained control over query behavior at the archetype level. +::: + +Example: +```luau for i, archetype in world:query(Position, Velocity):archetypes() do local columns = archetype.columns local field = archetype.records @@ -96,15 +103,3 @@ for i, archetype in world:query(Position, Velocity):archetypes() do end end ``` - -:::info -This function is meant for people who want to really customize their query behaviour at the archetype-level -::: - -## cached - -Returns a cached version of the query. This is useful if you want to iterate over the same query multiple times. - -```luau -function Query:cached(): Query -- Returns the cached Query -``` diff --git a/docs/api/world.md b/docs/api/world.md index ddfaf7c3..cc097953 100644 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -1,35 +1,137 @@ -# World +# World API A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components and to perform different kinds of operations on them. -# Functions - -## new - -`World` utilizes a class, meaning JECS allows you to create multiple worlds. +## Constructor +### new() ```luau function World.new(): World ``` -Example: +Creates a new World instance. Multiple worlds can exist simultaneously, but component IDs may conflict between worlds. +Example: ::: code-group - ```luau [luau] local world = jecs.World.new() -local myOtherWorld = jecs.World.new() +local otherWorld = jecs.World.new() ``` +```typescript [typescript] +const world = new World(); +const otherWorld = new World(); +``` +::: -```ts [typescript] -import { World } from "@rbxts/jecs"; +## Entity Operations -const world = new World(); -const myOtherWorld = new World(); +### entity() +```luau +function World:entity(): Entity ``` +Creates a new entity with no components. + +Example: +::: code-group +```luau [luau] +local entity = world:entity() +``` +```typescript [typescript] +const entity = world.entity(); +``` +::: + +### component() +```luau +function World:component(): Entity +``` + +Creates a new component type. Components are entities that can have other components added to them. + +Example: +::: code-group +```luau [luau] +local Health = world:component() :: jecs.Entity +local Position = world:component() :: jecs.Entity +``` +```typescript [typescript] +const Health = world.component(); +const Position = world.component(); +``` ::: +### delete() +```luau +function World:delete(id: Entity) +``` + +Deletes an entity and all its components/relationships. + +## Component Operations + +### add() +```luau +function World:add(id: Entity, component: Id) +``` + +Adds a component to an entity without setting a value. Useful for tags or components with default values. + +### set() +```luau +function World:set(id: Entity, component: Id, value: U) +``` + +Sets a component's value on an entity. Will add the component if it doesn't exist. + +### get() +```luau +function World:get(entity: Entity, id: Entity): T? +``` + +Gets the value of a component on an entity. Returns nil if the component doesn't exist. + +### remove() +```luau +function World:remove(id: Entity, component: Id) +``` + +Removes a component from an entity. + +### clear() +```luau +function World:clear(id: Entity) +``` + +Removes all components from an entity without deleting the entity. + +## Relationship Operations + +### target() +```luau +function World:target(id: Entity, relation: Entity, index?: number): Entity? +``` + +Gets the target entity of a relationship. For example, getting the parent entity in a ChildOf relationship. + +## Query Operations + +### query() +```luau +function World:query(...: Entity): Query +``` + +Creates a new Query to find entities with specific components. See [Query API](query.md) for details. + +## Maintenance + +### cleanup() +```luau +function World:cleanup() +``` + +Cleans up the world by removing empty archetypes and rebuilding archetype collections. Helps maintain memory efficiency. + # Methods ## entity diff --git a/docs/contributing/guidelines.md b/docs/contributing/guidelines.md index 2e034280..8bacb43c 100644 --- a/docs/contributing/guidelines.md +++ b/docs/contributing/guidelines.md @@ -1,3 +1,91 @@ -## TODO +# Contributing to Jecs -This is a TODO stub. \ No newline at end of file +Thank you for your interest in contributing to Jecs! This document will help you get started with contributing to the project. + +## Code of Conduct + +We expect all contributors to follow our Code of Conduct. Please be respectful and professional in all interactions. + +## Ways to Contribute + +There are many ways to contribute to Jecs: + +1. **Bug Reports**: Report bugs through [GitHub Issues](https://github.com/ukendio/jecs/issues) +2. **Feature Requests**: Suggest new features or improvements +3. **Documentation**: Help improve or translate documentation +4. **Code Contributions**: Submit pull requests for bug fixes or features +5. **Examples**: Create example projects using Jecs + +## Development Setup + +1. Fork and clone the repository: +```bash +git clone https://github.com/your-username/jecs.git +cd jecs +``` + +2. Install dependencies: +```bash +# Using Wally (Luau) +wally install + +# Using npm (TypeScript) +npm install +``` + +3. Run tests: +```bash +# Luau tests +luau test/tests.luau +``` + +## Code Style + +- Follow existing code style and formatting +- Use clear, descriptive variable and function names +- Add comments for complex logic +- Include type annotations +- Write tests for new features + +## Commit Messages + +- Use clear, descriptive commit messages +- Start with a verb in present tense (e.g., "Add", "Fix", "Update") +- Reference issue numbers when applicable + +Example: +``` +Add relationship query caching (#123) + +- Implement query result caching for relationship queries +- Add cache invalidation on component changes +- Update documentation with caching examples +``` + +## Documentation + +When adding new features or making changes: + +1. Update relevant API documentation +2. Add examples demonstrating usage +3. Update type definitions if necessary +4. Consider adding performance benchmarks for significant changes + +## Testing + +- Add tests for new features +- Ensure all tests pass before submitting PR +- Include performance tests for performance-critical code +- Test both Luau and TypeScript implementations + +## Getting Help + +If you need help: + +- Join our [Discord server](https://discord.gg/h2NV8PqhAD) +- Ask questions in GitHub Discussions +- Check existing issues and documentation + +## License + +By contributing to Jecs, you agree that your contributions will be licensed under the MIT License. \ No newline at end of file diff --git a/docs/contributing/issues.md b/docs/contributing/issues.md index 2e034280..4e136991 100644 --- a/docs/contributing/issues.md +++ b/docs/contributing/issues.md @@ -1,3 +1,84 @@ -## TODO +# Submitting Issues -This is a TODO stub. \ No newline at end of file +When submitting issues for Jecs, please follow these guidelines to help us understand and address your concerns effectively. + +## Issue Types + +We use different templates for different types of issues: + +1. **Bug Reports**: For reporting bugs or unexpected behavior +2. **Feature Requests**: For suggesting new features or improvements +3. **Documentation**: For documentation-related issues + +## Before Submitting + +1. Search existing issues to avoid duplicates +2. Check the documentation to ensure the behavior isn't intended +3. Try the latest version to see if the issue persists + +## Bug Reports + +When submitting a bug report: + +1. Use the bug report template +2. Include a clear description of the problem +3. Provide reproduction steps +4. Include code examples +5. Specify your environment: + - Jecs version + - Roblox version + - Platform (Luau/TypeScript) + +Example bug report: +```markdown +**Description** +Query iterator crashes when using relationship components + +**Steps to Reproduce** +1. Create a world +2. Add relationship components +3. Query for relationships +4. Iterate results + +**Code Example** +```lua +local world = jecs.World.new() +local ChildOf = world:component() +-- ... rest of reproduction code +``` + +**Expected Behavior** +Query should iterate through all matching entities + +**Actual Behavior** +Query crashes with error: ... +``` + +## Feature Requests + +When requesting features: + +1. Use the feature request template +2. Explain the use case +3. Provide example usage +4. Consider implementation details +5. Discuss alternatives considered + +## Documentation Issues + +For documentation issues: + +1. Specify the affected documentation section +2. Explain what needs to be changed +3. Provide suggested improvements +4. Include examples if relevant + +## Labels + +Common issue labels: + +- `bug`: Bug reports +- `enhancement`: Feature requests +- `documentation`: Documentation issues +- `good first issue`: Good for newcomers +- `help wanted`: Extra attention needed \ No newline at end of file diff --git a/docs/contributing/pull-requests.md b/docs/contributing/pull-requests.md index 2e034280..d6a1763a 100644 --- a/docs/contributing/pull-requests.md +++ b/docs/contributing/pull-requests.md @@ -1,3 +1,103 @@ -## TODO +# Submitting Pull Requests -This is a TODO stub. \ No newline at end of file +This guide will help you submit effective pull requests to Jecs. + +## Before Creating a PR + +1. Create an issue for discussion if one doesn't exist +2. Fork the repository +3. Create a feature branch +4. Ensure tests pass +5. Update documentation + +## PR Guidelines + +### Branch Naming + +Use descriptive branch names: +- `feature/description` +- `fix/issue-description` +- `docs/update-section` + +Example: `feature/add-relationship-caching` + +### PR Description + +Use the PR template and include: + +1. Brief description of changes +2. Link to related issues +3. Breaking changes (if any) +4. Testing performed +5. Documentation updates + +Example: +```markdown +## Description +Add caching support for relationship queries + +Fixes #123 + +## Changes +- Implement query result caching +- Add cache invalidation +- Update documentation + +## Breaking Changes +None + +## Testing +- Added unit tests +- Performed performance benchmarks +- Tested with example project +``` + +### Code Review Process + +1. Submit draft PR early for feedback +2. Address review comments +3. Keep commits focused and clean +4. Rebase on main when needed +5. Ensure CI passes + +### Testing Requirements + +- Add/update unit tests +- Test both Luau and TypeScript +- Include performance tests if relevant +- Test documentation examples + +### Documentation + +Update documentation: + +1. API references +2. Examples +3. Type definitions +4. Performance notes + +### Final Checklist + +Before marking PR as ready: + +- [ ] Tests pass +- [ ] Documentation updated +- [ ] Code follows style guide +- [ ] Commits are clean +- [ ] PR description complete +- [ ] Changes reviewed locally + +## After Submission + +1. Respond to review comments +2. Keep PR updated with main +3. Help with integration testing +4. Update based on feedback + +## Getting Help + +Need help with your PR? + +- Ask in Discord +- Comment on the PR +- Tag maintainers if stuck \ No newline at end of file diff --git a/docs/learn/concepts/component-traits.md b/docs/learn/concepts/component-traits.md index 591883db..43581109 100644 --- a/docs/learn/concepts/component-traits.md +++ b/docs/learn/concepts/component-traits.md @@ -1,6 +1,111 @@ # Component Traits -Component traits are IDs and pairs that can be added to components to modify their behavior. Although it is possible to create custom traits, this manual only contains an overview of all builtin component traits supported by Jecs. +Component traits in Jecs allow you to modify component behavior through special IDs and pairs. They provide a way to configure how components interact with entities and the world. + +## Built-in Traits + +### Component +```lua +jecs.Component +``` +Identifies an ID as a component. Every component created with `world:component()` automatically has this trait. + +### Tag +```lua +jecs.Tag +``` +Marks a component as a tag that never contains data. This improves performance for structural changes. + +## Cleanup Traits + +Cleanup traits define what happens when entities used as components or relationship targets are deleted. + +### OnDelete +Specifies what happens when a component or relationship is deleted: + +::: code-group +```lua [luau] +local Archer = world:component() +world:add(Archer, pair(jecs.OnDelete, jecs.Remove)) + +local entity = world:entity() +world:add(entity, Archer) + +-- This will remove Archer from entity +world:delete(Archer) +``` +```typescript [typescript] +const Archer = world.component(); +world.add(Archer, pair(jecs.OnDelete, jecs.Remove)); + +const entity = world.entity(); +world.add(entity, Archer); + +// This will remove Archer from entity +world.delete(Archer); +``` +::: + +### OnDeleteTarget +Specifies what happens when a relationship target is deleted: + +```lua +local OwnedBy = world:component() +world:add(OwnedBy, pair(jecs.OnDeleteTarget, jecs.Remove)) + +local item = world:entity() +local player = world:entity() +world:add(item, pair(OwnedBy, player)) + +-- This will remove (OwnedBy, player) from item +world:delete(player) +``` + +## Cleanup Actions + +Two cleanup actions are available: + +1. **Remove**: Removes instances of the specified component/relationship (default) +2. **Delete**: Deletes all entities with the specified component/relationship + +## Example Usage + +### Tag Components +```lua +local IsEnemy = world:component() +world:add(IsEnemy, jecs.Tag) + +-- More efficient than storing a boolean +world:add(entity, IsEnemy) +``` + +### Cleanup Configuration +```lua +-- Delete children when parent is deleted +local ChildOf = world:component() +world:add(ChildOf, pair(jecs.OnDeleteTarget, jecs.Delete)) + +-- Remove ownership when owner is deleted +local OwnedBy = world:component() +world:add(OwnedBy, pair(jecs.OnDeleteTarget, jecs.Remove)) +``` + +## Best Practices + +1. **Use Tags Appropriately** + - Use tags for boolean-like components + - Configure tags early in component setup + - Document tag usage + +2. **Cleanup Configuration** + - Consider cleanup behavior during design + - Document cleanup policies + - Test cleanup behavior + +3. **Performance Considerations** + - Use tags for better performance + - Configure cleanup for efficient entity management + - Consider relationship cleanup impact # Component diff --git a/docs/learn/concepts/entities-and-components.md b/docs/learn/concepts/entities-and-components.md index 5e7e0e9d..e8689867 100644 --- a/docs/learn/concepts/entities-and-components.md +++ b/docs/learn/concepts/entities-and-components.md @@ -1,119 +1,138 @@ # Entities and Components -## Entities +## What are Entities? -Entities represent things in a game. In a game there may be entities of characters, buildings, projectiles, particle effects etc. +Entities are the fundamental building blocks in Jecs. An entity represents any object in your game - a character, a building, a projectile, or even abstract concepts like game rules or spawn points. -By itself, an entity is just an unique identifier without any data +By itself, an entity is just a unique identifier (a number) without any data. Entities become useful when you add components to them. -## Components +## What are Components? -A component is something that is added to an entity. Components can simply tag an entity ("this entity is an `Npc`"), attach data to an entity ("this entity is at `Position` `Vector3.new(10, 20, 30)`") and create relationships between entities ("bob `Likes` alice") that may also contain data ("bob `Eats` `10` apples"). +Components are reusable pieces of data that can be attached to entities. They serve three main purposes: -## Operations +1. **Data Storage**: Hold data for an entity (e.g., Position, Health) +2. **Tagging**: Mark an entity as having certain properties (e.g., IsPlayer, IsEnemy) +3. **Relationships**: Create connections between entities (e.g., ChildOf, Owns) -| Operation | Description | -| --------- | ---------------------------------------------------------------------------------------------- | -| `get` | Get a specific component or set of components from an entity. | -| `add` | Adds component to an entity. If entity already has the component, `add` does nothing. | -| `set` | Sets the value of a component for an entity. `set` behaves as a combination of `add` and `get` | -| `remove` | Removes component from entity. If entity doesn't have the component, `remove` does nothing. | -| `clear` | Remove all components from an entity. Clearing is more efficient than removing one by one. | +### Example Components +```lua +-- Data component +local Position = world:component() :: jecs.Entity +local Health = world:component() :: jecs.Entity + +-- Tag component +local IsEnemy = world:component() + +-- Relationship component +local ChildOf = world:component() +``` + +## Component Operations -## Components are entities +Jecs provides several operations for working with components: -In an ECS, components need to be uniquely identified. In Jecs this is done by making each component its own unique entity. If a game has a component Position and Velocity, there will be two entities, one for each component. Component entities can be distinguished from "regular" entities as they have a `Component` component. An example: +| Operation | Description | Example | +|-----------|------------------------------------------------------------|-| +| `add` | Adds a component to an entity (no value) | `world:add(entity, IsEnemy)` | +| `set` | Sets a component's value on an entity | `world:set(entity, Health, 100)` | +| `get` | Gets a component's value from an entity | `local health = world:get(entity, Health)` | +| `remove` | Removes a component from an entity | `world:remove(entity, IsEnemy)` | +| `clear` | Removes all components from an entity | `world:clear(entity)` | +### Example Usage ::: code-group +```lua [luau] +local world = jecs.World.new() -```luau [luau] +-- Create components local Position = world:component() :: jecs.Entity -print(world:has(Position, jecs.Component)) -``` +local Health = world:component() :: jecs.Entity +local IsEnemy = world:component() + +-- Create an entity +local enemy = world:entity() + +-- Add components and data +world:set(enemy, Position, Vector3.new(0, 0, 0)) +world:set(enemy, Health, 100) +world:add(enemy, IsEnemy) +-- Get component data +local pos = world:get(enemy, Position) +print(`Enemy position: {pos}`) + +-- Check if entity has component +if world:has(enemy, IsEnemy) then + print("This is an enemy!") +end +``` ```typescript [typescript] +const world = new World(); + +// Create components const Position = world.component(); -print(world.has(Position, jecs.Component)); -``` +const Health = world.component(); +const IsEnemy = world.component(); + +// Create an entity +const enemy = world.entity(); + +// Add components and data +world.set(enemy, Position, new Vector3(0, 0, 0)); +world.set(enemy, Health, 100); +world.add(enemy, IsEnemy); + +// Get component data +const pos = world.get(enemy, Position); +print(`Enemy position: ${pos}`); +// Check if entity has component +if (world.has(enemy, IsEnemy)) { + print("This is an enemy!"); +} +``` ::: -All of the APIs that apply to regular entities also apply to component entities. This means it is possible to contexualize components with logic by adding traits to components +## Components are Entities -::: code-group +In Jecs, components themselves are entities with a special `Component` component. This means you can add components to components! This enables powerful features like: -```luau [luau] +1. Adding metadata to components +2. Creating component hierarchies +3. Defining component relationships + +Example: +```lua +local Position = world:component() :: jecs.Entity local Networked = world:component() local Type = world:component() -local Name = world:component() -local Position = world:component() :: jecs.Entity + +-- Add metadata to Position component world:add(Position, Networked) -world:set(Position, Name, "Position") -world:set(Position, Type, { size = 12, type = "Vector3" } ) -- 12 bytes to represent a Vector3 - -for id, ty, name in world:query(Type, Name, Networked) do - local batch = {} - for entity, data in world:query(id) do - table.insert(batch, { entity = entity, data = data }) - end - -- entities are sized f64 - local packet = buffer.create(#batch * (8 + ty.size)) - local offset = 0 - for _, entityData in batch do - offset+=8 - buffer.writef64(packet, offset, entityData.entity) - if ty.type == "Vector3" then - local vec3 = entity.data :: Vector3 - offset += 4 - buffer.writei32(packet, offset, vec3.X) - offset += 4 - buffer.writei32(packet, offset, vec3.Y) - offset += 4 - buffer.writei32(packet, offset, vec3.Z) - end - end - - updatePositions:FireServer(packet) -end +world:set(Position, Type, { size = 12, type = "Vector3" }) ``` -```typescript [typescript] -const Networked = world.component(); -const Type = world.component(); -const Name = world.component(); -const Position = world.component(); -world.add(Position, Networked); -world.set(Position, Name, "Position"); -world.set(Position, Type, { size: 12, type: "Vector3" }); // 12 bytes to represent a Vector3 - -for (const [id, ty, name] of world.query(Type, Name, Networked)) { - const batch = new Array<{ entity: Entity; data: unknown }>(); - - for (const [entity, data] of world.query(id)) { - batch.push({ entity, data }); - } - // entities are sized f64 - const packet = buffer.create(batch.size() * (8 + ty.size)); - const offset = 0; - for (const [_, entityData] of batch) { - offset += 8; - buffer.writef64(packet, offset, entityData.entity); - if (ty.type == "Vector3") { - const vec3 = entity.data as Vector3; - offset += 4; - buffer.writei32(packet, offsetm, vec3.X); - offset += 4; - buffer.writei32(packet, offset, vec3.Y); - offset += 4; - buffer.writei32(packet, offset, vec3.Z); - } - } - - updatePositions.FireServer(packet); -} -``` +## Best Practices -::: +1. **Keep Components Simple** + - Components should store related data + - Avoid complex nested structures + - Use multiple components instead of one complex component + +2. **Use Tags Effectively** + - Tags are components without data + - Great for marking entity states or categories + - More efficient than components with boolean values + +3. **Component Naming** + - Use clear, descriptive names + - Consider using noun-based names for data (Position, Health) + - Consider using adjective-based names for tags (IsEnemy, IsDead) + +4. **Data Organization** + - Group related data in components + - Split unrelated data into separate components + - Use relationships for entity connections ## Singletons diff --git a/docs/learn/concepts/queries.md b/docs/learn/concepts/queries.md index 38bd20a2..9c57fcca 100644 --- a/docs/learn/concepts/queries.md +++ b/docs/learn/concepts/queries.md @@ -1,5 +1,147 @@ # Queries +Queries are the primary way to find and iterate over entities with specific components in Jecs. They provide an efficient way to process only the entities you care about. + +## Basic Queries + +A basic query finds all entities that have a specific set of components: + +::: code-group +```lua [luau] +local Position = world:component() :: jecs.Entity +local Velocity = world:component() :: jecs.Entity + +-- Find all entities with both Position and Velocity +for id, position, velocity in world:query(Position, Velocity) do + -- Update position based on velocity + world:set(id, Position, position + velocity) +end +``` +```typescript [typescript] +const Position = world.component(); +const Velocity = world.component(); + +// Find all entities with both Position and Velocity +for (const [id, position, velocity] of world.query(Position, Velocity)) { + // Update position based on velocity + world.set(id, Position, position.add(velocity)); +} +``` +::: + +## Query Filters + +Queries can be refined using filters to be more specific about what entities you want: + +### with() +Find entities that have additional components, but don't need their values: + +```lua +-- Find entities with Position that also have IsEnemy +for id, position in world:query(Position):with(IsEnemy) do + -- Process enemy positions +end +``` + +### without() +Exclude entities that have specific components: + +```lua +-- Find entities with Position that don't have IsDestroyed +for id, position in world:query(Position):without(IsDestroyed) do + -- Process active entities +end +``` + +## Query Performance + +Queries in Jecs are optimized for performance: + +1. **Archetype-based**: Entities are grouped by their component combinations +2. **Cache-friendly**: Components are stored in contiguous memory +3. **Zero Allocation**: Iteration doesn't allocate memory + +### Query Caching + +For frequently used queries, you can cache them to avoid rebuilding the query: + +```lua +-- Cache a commonly used query +local movementQuery = world:query(Position, Velocity):cached() + +-- Use the cached query +for id, position, velocity in movementQuery:iter() do + -- Process movement +end +``` + +## Advanced Query Features + +### Relationship Queries +Query entities based on their relationships: + +```lua +local ChildOf = world:component() + +-- Find all children of a parent +for child in world:query(pair(ChildOf, parent)) do + -- Process child entities +end + +-- Find entities with any parent +for child in world:query(pair(ChildOf, jecs.Wildcard)) do + local parent = world:target(child, ChildOf) + -- Process parent-child relationship +end +``` + +### Component Trait Queries +Find components with specific traits: + +```lua +-- Find all networked components +for component in world:query(jecs.Component):with(Networked) do + -- Process networked components +end +``` + +## Best Practices + +1. **Query Organization** + - Keep queries focused on related components + - Cache frequently used queries + - Consider splitting complex queries into simpler ones + +2. **Performance Optimization** + - Use `with()` for components you don't need values from + - Consider component order (most restrictive first) + - Cache queries used in hot paths + +3. **Query Design** + - Query only the components you need + - Use relationships for hierarchical queries + - Consider using tags for efficient filtering + +4. **Common Patterns** + ```lua + -- Movement system + for id, pos, vel in world:query(Position, Velocity):without(Frozen) do + world:set(id, Position, pos + vel) + end + + -- Damage system + for id, health in world:query(Health):with(TakingDamage) do + if health <= 0 then + world:add(id, IsDead) + end + end + + -- Cleanup system + for id in world:query(IsDead) do + world:delete(id) + end + ``` + ## Introductiuon Queries enable games to quickly find entities that satifies provided conditions. @@ -9,7 +151,7 @@ Jecs queries can do anything from returning entities that match a simple list of This manual contains a full overview of the query features available in Jecs. Some of the features of Jecs queries are: - Queries have support for relationships pairs which allow for matching against entity graphs without having to build complex data structures for it. -- Queries support filters such as [`query:with(...)`](../../api/query.md#with) if entities are required to have the components but you don’t actually care about components value. And [`query:without(...)`](../../api/query.md#without) which selects entities without the components. +- Queries support filters such as [`query:with(...)`](../../api/query.md#with) if entities are required to have the components but you don't actually care about components value. And [`query:without(...)`](../../api/query.md#without) which selects entities without the components. - Queries can be drained or reset on when called, which lets you choose iterator behaviour. - Queries can be called with any ID, including entities created dynamically, this is useful for pairs. - Queries are already fast but can be futher inlined via [`query:archetypes()`](../../api/query.md#archetypes) for maximum performance to eliminate function call overhead which is roughly 70-80% of the cost for iteration. diff --git a/docs/learn/concepts/relationships.md b/docs/learn/concepts/relationships.md index f9aff16e..8852904e 100644 --- a/docs/learn/concepts/relationships.md +++ b/docs/learn/concepts/relationships.md @@ -1,9 +1,114 @@ # Relationships -Relationships makes it possible to describe entity graphs natively in ECS. -Adding/removing relationships is similar to adding/removing regular components, with as difference that instead of a single component id, a relationship adds a pair of two things to an entity. In this pair, the first element represents the relationship (e.g. "Eats"), and the second element represents the relationship target (e.g. "Apples"). +Relationships in Jecs allow you to create connections between entities using pairs. This enables modeling hierarchies, ownership, and other entity associations. -Relationships can be used to describe many things, from hierarchies to inventory systems to trade relationships between players in a game. The following sections go over how to use relationships, and what features they support. +## Basic Relationships + +A relationship is created using pairs: + +::: code-group +```lua [luau] +local world = jecs.World.new() +local ChildOf = world:component() + +local parent = world:entity() +local child = world:entity() + +-- Create parent-child relationship +world:add(child, pair(ChildOf, parent)) +``` +```typescript [typescript] +const world = new World(); +const ChildOf = world.component(); + +const parent = world.entity(); +const child = world.entity(); + +// Create parent-child relationship +world.add(child, pair(ChildOf, parent)); +``` +::: + +## Relationship Types + +Jecs supports several types of relationships: + +### Parent-Child +Built-in `ChildOf` relationship for hierarchies: +```lua +-- Using built-in ChildOf +world:add(child, pair(jecs.ChildOf, parent)) + +-- Get parent +local parent = world:parent(child) +``` + +### Custom Relationships +Create your own relationship types: +```lua +local OwnedBy = world:component() +local Likes = world:component() + +-- Create relationships +world:add(item, pair(OwnedBy, player)) +world:add(bob, pair(Likes, alice)) +``` + +## Querying Relationships + +Find entities with specific relationships: + +```lua +-- Find all children of a parent +for child in world:query(pair(ChildOf, parent)) do + -- Process child +end + +-- Find entities with any parent +for child in world:query(pair(ChildOf, jecs.Wildcard)) do + local parent = world:target(child, ChildOf) + -- Process child and parent +end +``` + +## Relationship Data + +Relationships can store data: + +```lua +local Damage = world:component() + +-- Store damage amount in relationship +world:set(weapon, pair(Damage, target), 50) + +-- Get damage amount +local damage = world:get(weapon, pair(Damage, target)) +``` + +## Best Practices + +1. **Relationship Design** + - Use clear relationship names + - Consider relationship directionality + - Document relationship semantics + +2. **Performance** + - Cache frequently used relationship queries + - Be mindful of relationship complexity + - Use built-in relationships when possible + +3. **Common Patterns** + ```lua + -- Hierarchy traversal + for child in world:children(parent) do + -- Process children + end + + -- Relationship queries + for entity, damage in world:query(pair(Damage, target)) do + -- Process damage + end + ``` ## Definitions diff --git a/docs/learn/faq/common-issues.md b/docs/learn/faq/common-issues.md new file mode 100644 index 00000000..8068cf5e --- /dev/null +++ b/docs/learn/faq/common-issues.md @@ -0,0 +1,116 @@ +# Common Issues + +This guide covers common issues you might encounter when using Jecs and their solutions. + +## Performance Issues + +### Query Performance + +**Issue**: Queries are running slower than expected + +**Solutions**: +1. Cache frequently used queries: +```lua +local movementQuery = world:query(Position, Velocity):cached() +``` + +2. Use `with()` for components you don't need values from: +```lua +-- Instead of +for id, pos, _ in world:query(Position, IsEnemy) do end + +-- Use +for id, pos in world:query(Position):with(IsEnemy) do end +``` + +3. Minimize relationship complexity to reduce archetype fragmentation + +### Memory Usage + +**Issue**: High memory usage with many entities + +**Solutions**: +1. Use tags instead of boolean components +2. Call `world:cleanup()` periodically to remove empty archetypes +3. Consider component data structure size + +## Type Issues + +### Component Type Errors + +**Issue**: Type errors with components + +**Solutions**: +1. Always specify component types: +```lua +local Health = world:component() :: jecs.Entity +local Position = world:component() :: jecs.Entity +``` + +2. Use correct types in set operations: +```lua +-- Correct +world:set(entity, Position, Vector3.new(0, 0, 0)) + +-- Wrong +world:set(entity, Position, {x=0, y=0, z=0}) +``` + +## Relationship Issues + +### Missing Targets + +**Issue**: Cannot find relationship targets + +**Solutions**: +1. Use `world:target()` to get relationship targets: +```lua +local parent = world:target(entity, ChildOf) +``` + +2. Check relationship existence: +```lua +if world:has(entity, pair(ChildOf, parent)) then + -- Process relationship +end +``` + +### Cleanup Issues + +**Issue**: Entities not cleaning up properly + +**Solutions**: +1. Configure cleanup traits: +```lua +world:add(ChildOf, pair(jecs.OnDeleteTarget, jecs.Delete)) +``` + +2. Manually clean up relationships before deleting entities: +```lua +world:clear(entity) -- Remove all components +world:delete(entity) -- Then delete entity +``` + +## Best Practices + +### Query Organization +- Cache frequently used queries +- Use appropriate filters (`with`/`without`) +- Consider component order in queries + +### Component Design +- Keep components small and focused +- Use tags for boolean states +- Document component types + +### Memory Management +- Clean up unused entities +- Configure appropriate cleanup traits +- Monitor archetype fragmentation + +## Getting Help + +If you encounter issues not covered here: +1. Check the [API documentation](../../api/jecs.md) +2. Join our [Discord server](https://discord.gg/h2NV8PqhAD) +3. Open an issue on [GitHub](https://github.com/ukendio/jecs/issues) \ No newline at end of file diff --git a/docs/learn/faq/migrating-from-matter.md b/docs/learn/faq/migrating-from-matter.md new file mode 100644 index 00000000..4b2ad28e --- /dev/null +++ b/docs/learn/faq/migrating-from-matter.md @@ -0,0 +1,122 @@ +# Migrating from Matter + +This guide helps you migrate your code from Matter ECS to Jecs. + +## Key Differences + +### World Creation +```lua +-- Matter +local world = Matter.World.new() + +-- Jecs +local world = jecs.World.new() +``` + +### Component Definition +```lua +-- Matter +local Position = { name = "Position" } +local Velocity = { name = "Velocity" } + +-- Jecs +local Position = world:component() :: jecs.Entity +local Velocity = world:component() :: jecs.Entity +``` + +### Entity Creation +```lua +-- Matter +local entity = world:spawn() + +-- Jecs +local entity = world:entity() +``` + +### Adding Components +```lua +-- Matter +world:insert(entity, Position, { x = 0, y = 0, z = 0 }) + +-- Jecs +world:set(entity, Position, Vector3.new(0, 0, 0)) +``` + +### Querying +```lua +-- Matter +for id, pos, vel in world:query(Position, Velocity) do + -- Process entities +end + +-- Jecs +for id, pos, vel in world:query(Position, Velocity) do + -- Process entities +end +``` + +## Major Feature Differences + +### Relationships +Jecs provides built-in support for entity relationships: +```lua +-- Jecs only +world:add(child, pair(ChildOf, parent)) +``` + +### Component Traits +Jecs allows adding traits to components: +```lua +-- Jecs only +world:add(Position, Networked) +``` + +### Query Caching +Jecs provides explicit query caching: +```lua +-- Jecs only +local cachedQuery = world:query(Position, Velocity):cached() +``` + +## Migration Steps + +1. **Update Component Definitions** + - Replace Matter component tables with Jecs components + - Add type annotations for better type safety + +2. **Update Entity Management** + - Replace `spawn()` with `entity()` + - Update component insertion syntax + +3. **Update Queries** + - Review and update query usage + - Consider using query caching for performance + +4. **Add Relationships** + - Replace custom parent-child implementations with Jecs relationships + - Use built-in relationship features + +5. **Update Systems** + - Review system implementation patterns + - Consider using component traits for better organization + +## Performance Considerations + +1. **Query Performance** + - Cache frequently used queries + - Use appropriate filters + +2. **Component Storage** + - Use tags for boolean states + - Consider component data structure size + +3. **Relationship Usage** + - Be mindful of relationship complexity + - Use built-in relationships when possible + +## Getting Help + +Need help migrating? +- Join our [Discord server](https://discord.gg/h2NV8PqhAD) +- Check the [API documentation](../../api/jecs.md) +- Open an issue on [GitHub](https://github.com/ukendio/jecs/issues) \ No newline at end of file diff --git a/docs/learn/overview/first-jecs-project.md b/docs/learn/overview/first-jecs-project.md index 324d170a..bab3ea65 100644 --- a/docs/learn/overview/first-jecs-project.md +++ b/docs/learn/overview/first-jecs-project.md @@ -1,68 +1,210 @@ -# First Jecs project +# Your First Jecs Project -Now that you have installed Jecs, you can create your [World](https://ukendio.github.io/jecs/api/world.html). +This tutorial will walk you through creating your first project with Jecs, demonstrating core concepts and best practices. -:::code-group -```luau [luau] -local jecs = require(path/to/jecs) +## Setting Up + +First, make sure you have Jecs installed. If not, check the [Getting Started](get-started.md) guide. + +::: code-group +```lua [luau] +local jecs = require(path.to.jecs) local world = jecs.World.new() ``` ```typescript [typescript] -import { World } from "@rbxts/jecs" -const world = new World() +import { World } from "@rbxts/jecs"; +const world = new World(); ``` ::: -Let's create a couple components. +## Creating Components -:::code-group -```luau [luau] -local jecs = require(path/to/jecs) -local world = jecs.World.new() +Let's create some basic components for a simple game: -local Position = world:component() -local Velocity = world:component() -``` +::: code-group +```lua [luau] +-- Position in 3D space +local Position = world:component() :: jecs.Entity + +-- Velocity for movement +local Velocity = world:component() :: jecs.Entity + +-- Health for gameplay +local Health = world:component() :: jecs.Entity +-- Tag for marking enemies +local IsEnemy = world:component() +``` ```typescript [typescript] -import { World } from "@rbxts/jecs" -const world = new World() +// Position in 3D space +const Position = world.component(); + +// Velocity for movement +const Velocity = world.component(); + +// Health for gameplay +const Health = world.component(); -const Position = world.component() -const Velocity = world.component() +// Tag for marking enemies +const IsEnemy = world.component(); ``` ::: -Systems can be as simple as a query in a function or a more contextualized construct. Let's make a system that moves an entity and decelerates over time. +## Creating Game Systems -:::code-group -```luau [luau] -local jecs = require(path/to/jecs) -local world = jecs.World.new() +Let's create some basic systems to handle movement and gameplay: -local Position = world:component() -local Velocity = world:component() +### Movement System +::: code-group +```lua [luau] +-- Cache the query for better performance +local movementQuery = world:query(Position, Velocity):cached() -for id, position, velocity in world:query(Position, Velocity) do - world:set(id, Position, position + velocity) - world:set(id, Velocity, velocity * 0.9) +local function updateMovement(deltaTime) + for id, position, velocity in movementQuery:iter() do + -- Update position based on velocity and time + world:set(id, Position, position + velocity * deltaTime) + end end ``` +```typescript [typescript] +// Cache the query for better performance +const movementQuery = world.query(Position, Velocity).cached(); + +function updateMovement(deltaTime: number) { + for (const [id, position, velocity] of movementQuery) { + // Update position based on velocity and time + world.set(id, Position, position.add(velocity.mul(deltaTime))); + } +} +``` +::: +### Damage System +::: code-group +```lua [luau] +local function applyDamage(entity, amount) + local currentHealth = world:get(entity, Health) + if currentHealth then + local newHealth = currentHealth - amount + if newHealth <= 0 then + world:delete(entity) + else + world:set(entity, Health, newHealth) + end + end +end +``` ```typescript [typescript] -import { World } from "@rbxts/jecs" -const world = new World() +function applyDamage(entity: Entity, amount: number) { + const currentHealth = world.get(entity, Health); + if (currentHealth) { + const newHealth = currentHealth - amount; + if (newHealth <= 0) { + world.delete(entity); + } else { + world.set(entity, Health, newHealth); + } + } +} +``` +::: + +## Creating Game Entities -const Position = world.component() -const Velocity = world.component() +Now let's create some game entities: -for (const [id, position, velocity] of world.query(Position, Velocity)) { - world.set(id, Position, position.add(velocity)) - world.set(id, Velocity, velocity.mul(0.9)) +::: code-group +```lua [luau] +-- Create a player +local player = world:entity() +world:set(player, Position, Vector3.new(0, 0, 0)) +world:set(player, Velocity, Vector3.new(0, 0, 0)) +world:set(player, Health, 100) + +-- Create an enemy +local enemy = world:entity() +world:set(enemy, Position, Vector3.new(10, 0, 10)) +world:set(enemy, Health, 50) +world:add(enemy, IsEnemy) +``` +```typescript [typescript] +// Create a player +const player = world.entity(); +world.set(player, Position, new Vector3(0, 0, 0)); +world.set(player, Velocity, new Vector3(0, 0, 0)); +world.set(player, Health, 100); + +// Create an enemy +const enemy = world.entity(); +world.set(enemy, Position, new Vector3(10, 0, 10)); +world.set(enemy, Health, 50); +world.add(enemy, IsEnemy); +``` +::: + +## Adding Relationships + +Let's add some parent-child relationships: + +::: code-group +```lua [luau] +-- Create weapon entity +local weapon = world:entity() +world:add(weapon, pair(jecs.ChildOf, player)) + +-- Query for player's children +for child in world:query(pair(jecs.ChildOf, player)) do + print("Found player's child:", child) +end +``` +```typescript [typescript] +// Create weapon entity +const weapon = world.entity(); +world.add(weapon, pair(jecs.ChildOf, player)); + +// Query for player's children +for (const [child] of world.query(pair(jecs.ChildOf, player))) { + print("Found player's child:", child); } ``` ::: +## Running the Game Loop + +Here's how to put it all together: + +::: code-group +```lua [luau] +local RunService = game:GetService("RunService") + +RunService.Heartbeat:Connect(function(deltaTime) + -- Update movement + updateMovement(deltaTime) + + -- Other game systems... +end) +``` +```typescript [typescript] +const RunService = game.GetService("RunService"); + +RunService.Heartbeat.Connect((deltaTime) => { + // Update movement + updateMovement(deltaTime); + + // Other game systems... +}); +``` +::: + +## Next Steps + +1. Learn more about [Entities and Components](../concepts/entities-and-components.md) +2. Explore [Queries](../concepts/queries.md) in depth +3. Understand [Relationships](../concepts/relationships.md) +4. Check out [Component Traits](../concepts/component-traits.md) +5. Browse the [API Reference](../../api/jecs.md) + ## Where To Get Help If you are encountering problems, there are resources for you to get help: diff --git a/docs/learn/overview/get-started.md b/docs/learn/overview/get-started.md index e7714bbc..1a0e35fd 100644 --- a/docs/learn/overview/get-started.md +++ b/docs/learn/overview/get-started.md @@ -1,29 +1,29 @@ -# Getting Started +# Getting Started with Jecs -## Installation - -### Installing Standalone +This guide will help you get Jecs up and running in your project. -Navigate to the [releases page](https://github.com/Ukendio/jecs/releases) and download `jecs.rbxm` from the assets. - -![jecs.rbxm](rbxm.png) +## Installation -### Using Wally +Choose your preferred installation method: -Add the following to your wally configuration: +### Using Wally (Luau) +Add to your wally.toml: ::: code-group - ```toml [wally.toml] +[dependencies] jecs = "ukendio/jecs@0.2.3" ``` - ::: -### Using npm (roblox-ts) +Then run: +```bash +wally install +``` -Use one of the following commands on your root project directory: +### Using npm (roblox-ts) +Use your preferred package manager: ::: code-group ```bash [npm] npm i https://github.com/Ukendio/jecs.git @@ -34,97 +34,166 @@ yarn add https://github.com/Ukendio/jecs.git ```bash [pnpm] pnpm add https://github.com/Ukendio/jecs.git ``` - ::: -## Example Usage +### Standalone Installation + +1. Navigate to the [releases page](https://github.com/Ukendio/jecs/releases) +2. Download `jecs.rbxm` from the assets +3. Import into your Roblox project + +![jecs.rbxm](rbxm.png) + +## Basic Usage + +Here's a simple example to get you started: ::: code-group +```lua [luau] +local jecs = require(path.to.jecs) -```luau [Luau] +-- Create a world local world = jecs.World.new() -local pair = jecs.pair -local Wildcard = jecs.Wildcard -local Name = world:component() +-- Define components +local Position = world:component() :: jecs.Entity +local Velocity = world:component() :: jecs.Entity + +-- Create an entity +local entity = world:entity() -local function getName(e) - return world:get(e, Name) +-- Add components +world:set(entity, Position, Vector3.new(0, 0, 0)) +world:set(entity, Velocity, Vector3.new(1, 0, 0)) + +-- Update system +local function updatePositions() + for id, position, velocity in world:query(Position, Velocity) do + world:set(id, Position, position + velocity) + end end +``` +```typescript [typescript] +import { World } from "@rbxts/jecs"; + +// Create a world +const world = new World(); + +// Define components +const Position = world.component(); +const Velocity = world.component(); + +// Create an entity +const entity = world.entity(); + +// Add components +world.set(entity, Position, new Vector3(0, 0, 0)); +world.set(entity, Velocity, new Vector3(1, 0, 0)); +// Update system +function updatePositions() { + for (const [id, position, velocity] of world.query(Position, Velocity)) { + world.set(id, Position, position.add(velocity)); + } +} +``` +::: + +## Advanced Example: Relationships + +Here's an example showing Jecs' relationship features: + +::: code-group +```lua [luau] +local world = jecs.World.new() +local pair = jecs.pair +local Wildcard = jecs.Wildcard + +-- Define components +local Name = world:component() local Eats = world:component() --- Relationship objects +-- Create food types (components are entities!) local Apples = world:component() --- components are entities, so you can add components to components world:set(Apples, Name, "apples") local Oranges = world:component() world:set(Oranges, Name, "oranges") +-- Create entities with relationships local bob = world:entity() --- Pairs can be constructed from two entities - +world:set(bob, Name, "bob") world:set(bob, pair(Eats, Apples), 10) world:set(bob, pair(Eats, Oranges), 5) -world:set(bob, Name, "bob") local alice = world:entity() -world:set(alice, pair(Eats, Apples), 4) world:set(alice, Name, "alice") +world:set(alice, pair(Eats, Apples), 4) +-- Query relationships for id, amount in world:query(pair(Eats, Wildcard)) do - -- get the second target of the pair local food = world:target(id, Eats) - print(string.format("%s eats %d %s", getName(id), amount, getName(food))) + print(string.format("%s eats %d %s", + world:get(id, Name), + amount, + world:get(food, Name))) end -- Output: -- bob eats 10 apples --- bob eats 5 pears --- alice eats 4 apples +-- bob eats 5 oranges +-- alice eats 4 apples ``` +::: +## Key Features -```ts [Typescript] -import { Wildcard, pair, World } from "@rbxts/jecs" +### Entity Management +```lua +-- Create entities +local entity = world:entity() +-- Delete entities +world:delete(entity) -const world = new World() -const Name = world.component() -function getName(e) { - return world.get(e, Name) -} +-- Clear all components +world:clear(entity) +``` -const Eats = world.component() +### Component Operations +```lua +-- Add component (tag) +world:add(entity, IsEnemy) -// Relationship objects -const Apples = world.component() -// components are entities, so you can add components to components -world.set(Apples, Name, "apples") -const Oranges = world.component() -world.set(Oranges, Name, "oranges") +-- Set component value +world:set(entity, Health, 100) -const bob = world.entity() -// Pairs can be constructed from two entities +-- Get component value +local health = world:get(entity, Health) -world.set(bob, pair(Eats, Apples), 10) -world.set(bob, pair(Eats, Oranges), 5) -world.set(bob, Name, "bob") +-- Remove component +world:remove(entity, Health) +``` -const alice = world.entity() -world.set(alice, pair(Eats, Apples), 4) -world.set(alice, Name, "alice") +### Relationships +```lua +-- Create parent-child relationship +world:add(child, pair(jecs.ChildOf, parent)) -for (const [id, amount] of world.query(pair(Eats, Wildcard))) { - // get the second target of the pair - const food = world:target(id, Eats) - print(string.format("%s eats %d %s", getName(id), amount, getName(food))) -} +-- Query children +for child in world:query(pair(jecs.ChildOf, parent)) do + -- Process child entities +end +``` -// Output: -// bob eats 10 apples -// bob eats 5 pears -// alice eats 4 apples +## Next Steps -``` +1. Check out the [First Jecs Project](first-jecs-project.md) tutorial +2. Learn about [Entities and Components](../concepts/entities-and-components.md) +3. Explore [Queries](../concepts/queries.md) and [Relationships](../concepts/relationships.md) +4. Browse the [API Reference](../../api/jecs.md) + +## Getting Help +- Join our [Discord server](https://discord.gg/h2NV8PqhAD) +- Check the [FAQ](../faq/common-issues.md) +- Report issues on [GitHub](https://github.com/ukendio/jecs/issues) diff --git a/docs/public/robots.txt b/docs/public/robots.txt new file mode 100644 index 00000000..a30d6092 --- /dev/null +++ b/docs/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://ukendio.github.io/jecs/sitemap.xml \ No newline at end of file diff --git a/docs/public/sitemap.xml b/docs/public/sitemap.xml new file mode 100644 index 00000000..e7071999 --- /dev/null +++ b/docs/public/sitemap.xml @@ -0,0 +1,16 @@ + + + + https://ukendio.github.io/jecs/ + 1.0 + + + https://ukendio.github.io/jecs/learn/overview/get-started + 0.9 + + + https://ukendio.github.io/jecs/api/jecs + 0.8 + + + \ No newline at end of file