From 5a13bbb760c0533e4892664ee138238ddd648b17 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Wed, 11 Jun 2025 00:53:32 +0200 Subject: [PATCH] Delete luau specific files --- .github/workflows/analysis.yaml | 19 - .github/workflows/dependabot.yml | 11 - .github/workflows/deploy-docs.yaml | 64 - .github/workflows/publish-npm.yml | 17 - .github/workflows/release.yaml | 71 - .github/workflows/unit-testing.yaml | 31 - .luaurc | 10 - README.md | 40 +- docs/.vitepress/config.mts | 50 - docs/api/jecs.md | 50 - docs/api/query.md | 110 - docs/api/world.md | 505 --- docs/index.md | 29 - docs/learn/contributing/coverage.md | 17 - docs/learn/contributing/guidelines.md | 21 - docs/learn/contributing/issues.md | 24 - docs/learn/contributing/pull-requests.md | 77 - docs/learn/overview.md | 697 ---- docs/learn/public/jecs_logo.svg | 41 - docs/public/coverage/ansi.luau.html | 69 - .../coverage/entity_visualiser.luau.html | 74 - docs/public/coverage/index.html | 12 - docs/public/coverage/jecs.luau.html | 2798 ----------------- .../coverage/lifetime_tracker.luau.html | 254 -- docs/public/coverage/testkit.luau.html | 617 ---- docs/public/coverage/tests.luau.html | 2044 ------------ docs/public/jecs_logo.svg | 41 - docs/resources.md | 78 - 28 files changed, 4 insertions(+), 7867 deletions(-) delete mode 100644 .github/workflows/analysis.yaml delete mode 100644 .github/workflows/dependabot.yml delete mode 100644 .github/workflows/deploy-docs.yaml delete mode 100644 .github/workflows/publish-npm.yml delete mode 100644 .github/workflows/release.yaml delete mode 100644 .github/workflows/unit-testing.yaml delete mode 100644 .luaurc delete mode 100644 docs/.vitepress/config.mts delete mode 100644 docs/api/jecs.md delete mode 100644 docs/api/query.md delete mode 100644 docs/api/world.md delete mode 100644 docs/index.md delete mode 100644 docs/learn/contributing/coverage.md delete mode 100644 docs/learn/contributing/guidelines.md delete mode 100644 docs/learn/contributing/issues.md delete mode 100644 docs/learn/contributing/pull-requests.md delete mode 100644 docs/learn/overview.md delete mode 100644 docs/learn/public/jecs_logo.svg delete mode 100644 docs/public/coverage/ansi.luau.html delete mode 100644 docs/public/coverage/entity_visualiser.luau.html delete mode 100644 docs/public/coverage/index.html delete mode 100644 docs/public/coverage/jecs.luau.html delete mode 100644 docs/public/coverage/lifetime_tracker.luau.html delete mode 100644 docs/public/coverage/testkit.luau.html delete mode 100644 docs/public/coverage/tests.luau.html delete mode 100644 docs/public/jecs_logo.svg delete mode 100644 docs/resources.md diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml deleted file mode 100644 index 9535921..0000000 --- a/.github/workflows/analysis.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: analysis - -on: [push, pull_request, workflow_dispatch] - -jobs: - run: - name: Run Luau Analyze - runs-on: ubuntu-latest - - steps: - - name: Checkout Project - uses: actions/checkout@v4 - - - name: Install Luau - uses: encodedvenom/install-luau@v4.3 - - - name: Analyze - run: | - output=$(luau-analyze src || true) # Suppress errors for now. diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml deleted file mode 100644 index 17f20b6..0000000 --- a/.github/workflows/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: npm - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/deploy-docs.yaml b/.github/workflows/deploy-docs.yaml deleted file mode 100644 index 41c68dc..0000000 --- a/.github/workflows/deploy-docs.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# Sample workflow for building and deploying a VitePress site to GitHub Pages -# -name: deploy-docs - -on: - # Runs on pushes targeting the `main` branch. Change this to `master` if you're - # using the `master` branch as the default branch. - push: - branches: [main] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: pages - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Not needed if lastUpdated is not enabled - # - uses: pnpm/action-setup@v3 # Uncomment this if you're using pnpm - # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm # or pnpm / yarn - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Install dependencies - run: npm ci # or pnpm install / yarn install / bun install - - name: Build with VitePress - run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/.vitepress/dist - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - name: Deploy - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml deleted file mode 100644 index bce339d..0000000 --- a/.github/workflows/publish-npm.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: publish-npm - -on: - push: - branches: [main] - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 - with: - node-version: "20" - - uses: JS-DevTools/npm-publish@v3 - with: - token: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index a423627..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: release - -on: - push: - tags: ["v*"] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout Project - uses: actions/checkout@v4 - - - name: Install Rokit - uses: CompeyDev/setup-rokit@v0.1.2 - - - name: Install Dependencies - run: wally install - - - name: Build - run: rojo build --output build.rbxm default.project.json - - - name: Upload Build Artifact - uses: actions/upload-artifact@v3 - with: - name: build - path: build.rbxm - - release: - name: Release - needs: [build] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout Project - uses: actions/checkout@v4 - - - name: Download Jecs Build - uses: actions/download-artifact@v3 - with: - name: build - path: build - - - name: Rename Build - run: mv build/build.rbxm jecs.rbxm - - - name: Create Release - uses: softprops/action-gh-release@v1 - with: - name: Jecs ${{ github.ref_name }} - files: | - jecs.rbxm - - publish: - name: Publish - needs: [release] - runs-on: ubuntu-latest - steps: - - name: Checkout Project - uses: actions/checkout@v4 - - - name: Install Rokit - uses: CompeyDev/setup-rokit@v0.1.2 - - - name: Wally Login - run: wally login --token ${{ secrets.WALLY_AUTH_TOKEN }} - - - name: Publish - run: wally publish diff --git a/.github/workflows/unit-testing.yaml b/.github/workflows/unit-testing.yaml deleted file mode 100644 index da2664a..0000000 --- a/.github/workflows/unit-testing.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: unit-testing - -on: [push, pull_request, workflow_dispatch] - -jobs: - run: - name: Run Luau Tests - runs-on: ubuntu-latest - timeout-minutes: 2 - - steps: - - name: Checkout Project - uses: actions/checkout@v4 - - - name: Install Luau - uses: encodedvenom/install-luau@v4.3 - with: - version: "0.667" - verbose: "true" - - - name: Run Unit Tests - id: run_tests - run: | - output=$(luau test/tests.luau) - echo "$output" - if [[ "$output" == *"0 fails"* ]]; then - echo "Unit Tests Passed" - else - echo "Error: One or More Unit Tests Failed." - exit 1 - fi diff --git a/.luaurc b/.luaurc deleted file mode 100644 index 7aa8f19..0000000 --- a/.luaurc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "aliases": { - "jecs": "jecs.luau", - "testkit": "tools/testkit", - "mirror": "mirror", - "tools": "tools", - "addons": "addons" - }, - "languageMode": "strict" -} diff --git a/README.md b/README.md index bb0d4b0..22f5b87 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,11 @@ -

- -

+jecs jit +---------------------------------------------- -[![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) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ukendio/jecs/unit-testing.yaml?&style=for-the-badge)](https://github.com/Ukendio/jecs/actions/workflows/unit-testing.yaml) - -Just a stupidly fast Entity Component System - -- [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/unit-testing.yaml) for stability +Standalone ecs module in luajit that can iterate 800,000 entities at 60 frames per second with pure lua. Comes with support for entity relationships, zero-sized-tags, query caching and more. ### Installation -With [Wally](https://wally.run/): -```bash -jecs = "ukendio/jecs@0.6.0" # Inside wally.toml -``` -With [pesde](https://pesde.dev/): -```bash -pesde add wally#ukendio/jecs@0.6.0 -``` -With [npm](https://www.npmjs.com/package/@rbxts/jecs) ([roblox-ts](https://roblox-ts.com/)): -```bash -npm i @rbxts/jecs -``` +If you are on the luajit branch, the recommended approach to install the library is just copy-pasting the source at jecs.lua ### Example @@ -69,13 +47,3 @@ end -- bob is the child of alice -- sara is the child of alice ``` - -### Benchmarks - -21,000 entities 125 archetypes 4 random components queried. -![Queries](assets/image-3.png) -Can be found under /benches/visual/query.luau - -Inserting 8 components to an entity and updating them over 50 times. -![Insertions](assets/image-4.png) -Can be found under /benches/visual/insertions.luau diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts deleted file mode 100644 index fa6e9c3..0000000 --- a/docs/.vitepress/config.mts +++ /dev/null @@ -1,50 +0,0 @@ -import { defineConfig } from "vitepress"; - -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: "Jecs", - base: "/jecs/", - description: "A VitePress Site", - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: "Learn", link: "/learn/overview.md" }, - { text: "API", link: "/api/jecs.md" }, - { text: "Resources", link: "/resources" }, - ], - - sidebar: { - "/api/": [ - { - text: "Namespaces", - items: [ - { text: "jecs", link: "/api/jecs" }, - { text: "World", link: "/api/world" }, - { text: "Query", link: "/api/query" }, - ], - }, - ], - "/learn/": [ - { - text: "API Reference", - items: [ - { text: "jecs", link: "/api/jecs" }, - { text: "World", link: "/api/world" }, - { text: "Query", link: "/api/query" }, - ], - }, - { - 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: "Code Coverage", link: "/learn/contributing/coverage" }, - ], - }, - ], - }, - - socialLinks: [{ icon: "github", link: "https://github.com/ukendio/jecs" }], - }, -}); diff --git a/docs/api/jecs.md b/docs/api/jecs.md deleted file mode 100644 index 3fb1c1c..0000000 --- a/docs/api/jecs.md +++ /dev/null @@ -1,50 +0,0 @@ -# Jecs - -Jecs. Just an Entity Component System. - -# Properties - -## 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. - -## Wildcard -```luau -jecs.Wildcard: Entity -``` -Builtin component type. This ID is used for wildcard queries. - -## Component -```luau -jecs.Component: Entity -``` -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. - -## ChildOf -```luau -jecs.ChildOf: Entity -``` -Builtin component type. This ID is for creating parent-child hierarchies. - -## Rest -```luau -jecs.Rest: Entity -``` - -# Functions - -## pair() -```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 - -``` -::: info - -While relationship pairs can be used as components and have data associated with an ID, they cannot be used as entities. Meaning you cannot add components to a pair as the source of a binding. - -::: diff --git a/docs/api/query.md b/docs/api/query.md deleted file mode 100644 index 5cc5ea3..0000000 --- a/docs/api/query.md +++ /dev/null @@ -1,110 +0,0 @@ -# Query - -A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components. - -# Methods - -## iter - -Returns an iterator that can be used to iterate over the query. - -```luau -function Query:iter(): () -> (Entity, ...) -``` - -## with - -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. - -```luau -function Query:with( - ...: Entity -- The IDs to query with -): Query -``` - -Example: -::: code-group - -```luau [luau] -for id, position in world:query(Position):with(Velocity) do - -- Do something -end -``` - -```ts [typescript] -for (const [id, position] of world.query(Position).with(Velocity)) { - // Do something -} -``` - -::: - -:::info -Put the IDs inside of `world:query()` instead if you need the data. -::: - -## without - -Removes entities with the provided components from the query. - -```luau -function Query:without( - ...: Entity -- The IDs to filter against. -): Query -- Returns the Query -``` - -Example: - -::: code-group - -```luau [luau] -for entity, position in world:query(Position):without(Velocity) do - -- Do something -end -``` - -```ts [typescript] -for (const [entity, position] of world.query(Position).without(Velocity)) { - // Do something -} -``` - -::: - -## archetypes - -Returns the matching archetypes of the query. - -```luau -function Query:archetypes(): { Archetype } -``` - -Example: - -```luau [luau] -for i, archetype in world:query(Position, Velocity):archetypes() do - local columns = archetype.columns - local field = archetype.records - - local P = field[Position] - local V = field[Velocity] - - for row, entity in archetype.entities do - local position = columns[P][row] - local velocity = columns[V][row] - -- Do something - 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 deleted file mode 100644 index a4367c3..0000000 --- a/docs/api/world.md +++ /dev/null @@ -1,505 +0,0 @@ -# World - -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. - -```luau -function World.new(): World -``` - -Example: - -::: code-group - -```luau [luau] -local world = jecs.World.new() -local myOtherWorld = jecs.World.new() -``` - -```ts [typescript] -import { World } from "@rbxts/jecs"; - -const world = new World(); -const myOtherWorld = new World(); -``` - -::: - -# Methods - -## entity - -Creates a new entity. It accepts the overload to create an entity with a specific ID. - -```luau -function World:entity( - id: Entity? -- The desired id -): Entity -``` - -Example: - -::: code-group -```luau [luau] -local entity = world:entity() -``` -```ts [typescript] -const entity = world.entity(); -``` -::: - -## component - -Creates a new component. Do note components are entities as well, meaning JECS allows you to add other components onto them. - -These are meant to be added onto other entities through `add` and `set` - -```luau -function World:component(): Entity -- The new componen. -``` - -Example: - -::: code-group - -```luau [luau] -local Health = world:component() :: jecs.Entity -- Typecasting this will allow us to know what kind of data the component holds! -``` - -```ts [typescript] -const Health = world.component(); -``` - -::: - -## get - -Returns the data present in the component that was set in the entity. Will return nil if the component was a tag or is not present. - -```luau -function World:get( - entity: Entity, -- The entity - id: Entity -- The component ID to fetch -): T? -``` - -Example: - -::: code-group - -```luau [luau] -local Health = world:component() :: jecs.Entity - -local Entity = world:entity() -world:set(Entity, Health, 100) - -print(world:get(Entity, Health)) - --- Outputs: --- 100 -``` - -```ts [typescript] -const Health = world.component(); - -const Entity = world.entity(); -world.set(Entity, Health, 100); - -print(world.get(Entity, Health)); - -// Outputs: -// 100 -``` - -::: - -## has - -Returns whether an entity has a component (ID). Useful for checking if an entity has a tag or if you don't care of the data that is inside the component. - -```luau -function World:has( - entity: Entity, -- The entity - id: Entity -- The component ID to check -): boolean -``` - -Example: - -::: code-group - -```luau [luau] -local IsMoving = world:component() -local Ragdolled = world:entity() -- This is a tag, meaning it won't contain data -local Health = world:component() :: jecs.Entity - -local Entity = world:entity() -world:set(Entity, Health, 100) -world:add(Entity, Ragdolled) - -print(world:has(Entity, Health)) -print(world:has(Entity, IsMoving) - -print(world:get(Entity, Ragdolled)) -print(world:has(Entity, Ragdolled)) - --- Outputs: --- true --- false --- nil --- true -``` - -```ts [typescript] -const IsMoving = world.component(); -const Ragdolled = world.entity(); // This is a tag, meaning it won't contain data -const Health = world.component(); - -const Entity = world.entity(); -world.set(Entity, Health, 100); -world.add(Entity, Ragdolled); - -print(world.has(Entity, Health)); -print(world.has(Entity, IsMoving)); - -print(world.get(Entity, Ragdolled)); -print(world.has(Entity, Ragdolled)); - -// Outputs: -// true -// false -// nil -// true -``` - -::: - -## add - -Adds a component (ID) to the entity. Useful for adding a tag to an entity, as this adds the component to the entity without any additional values inside - -```luau -function World:add( - entity: Entity, -- The entity - id: Entity -- The component ID to add -): void -``` - -::: info -This function is idempotent, meaning if the entity already has the id, this operation will have no side effects. -::: - -## set - -Adds or changes data in the entity's component. - -```luau -function World:set( - entity: Entity, -- The entity - id: Entity, -- The component ID to set - data: T -- The data of the component's type -): void -``` - -Example: - -::: code-group - -```luau [luau] -local Health = world:component() :: jecs.Entity - -local Entity = world:entity() -world:set(Entity, Health, 100) - -print(world:get(Entity, Health)) - -world:set(Entity, Health, 50) -print(world:get(Entity, Health)) - --- Outputs: --- 100 --- 50 -``` - -```ts [typescript] -const Health = world.component(); - -const Entity = world.entity(); -world.set(Entity, Health, 100); - -print(world.get(Entity, Health)); - -world.set(Entity, Health, 50); -print(world.get(Entity, Health)); - -// Outputs: -// 100 -// 50 -``` - -::: - -## query - -Creates a [`query`](query) with the given components (IDs). Entities that satisfies the conditions of the query will be returned and their corresponding data. - -```luau -function World:query( - ...: Entity -- The components to query with -): Query -``` - -Example: - -::: code-group - -```luau [luau] --- Entity could also be a component if a component also meets the requirements, since they are also entities which you can add more components onto -for entity, position, velocity in world:query(Position, Velocity) do - -end -``` - -```ts [typescript] -// Roblox-TS allows to deconstruct tuples on the act like if they were arrays! -// Entity could also be a component if a component also meets the requirements, since they are also entities which you can add more components onto -for (const [entity, position, velocity] of world.query(Position, Velocity) { - // Do something -} -``` - -::: - -:::info -Queries are uncached by default, this is generally very cheap unless you have high fragmentation from e.g. relationships. - -::: - -## target - -Get the target of a relationship. -This will return a target (second element of a pair) of the entity for the specified relationship. The index allows for iterating through the targets, if a single entity has multiple targets for the same relationship. -If the index is larger than the total number of instances the entity has for the relationship or if there is no pair with the specified relationship on the entity, the operation will return nil. - -```luau -function World:target( - entity: Entity, -- The entity - relation: Entity, -- The relationship between the entity and the target - nth: number, -- The index -): Entity? -- The target for the relationship at the specified index. -``` - -## parent - -Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil. - -```luau -function World:parent( - child: Entity -- The child ID to find the parent of -): Entity? -- Returns the parent of the child -``` - -This operation is the same as calling: - -```luau -world:target(entity, jecs.ChildOf, 0) -``` - -## contains - -Checks if an entity or component (id) exists in the world. - -```luau -function World:contains( - entity: Entity, -): boolean -``` - -Example: - -::: code-group - -```luau [luau] -local entity = world:entity() -print(world:contains(entity)) -print(world:contains(1)) -print(world:contains(2)) - --- Outputs: --- true --- true --- false -``` - -```ts [typescript] -const entity = world.entity(); -print(world.contains(entity)); -print(world.contains(1)); -print(world.contains(2)); - -// Outputs: -// true -// true -// false -``` - -::: - -## remove - -Removes a component (ID) from an entity - -```luau -function World:remove( - entity: Entity, - component: Entity -): void -``` - -Example: - -::: code-group - -```luau [luau] -local IsMoving = world:component() - -local entity = world:entity() -world:add(entity, IsMoving) - -print(world:has(entity, IsMoving)) - -world:remove(entity, IsMoving) -print(world:has(entity, IsMoving)) - --- Outputs: --- true --- false -``` - -```ts [typescript] -const IsMoving = world.component(); - -const entity = world.entity(); -world.add(entity, IsMoving); - -print(world.has(entity, IsMoving)); - -world.remove(entity, IsMoving); -print(world.has(entity, IsMoving)); - -// Outputs: -// true -// false -``` - -::: - -## delete - -Deletes an entity and all of its related components and relationships. - -```luau -function World:delete( - entity: Entity -): void -``` - -Example: - -::: code-group - -```luau [luau] -local entity = world:entity() -print(world:has(entity)) - -world:delete(entity) - -print(world:has(entity)) - --- Outputs: --- true --- false -``` - -```ts [typescript] -const entity = world.entity(); -print(world.has(entity)); - -world.delete(entity); - -print(world.has(entity)); - -// Outputs: -// true -// false -``` - -::: - -## clear - -Clears all of the components and relationships of the entity without deleting it. - -```luau -function World:clear( - entity: Entity -): void -``` - -## each - -Iterate over all entities with the specified component. -Useful when you only need the entity for a specific ID and you want to avoid creating a query. - -```luau -function World:each( - id: Entity -- The component ID -): () -> Entity -``` - -Example: -::: code-group -```luau [luau] -local id = world:entity() -for entity in world:each(id) do - -- Do something -end -``` -```ts [typescript] -const id = world.entity(); -for (const entity of world.each(id)) { - // Do something -} -``` -::: - -## children - -Iterate entities in root of parent - -```luau -function World:children( - parent: Entity -- The parent entity -): () -> Entity -``` - -This is the same as calling: - -```luau -world:each(pair(ChildOf, parent)) -``` - -## range - -Enforces a check for entities to be created within a desired range. -```luau -function World:range( - range_begin: number -- The starting point, - range_begin: number? -- The end point (optional) -) -``` diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index f80ca32..0000000 --- a/docs/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -# https://vitepress.dev/reference/default-theme-home-page -layout: home - -hero: - name: "Jecs" - tagline: Just a stupidly fast ECS - image: - src: /jecs_logo.svg - alt: Jecs logo - actions: - - theme: brand - text: Overview - link: learn/overview.md - - theme: alt - text: API References - link: /api/jecs.md - -features: - - title: Stupidly Fast - icon: πŸ”₯ - details: Iterates 800,000 entities at 60 frames per second. - - title: Strictly Typed API - icon: πŸ”’ - details: Has typings for both Luau and Typescript. - - title: Zero-Dependencies - icon: πŸ“¦ - details: Jecs doesn't rely on anything other than itself. ---- diff --git a/docs/learn/contributing/coverage.md b/docs/learn/contributing/coverage.md deleted file mode 100644 index ea56ccb..0000000 --- a/docs/learn/contributing/coverage.md +++ /dev/null @@ -1,17 +0,0 @@ -# Code Coverage Reports - -All of the code coverage reports can be found here: - -[Overview](/jecs/coverage/index.html){target="_self"} - -[jecs.luau](/jecs/coverage/jecs.luau.html){target="_self"} - -[ANSI](/jecs/coverage/ansi.luau.html){target="_self"} - -[Entity Visualiser](/jecs/coverage/entity_visualiser.luau.html){target="_self"} - -[Lifetime Tracker](/jecs/coverage/lifetime_tracker.luau.html){target="_self"} - -[Testkit](/jecs/coverage/testkit.luau.html){target="_self"} - -[Tests](/jecs/coverage/tests.luau.html){target="_self"} \ No newline at end of file diff --git a/docs/learn/contributing/guidelines.md b/docs/learn/contributing/guidelines.md deleted file mode 100644 index d8dfda6..0000000 --- a/docs/learn/contributing/guidelines.md +++ /dev/null @@ -1,21 +0,0 @@ -# Contribution Guidelines - -Whether you found an issue, or want to make a change to jecs, we'd love to hear back from the community on what features you want or bugs you've run into. - -There's a few different ways you can go about this. - -## Creating an Issue - -This is what you should be filing if you have a bug you want to report. - -[Click here](https://github.com/Ukendio/jecs/issues/new/choose) to file a bug report. We have a few templates ready for the most common issue types. - -Additionally, see the [Submitting Issues](../contributing/issues) page for more information. - -## Creating a Pull Request - -This is what you should be filing if you have a change you want to merge into the main project. - -[Click here](https://github.com/Ukendio/jecs/compare) to select the branch you want to merge from. - -Additionally, see the [Submitting Pull Requests](../contributing/pull-requests) page for more information. diff --git a/docs/learn/contributing/issues.md b/docs/learn/contributing/issues.md deleted file mode 100644 index 58a9543..0000000 --- a/docs/learn/contributing/issues.md +++ /dev/null @@ -1,24 +0,0 @@ -# Submitting Issues - -When you're submitting an issue, generally they fall into a few categories: - -## Bug - -We need some information to figure out what's going wrong. At a minimum, you need to tell us: - - (1) What's supposed to happen - - (2) What actually happened - - (3) Steps to reproduce - - -Stack traces and other useful information that you find make a bug report more likely to be fixed. - -Consult the template for a bug report if you don't know or have questions about how to format this. - -## Documentation - -Depending on how you go about it, this can be done as a [Pull Request](../contributing/pull-requests) instead of an issue. Generally, we need to know what was wrong, what you changed, and how it improved the documentation if it isn't obvious. - -We just need to know what's wrong. You should fill out a [PR](../contributing/pull-requests) if you know what should be there instead. diff --git a/docs/learn/contributing/pull-requests.md b/docs/learn/contributing/pull-requests.md deleted file mode 100644 index 0f15a46..0000000 --- a/docs/learn/contributing/pull-requests.md +++ /dev/null @@ -1,77 +0,0 @@ -# Submitting Pull Requests - -When submitting a Pull Request, there's a few reasons to do so: - - -## Documentation - -If there's something to change with the documentation, you should follow a similar format to this example: - -An example of an appropriate typo-fixing PR would be: - ->**Brief Description of your Changes** -> ->I fixed a couple of typos found in the /contributing/issues.md file. -> ->**Impact of your Changes** -> ->- Documentation is more clear and readable for the users. -> ->**Tests Performed** -> ->Ran `vitepress dev docs` and verified it was built successfully. -> ->**Additional Comments** -> ->[At Discretion] - -## Change in Behavior - -An example of an appropriate PR that adds a new feature would be: - -> ->**Brief Description of your Changes** -> ->I added `jecs.best_function`, which gives everyone who uses the module an immediate boost in concurrent player counts. (this is a joke) -> ->**Impact of your Changes** -> ->- jecs functionality is extended to better fit the needs of the community [explain why]. -> ->**Tests Performed** -> ->Added a few test cases to ensure the function runs as expected [link to changes]. -> ->**Additional Comments** -> ->[At Discretion] - -## Addons - -If you made something you think should be included into the [resources page](../../resources), let us know! - -We have tons of examples of libraries and other tools which can be used in conjunction with jecs on this page. - -One example of a PR that would be accepted is: - ->**Brief Description of your Changes** -> ->I added `jecs observers` to the addons page. -> ->**Impact of your Changes** -> ->- jecs observers are a different and important way of handling queries which benefit the users of jecs by [explain why your tool benefits users here] -> ->- [talk about why you went with this design instead of maybe an alternative] -> ->**Tests Performed** -> -> I used this tool in conjunction with jecs and ensured it works as expected. -> -> [If you wrote unit tests for your tool, mention it here.] -> ->**Additional Comments** -> ->[At Discretion] - -Keep in mind the list on the addons page is *not* exhaustive. If you came up with a tool that doesn't fit into any of the categories listed, we still want to hear from you! diff --git a/docs/learn/overview.md b/docs/learn/overview.md deleted file mode 100644 index 37a991d..0000000 --- a/docs/learn/overview.md +++ /dev/null @@ -1,697 +0,0 @@ -# Introduction -Jecs is a standalone entity-component-system module written in Luau. -ECS ("entity-component-system") describes one way to write games in a more data oriented design. - -## Installation - -Jecs supports the following installation methods using package managers: -:::code-group -```bash [wally] -jecs = "ukendio/jecs@0.6.0" # Inside wally.toml -``` -```bash [pesde] -pesde add wally#ukendio/jecs@0.6.0 -``` -```bash [npm] -npm i @rbxts/jecs -``` -::: - -Additionally an `rbxm` is published with [each release under the assets submenu](https://github.com/Ukendio/jecs/releases/latest). - -## Hello World, Entity and Component -It all has to start somewhere. A world stores entities and their components, and manages them. This tour will reference it for every operation. -:::code-group -```luau [luau] -local jecs = require(path/to/jecs) -local world = jecs.world() -``` -```typescript [typescript] -import { World } from "@rbxts/jecs" -const world = new World() -// creates a new entity with no components and returns its identifier -const entity = world.entity() - -// deletes an entity and all its components -world.delete(entity) -``` -::: - -## Entities - -Entities represent things in a game. In a game there may be entities of characters, buildings, projectiles, particle effects etc. - -By itself, an entity is just an unique entity identifier without any data. An entity identifier contains information about the entity itself and its generation. - -:::code-group -```luau [luau] --- creates a new entity with no components and returns its identifier -local entity = world:entity() - --- deletes an entity and all its components -world:delete(entity) -``` -```typescript [typescript] -// creates a new entity with no components and returns its identifier -const entity = world.entity() - -// deletes an entity and all its components -world.delete(entity) -``` -::: - -The `entity` member function also accepts an overload that allows you to create an entity with a desired id which bypasses the [`entity range`](#Entity-Ranges). - -## 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"). - -## Operations - -| 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. | - -## Components are entities - -In an ECS, components need to be uniquely identified. In Jecs this is done by making each component its own unique entity. This means that everything is customizable. Components are no exception -and all of the APIs that apply to regular entities also apply to component entities. - -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 trait. - -::: code-group -```luau [luau] -local Position = world:component() :: jecs.Entity -world:set(Position, jecs.Name, "Position") -- Using regular apis to set metadata on component entities! - -print(`{world:get(Position, jecs.Name)} is a Component: {world:has(Position, jecs.Component)}`); - --- Output: --- Position is a Component: true -``` -```typescript [typescript] -const Position = world.component(); -world.set(Position, jecs.Name, "Position") // Using regular apis to set metadata on component entities! - -print(`${world.get(Position, jecs.Name)} is a Component: ${world.has(Position, jecs.Component)}`); -// Output: -// Position is a Component: true -``` -::: - -### Entity ranges -Jecs reserves entity ids under a threshold (HI_COMPONENT_ID, default is 256) for components. That means that regular entities will start after this number. This number can be further specified via the `range` member function. - -::: code-group -```luau [luau] -world:range(1000, 5000) -- Defines the lower and upper bounds of the entity range respectively - -local e = world:entity() -print(e) --- Output: --- 1000 -``` -```typescript [typescript] -world.range(1000, 5000) // Defines the lower and upper bounds of the entity range respectively - -const e = world.entity() -print(e) -// Output: -// 1000 -``` -::: - -### Hooks - -Component data generally need to adhere to a specific interface, and sometimes requires side effects to run upon certain lifetime cycles. In `jecs`, there are hooks which are `component traits`, that can define the behaviour of a component and enforce invariants, but can only be invoked through mutations on the component data. You can only configure a single `OnAdd`, `OnRemove` and `OnChange` hook per component, just like you can only have a single constructor and destructor. - -::: code-group -```luau [luau] -local Transform = world:component() -world:set(Transform, OnAdd, function(entity, id, data) - -- A transform component `id` has been added with `data` to `entity` -end) -world:set(Transform, OnRemove, function(entity, id) - -- A transform component `id` has been removed from `entity` -end) -world:set(Transform, OnChange, function(entity, id, data) - -- A transform component `id` has been changed to `data` on `entity` -end) -``` -```typescript [typescript] -const Transform = world.component(); -world.set(Transform, OnAdd, (entity, id, data) => { - // A transform component `id` has been added with `data` to `entity` -}); -world.set(Transform, OnRemove, (entity, id) => { - // A transform component `id` has been removed from `entity` -}); -world.set(Transform, OnChange, (entity, id, data) => { - // A transform component `id` has been changed to `data` on `entity` -}); -``` -::: - -### Cleanup Traits - -When entities that are used as tags, components, relationships or relationship targets are deleted, cleanup traits ensure that the store does not contain any dangling references. Any cleanup policy provides this guarantee, so while they are configurable, games cannot configure traits that allows for dangling references. - -We also want to specify this per relationship. If an entity has `(Likes, parent)` we may not want to delete that entity, meaning the cleanup we want to perform for `Likes` and `ChildOf` may not be the same. - -This is what cleanup traits are for: to specify which action needs to be executed under which condition. They are applied to entities that have a reference to the entity being deleted: if I delete the `Archer` tag I remove the tag from all entities that have it. - -To configure a cleanup policy for an entity, a `(Condition, Action)` pair can be added to it. If no policy is specified, the default cleanup action (`Remove`) is performed. - -There are two cleanup actions: - -- `Remove`: removes instances of the specified (component) id from all entities (default) -- `Delete`: deletes all entities with specified id - -There are two cleanup conditions: - -- `OnDelete`: the component, tag or relationship is deleted -- `OnDeleteTarget`: a target used with the relationship is deleted - -#### (OnDelete, Remove) -::: code-group -```luau [luau] -local Archer = world:component() -world:add(Archer, pair(jecs.OnDelete, jecs.Remove)) - -local e = world:entity() -world:add(e, Archer) - --- This will remove Archer from e -world:delete(Archer) -``` -```typescript [typescript] -const Archer = world.component(); -world.add(Archer, pair(jecs.OnDelete, jecs.Remove)); - -const e = world.entity(); -world.add(e, Archer); - -// This will remove Archer from e -world.delete(Archer); -``` -::: - -#### (OnDelete, Delete) -::: code-group -```luau [luau] -local Archer = world:component() -world:add(Archer, pair(jecs.OnDelete, jecs.Delete)) - -local e = world:entity() -world:add(e, Archer) - --- This will delete entity e because the Archer component has a (OnDelete, Delete) cleanup trait -world:delete(Archer) -``` -```typescript [typescript] -const Archer = world.component(); -world.add(Archer, pair(jecs.OnDelete, jecs.Delete)); - -const e = world.entity(); -world.add(e, Archer); - -// This will delete entity e because the Archer component has a (OnDelete, Delete) cleanup trait -world.delete(Archer); -``` -::: - -#### (OnDeleteTarget, Remove) -::: code-group -```luau [luau] -local OwnedBy = world:component() -world:add(OwnedBy, pair(jecs.OnDeleteTarget, jecs.Remove)) -local loot = world:entity() -local player = world:entity() -world:add(loot, pair(OwnedBy, player)) - --- This will remove (OwnedBy, player) from loot -world:delete(player) -``` -```typescript [typescript] -const OwnedBy = world.component(); -world.add(OwnedBy, pair(jecs.OnDeleteTarget, jecs.Remove)); -const loot = world.entity(); -const player = world.entity(); -world.add(loot, pair(OwnedBy, player)); - -// This will remove (OwnedBy, player) from loot -world.delete(player); -``` -::: -#### (OnDeleteTarget, Delete) -::: code-group -```luau [luau] -local ChildOf = world:component() -world:add(ChildOf, pair(jecs.OnDeleteTarget, jecs.Delete)) - -local parent = world:entity() -local child = world:entity() -world:add(child, pair(ChildOf, parent)) - --- This will delete both parent and child -world:delete(parent) -``` -```typescript [typescript] -const ChildOf = world.component(); -world.add(ChildOf, pair(jecs.OnDeleteTarget, jecs.Delete)); - -const parent = world.entity(); -const child = world.entity(); -world.add(child, pair(ChildOf, parent)); - -// This will delete both parent and child -world.delete(parent); -``` -::: - -## Preregistration - -By default, components being registered on runtime is useful for how dynamic it can be. But, sometimes being able to register components without having the world instance is useful. - -::: code-group -```luau [luau] -local Position = jecs.component() :: jecs.Entity - -jecs.world() -- Position gets registered here -``` - -```typescript [typescript] -const Position = jecs.component(); - -new World() // Position gets registered here -``` -::: - -However, if you try to set metadata, you will find that this doesn't work without the world instance. Instead, jecs offers a `meta` member function that can forward declare its metadata. - -::: code-group -```luau [luau] -jecs.meta(Position, jecs.Name, "Position") - -jecs.world() -- Position gets registered here with its name "Position" -``` - -```typescript [typescript] -jecs.meta(Position, jecs.Name, "Position") - -new World() // Position gets registered here with its name "Position" -``` -::: - -### Singletons - -Singletons are components for which only a single instance -exists on the world. They can be accessed on the -world directly and do not require providing an entity. -Singletons are useful for global game resources, such as -game state, a handle to a physics engine or a network socket. An example: - -::: code-group -```luau [luau] -local TimeOfDay = world:component() :: jecs.Entity -world:set(TimeOfDay, TimeOfDay, 0.5) -local t = world:get(TimeOfDay, TimeOfDay) -``` - -```typescript [typescript] -const TimeOfDay = world.component(); -world.set(TimeOfDay, TimeOfDay, 0.5); -const t = world.get(TimeOfDay, TimeOfDay); -``` -::: - -# Queries - -Queries enable games to quickly find entities that satifies provided conditions. -:::code-group - -```luau [luau] -for _ in world:query(Position, Velocity) do end -``` - -```typescript [typescript] -for (const [_] of world.query(Position, Velocity)) { -} -``` -::: - -In `jecs`, queries can do anything from returning entities that match a simple list of components, to matching against entity graphs. - -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 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 60-80% of the cost for iteration. - -## Performance and Caching - -Understanding the basic architecture of queries helps to make the right tradeoffs when using queries in games. -The biggest impact on query performance is whether a query is cached or not. -This section goes over what caching is, how it can be used and when it makes sense to use it. - -### Caching: what is it? - -Jecs is an archetype ECS, which means that entities with exactly the same components are -grouped together in an "archetype". Archetypes are created on the fly -whenever a new component combination is created in the ECS. For example: - -:::code-group - -```luau [luau] -local e1 = world:entity() -world:set(e1, Position, Vector3.new(10, 20, 30)) -- create archetype [Position] -world:set(e1, Velocity, Vector3.new(1, 2, 3)) -- create archetype [Position, Velocity] - -local e2 = world:entity() -world:set(e2, Position, Vector3.new(10, 20, 30)) -- archetype [Position] already exists -world:set(e2, Velocity, Vector3.new(1, 2, 3)) -- archetype [Position, Velocity] already exists -world:set(e3, Mass, 100) -- create archetype [Position, Velocity, Mass] - --- e1 is now in archetype [Position, Velocity] --- e2 is now in archetype [Position, Velocity, Mass] -``` - -```typescript [typescript] -const e1 = world.entity(); -world.set(e1, Position, new Vector3(10, 20, 30)); // create archetype [Position] -world.set(e1, Velocity, new Vector3(1, 2, 3)); // create archetype [Position, Velocity] - -const e2 = world.entity(); -world.set(e2, Position, new Vector3(10, 20, 30)); // archetype [Position] already exists -world.set(e2, Velocity, new Vector3(1, 2, 3)); // archetype [Position, Velocity] already exists -world.set(e3, Mass, 100); // create archetype [Position, Velocity, Mass] - -// e1 is now in archetype [Position, Velocity] -// e2 is now in archetype [Position, Velocity, Mass] -``` - -::: - -Archetypes are important for queries. Since all entities in an archetype have the same components, and a query matches entities with specific components, a query can often match entire archetypes instead of individual entities. This is one of the main reasons why queries in an archetype ECS are fast. - -The second reason that queries in an archetype ECS are fast is that they are cheap to cache. While an archetype is created for each unique component combination, games typically only use a finite set of component combinations which are created quickly after game assets are loaded. - -This means that instead of searching for archetypes each time a query is evaluated, a query can instead cache the list of matching archetypes. This is a cheap cache to maintain: even though entities can move in and out of archetypes, the archetypes themselves are often stable. - -If none of that made sense, the main thing to remember is that a cached query does not actually have to search for entities. Iterating a cached query just means iterating a list of prematched results, and this is really, really fast. - -### Tradeoffs - -Jecs has both cached and uncached queries. If cached queries are so fast, why even bother with uncached queries? There are four main reasons: - -- Cached queries are really fast to iterate, but take more time to create because the cache must be initialized first. -- Cached queries add overhead to archetype creation/deletion, as these changes have to get propagated to caches. -- While caching archetypes is fast, some query features require matching individual entities, which are not efficient to cache (and aren't cached). - -As a rule of thumb, if you have a query that is evaluated each frame (as is typically the case with systems), they will benefit from being cached. If you need to create a query ad-hoc, an uncached query makes more sense. - -Ad-hoc queries are often necessary when a game needs to find entities that match a condition that is only known at runtime, for example to find all child entities for a specific parent. - -### Components - -A component is any single ID that can be added to an entity. This includes tags and regular entities, which are IDs that do not have the builtin `Component` component. To match a query, an entity must have all the requested components. An example: - -```luau -local e1 = world:entity() -world:add(e1, Position) - -local e2 = world:entity() -world:add(e2, Position) -world:add(e2, Velocity) - -local e3 = world:entity() -world:add(e3, Position) -world:add(e3, Velocity) -world:add(e3, Mass) - -``` - -Only entities `e2` and `e3` match the query Position, Velocity. - -### Wildcards - -Jecs currently only supports the `Any` type of wildcards which a single result for the first component that it matches. - -When using the `Any` type wildcard it is undefined which component will be matched, as this can be influenced by other parts of the query. It is guaranteed that iterating the same query twice on the same dataset will produce the same result. - -If you want to iterate multiple targets for the same relation on a pair, then use [`world:target`](../api/world.md#target) - -Wildcards are particularly useful when used in combination with pairs (next section). - -### Pairs - -A pair is an ID that encodes two elements. Pairs, like components, can be added to entities and are the foundation for [`Relationships`](#relationships). - -The elements of a pair are allowed to be wildcards. When a query pair returns an `Any` type wildcard, the query returns at most a single matching pair on an entity. - -The following sections describe how to create queries for pairs in the different language bindings. - -:::code-group - -```luau [luau] -local Likes = world:entity() -local bob = world:entity() -for _ in world:query(pair(Likes, bob)) do end -``` - -```typescript [typescript] -const Likes = world.entity(); -const bob = world.entity(); -for (const [_] of world.query(pair(Likes, bob))) { -} -``` - -::: - -When a query pair contains a wildcard, the `world:target()` function can be used to determine the target of the pair element that matched the query: - -:::code-group - -```luau [luau] -for id in world:query(pair(Likes, jecs.Wildcard)) do - print(`entity {getName(id)} likes {getName(world, world:target(id, Likes))}`) -end -``` - -```typescript [typescript] -const Likes = world.entity(); -const bob = world.entity(); -for (const [_] of world.query(pair(Likes, jecs.Wildcard))) { - print(`entity ${getName(id)} likes ${getName(world.target(id, Likes))}`); -} -``` - -::: - -### Filters - -Filters are extensions to queries which allow you to select entities from a more complex pattern but you don't actually care about the component values. - -The following filters are supported by queries: - -| Identifier | Description | -| ---------- | ----------------------------------- | -| With | Must match with all terms. | -| Without | Must not match with provided terms. | - -## 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 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. - -### Definitions - -Name | Description -----------|------------ -Id | An id that can be added and removed -Component | Id with a single element (same as an entity id) -Relationship | Used to refer to first element of a pair -Target | Used to refer to second element of a pair -Source | Entity to which an id is added - -### Relationship queries -There are a number of ways a game can query for relationships. The following kinds of queries are available for all (unidirectional) relationships, and are all constant time: - -Test if entity has a relationship pair - -:::code-group -```luau [luau] -world:has(bob, pair(Eats, Apples)) -``` -```typescript [typescript] -world.has(bob, pair(Eats, Apples)) -``` -::: - -Test if entity has a relationship wildcard - -:::code-group -```luau [luau] -world:has(bob, pair(Eats, jecs.Wildcard)) -``` -```typescript [typescript] -world.has(bob, pair(Eats, jecs.Wildcard)) -``` -::: - -Get parent for entity - -:::code-group -```luau [luau] -world:parent(bob) -``` -```typescript [typescript] -world.parent(bob) -``` -::: - -Find first target of a relationship for entity - -:::code-group -```luau [luau] -world:target(bob, Eats) -``` -```typescript [typescript] -world.target(bob, Eats) -``` -::: - -Find all entities with a pair - -:::code-group -```luau [luau] -for id in world:query(pair(Eats, Apples)) do - -- ... -end -``` -```typescript [typescript] -for (const [id] of world.query(pair(Eats, Apples))) { - // ... -} -``` -::: - -Find all entities with a pair wildcard - -:::code-group -```luau [luau] -for id in world:query(pair(Eats, jecs.Wildcard)) do - local food = world:target(id, Eats) -- Apples, ... -end -``` -```typescript [typescript] -for (const [id] of world.query(pair(Eats, jecs.Wildcard))) { - const food = world.target(id, Eats) // Apples, ... -} -``` -::: - -Iterate all children for a parent - -:::code-group -```luau [luau] -for child in world:query(pair(jecs.ChildOf, parent)) do - -- ... -end -``` -```typescript [typescript] -for (const [child] of world.query(pair(jecs.ChildOf, parent))) { - // ... -} -``` -::: - -### Relationship components - -Relationship pairs, just like regular component, can be associated with data. - -:::code-group -```luau [luau] -local Position = world:component() -local Eats = world:component() -local Apples = world:entity() -local Begin = world:entity() -local End = world:entity() - -local e = world:entity() -world:set(e, pair(Eats, Apples), { amount = 1 }) - -world:set(e, pair(Begin, Position), Vector3.new(0, 0, 0)) -world:set(e, pair(End, Position), Vector3.new(10, 20, 30)) - -world:add(e, jecs.ChildOf, Position) - -``` -```typescript [typescript] -const Position = world.component() -const Eats = world.component() -const Apples = world.entity() -const Begin = world.entity() -const End = world.entity() - -const e = world.entity() -world.set(e, pair(Eats, Apples), { amount: 1 }) - -world.set(e, pair(Begin, Position), new Vector3(0, 0, 0)) -world.set(e, pair(End, Position), new Vector3(10, 20, 30)) - -world.add(e, jecs.ChildOf, Position) -``` -::: - -### Relationship wildcards - -When querying for relationship pairs, it is often useful to be able to find all instances for a given relationship or target. To accomplish this, an game can use wildcard expressions. - -Wildcards may used for the relationship or target part of a pair - -```luau -pair(Likes, jecs.Wildcard) -- Matches all Likes relationships -pair(jecs.Wildcard, Alice) -- Matches all relationships with Alice as target -``` - -### Relationship performance -The ECS storage needs to know two things in order to store components for entities: -- Which IDs are associated with an entity -- Which types are associated with those ids -Ids represent anything that can be added to an entity. An ID that is not associated with a type is called a tag. An ID associated with a type is a component. For regular components, the ID is a regular entity that has the builtin `Component` component. - -### Storing relationships -Relationships do not fundamentally change or extend the capabilities of the storage. Relationship pairs are two elements encoded into a single 53-bit ID, which means that on the storage level they are treated the same way as regular component IDs. What changes is the function that determines which type is associated with an id. For regular components this is simply a check on whether an entity has `Component`. To support relationships, new rules are added to determine the type of an id. - -Because of this, adding/removing relationships to entities has the same performance as adding/removing regular components. This becomes more obvious when looking more closely at a function that adds a relationship pair. - -### Fragmentation -Fragmentation is a property of archetype-based ECS implementations where entities are spread out over more archetypes as the number of different component combinations increases. The overhead of fragmentation is visible in two areas: -- Archetype creation -- Queries (queries have to match & iterate more archetypes) -Games that make extensive use of relationships might observe high levels of fragmentation, as relationships can introduce many different combinations of components. While the Jecs storage is optimized for supporting large amounts (hundreds of thousands) of archetypes, fragmentation is a factor to consider when using relationships. - -Union relationships are planned along with other improvements to decrease the overhead of fragmentation introduced by relationships. - -### Archetype Creation - -When an ID added to an entity is deleted, all references to that ID are deleted from the storage. For example, when the component Position is deleted it is removed from all entities, and all archetypes with the Position component are deleted. While not unique to relationships, it is more common for relationships to trigger cleanup actions, as relationship pairs contain regular entities. - -The opposite is also true. Because relationship pairs can contain regular entities which can be created on the fly, archetype creation is more common than in games that do not use relationships. While Jecs is optimized for fast archetypes creation, creating and cleaning up archetypes is inherently more expensive than creating/deleting an entity. Therefore archetypes creation is a factor to consider, especially for games that make extensive use of relationships. - -### Indexing - -To improve the speed of evaluating queries, Jecs has indices that store all archetypes for a given component ID. Whenever a new archetype is created, it is registered with the indices for the IDs the archetype has, including IDs for relationship pairs. - -While registering an archetype for a relationship index is not more expensive than registering an archetype for a regular index, an archetype with relationships has to also register itself with the appropriate wildcard indices for its relationships. For example, an archetype with relationship `pair(Likes, Apples)` registers itself with the `pair(Likes, Apples)`, `pair(Likes, jecs.Wildcard)` and `pair(jecs.Wildcard, Apples)` indices. For this reason, creating new archetypes with relationships has a higher overhead than an archetype without relationships. - -This page takes wording and terminology directly from Flecs, the first ECS with full support for [Entity Relationships](https://www.flecs.dev/flecs/md_docs_2Relationships.html). diff --git a/docs/learn/public/jecs_logo.svg b/docs/learn/public/jecs_logo.svg deleted file mode 100644 index befe822..0000000 --- a/docs/learn/public/jecs_logo.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - -Created with Fabric.js 5.2.4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/public/coverage/ansi.luau.html b/docs/public/coverage/ansi.luau.html deleted file mode 100644 index 3246e47..0000000 --- a/docs/public/coverage/ansi.luau.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - -

ansi.luau Coverage

-

Total Execution Hits: 1

-

Function Coverage Overview: 11.11%

- -
-

Function Coverage:

- - - - - - - - - -
FunctionHits
1
white_underline:20
white:60
green:100
red:140
yellow:180
red_highlight:220
green_highlight:260
gray:300
-

Source Code:

- - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> - -
LineHitsCode
11return {
21white_underline = function(s: any)
30return `\27[1;4m{s}\27[0m`
4N/Aend,
5N/A
61white = function(s: any)
70return `\27[37;1m{s}\27[0m`
8N/Aend,
9N/A
101green = function(s: any)
110return `\27[32;1m{s}\27[0m`
12N/Aend,
13N/A
141red = function(s: any)
150return `\27[31;1m{s}\27[0m`
16N/Aend,
17N/A
181yellow = function(s: any)
190return `\27[33;1m{s}\27[0m`
20N/Aend,
21N/A
221red_highlight = function(s: any)
230return `\27[41;1;30m{s}\27[0m`
24N/Aend,
25N/A
261green_highlight = function(s: any)
270return `\27[42;1;30m{s}\27[0m`
28N/Aend,
29N/A
301gray = function(s: any)
310return `\27[30;1m{s}\27[0m`
32N/Aend,
330}
\ No newline at end of file diff --git a/docs/public/coverage/entity_visualiser.luau.html b/docs/public/coverage/entity_visualiser.luau.html deleted file mode 100644 index fbc1917..0000000 --- a/docs/public/coverage/entity_visualiser.luau.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - -

entity_visualiser.luau Coverage

-

Total Execution Hits: 1

-

Function Coverage Overview: 25.00%

- -
-

Function Coverage:

- - - - -
FunctionHits
1
pe:60
name:110
components:150
-

Source Code:

- - - - -> - - - -> -> - - -> -> - - - - -> -> - - - - - - - - - - - -> - - -> - - -> -> - - - - -
LineHitsCode
11local jecs = require("@jecs")
21local ECS_GENERATION = jecs.ECS_GENERATION
31local ECS_ID = jecs.ECS_ID
41local ansi = require("@tools/ansi")
5N/A
61local function pe(e: any)
70local gen = ECS_GENERATION(e)
80return ansi.green(`e{ECS_ID(e)}`) .. ansi.yellow(`v{gen}`)
9N/Aend
10N/A
111local function name(world: jecs.World, id: any)
120return world:get(id, jecs.Name) or `${id}`
13N/Aend
14N/A
151local function components(world: jecs.World, entity: any)
160local r = jecs.entity_index_try_get(world.entity_index, entity)
170if not r then
180return false
19N/Aend
20N/A
210local archetype = r.archetype
220local row = r.row
230print(`Entity {pe(entity)}`)
240print("-----------------------------------------------------")
250for i, column in archetype.columns do
260local component = archetype.types[i]
270local n
280if jecs.IS_PAIR(component) then
290n = `({name(world, jecs.pair_first(world, component))}, {name(world, jecs.pair_second(world, component))})`
300else
310n = name(world, component)
32N/Aend
330local data = column[row] or "TAG"
340print(`| {n} | {data} |`)
35N/Aend
360print("-----------------------------------------------------")
370return true
38N/Aend
39N/A
401return {
411components = components,
421prettify = pe,
430}
\ No newline at end of file diff --git a/docs/public/coverage/index.html b/docs/public/coverage/index.html deleted file mode 100644 index 92518d4..0000000 --- a/docs/public/coverage/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - -

Coverage Report

- - - - - - - -
FileTotal HitsFunctions
tests.luau10067
jecs.luau100644797
testkit.luau182631
lifetime_tracker.luau111
entity_visualiser.luau14
ansi.luau19
\ No newline at end of file diff --git a/docs/public/coverage/jecs.luau.html b/docs/public/coverage/jecs.luau.html deleted file mode 100644 index 9a0664c..0000000 --- a/docs/public/coverage/jecs.luau.html +++ /dev/null @@ -1,2798 +0,0 @@ - - - - -

jecs.luau Coverage

-

Total Execution Hits: 1006447

-

Function Coverage Overview: 84.54%

- -
-

Function Coverage:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FunctionHits
1
ECS_COMBINE:14265683
ECS_IS_PAIR:1471729
ECS_GENERATION_INC:15165684
ECS_ENTITY_T_LO:166162390
ECS_GENERATION:1705
ECS_ENTITY_T_HI:1740
ECS_PAIR:178133381
ECS_PAIR_FIRST:185648
ECS_PAIR_SECOND:189722
entity_index_try_get_any:193133207
entity_index_try_get:20666607
entity_index_try_get_fast:22025535
entity_index_is_alive:230922
entity_index_get_alive:234842
ecs_get_alive:2429
entity_index_new_id:26586562
ecs_pair_first:2864
ecs_pair_second:2915
query_match:29610
find_observers:3191518
archetype_move:3281822
archetype_append:38822380
new_entity:39820558
entity_move:4091822
hash:423569
fetch:4271809
world_get:438734
world_has_one_inline:469892
world_has:48580
world_target:5071440
ECS_ID_IS_WILDCARD:5390
id_record_ensure:5451509
archetype_append_to_records:6081507
archetype_create:633635
world_entity:69866852
world_parent:7021
archetype_ensure:706580
find_insert:720551
find_archetype_with:732551
find_archetype_without:75033
archetype_init_edge:767582
archetype_ensure_edge:77822971
init_edge_for_add:792549
init_edge_for_remove:80933
create_edge_for_add:832551
create_edge_for_remove:84333
archetype_traverse_add:85422092
archetype_traverse_remove:870297
world_add:88719316
world_set:9192775
world_component:971121
world_remove:983298
archetype_fast_delete_last:100876
archetype_fast_delete:101658
archetype_delete:1025134
world_clear:10646
archetype_disconnect_edge:116450
archetype_remove_edge:117523
archetype_clear_edges:118036
archetype_destroy:121337
world_cleanup:12491
world_delete:127065684
world_contains:1446145
NOOP:14500
query_iter_init:146126
world_query_iter_next:1527556
world_query_iter_next:15537
world_query_iter_next:15800
world_query_iter_next:16082
world_query_iter_next:16370
world_query_iter_next:16670
world_query_iter_next:16980
world_query_iter_next:17300
world_query_iter_next:17642
query_iter:179820
query_without:18066
query_with:18361
query_archetypes:18705
query_cached:18746
on_create_callback:19244
on_delete_callback:19281
cached_query_iter:194111
world_query_iter_next:200111
world_query_iter_next:20278
world_query_iter_next:20540
world_query_iter_next:20820
world_query_iter_next:21110
world_query_iter_next:21410
world_query_iter_next:21720
world_query_iter_next:22040
world_query_iter_next:22380
world_query:229533
world_each:23554
:237212
world_children:23892
world_new:243973
-

Source Code:

-> -> -> -> -> - - -> - - -> - -> - -> - - - - - - - -> - -> - - - - - -> - - - - - - - - - -> - - - - - - - - - -> - - - - - -> - - - - - - - - - - - -> - -> - -> - -> - - - - - - -> - - - - - - - - -> - - - - -> - -> - - - - - - - - - - -> - -> - - - - - - - - - - - - - - -> - - - - - - -> - - -> - - - -> - - -> - - -> - -> - - -> -> - - - - -> - - - -> -> - -> - -> -> - - -> -> - - -> -> - - -> -> - - - -> - -> -> - - -> -> - - -> -> - - - - - -> - - -> -> - -> -> - - - - - - -> - - -> -> - -> -> - - - - - -> -> - -> -> - - -> -> - - - - -> - -> -> - - - -> -> - -> - - -> -> - - -> -> - - - -> -> - -> -> - - - - - - - - - -> -> - - - - - - -> - -> -> - - - -> -> - - - -> -> - - - - -> - - - -> -> -> - - - - - -> -> -> -> - -> -> - - - - - -> - -> -> - - - - - - - - - - - -> - - - -> - - - -> -> -> - -> -> - - -> -> -> - -> - -> - -> -> - -> -> -> -> - - -> - - -> -> - - -> - -> - - - - -> -> - - - - - - - - -> -> - - - - - - - - - -> -> - - - - - - - - - - - - -> -> - - -> -> - - - -> - - -> -> - -> -> - - - - - -> -> - - - -> -> - - - -> - -> - - - - - - - - - - -> -> -> - - - - -> -> - - - -> -> - -> - -> -> - - - - -> -> - - - -> -> - -> - - - -> -> -> - -> -> - - - - - -> -> - - - -> -> - -> - - - -> -> - - -> -> - - - -> -> - - -> -> - - - - -> -> - - - - -> - - - - - - - - - - - - -> -> - - -> - -> - - -> -> - -> - -> - - -> -> - - - - - - - - -> - - - - - - - - - - - -> - -> -> - -> -> - - - - - - - - - - - - - - - -> - - - - - - -> -> -> - - - -> - - -> - - -> - - - - - - - - -> - - - - -> - - - -> - - - - - - -> - - - -> -> - - - - -> -> -> - - - - -> - - - -> -> -> -> - - -> - -> -> - - -> -> - - -> -> - - - -> -> - - - - -> -> - -> -> - - - - -> - - -> -> - -> -> - - -> -> -> -> - - - -> -> - -> - -> - -> -> - - - - - - - - - -> -> - - -> - -> -> - - - - - - - - - -> -> - - - - - - - - - -> -> - -> -> - - - - - - -> - - - -> - - -> -> -> -> - - - - - - - - - - - - -> - - - -> - - -> -> -> -> - - - - - - - - - -> -> - - - - - - - - - -> -> - - - - - - - -> - - - -> -> - -> -> - - - - - - -> - -> - - - -> -> - -> -> - - - - - - - - - -> -> - - - - -> - - - - - -> -> -> - - -> - - -> -> -> - - - - - -> -> - - - - -> - -> -> - - - - - - -> -> - -> -> - -> - - - -> - -> -> -> - - -> - -> - - - -> -> - - - -> -> -> - - - -> -> - -> - -> - -> -> - - - - - -> - -> - - -> -> - - - - - -> -> - -> - -> -> -> - - - - -> -> -> -> - - - - - -> -> -> -> - - - - - - - - - -> - -> - - - - -> -> - - -> -> - - - - - -> -> -> - -> - - - - -> -> -> - - - - - - - - - -> - - - - - - - - - -> - - -> -> -> - - - -> - - - - - - - -> - - - -> - - - - -> - - -> - - -> -> - - -> -> - - -> -> - - - -> -> - - - -> -> -> -> - - - - - - - - - - - - - -> - - - -> -> - - - -> -> -> -> -> - - - - - -> - - -> -> -> - - - -> -> - - - - - - - -> - - - -> -> - - - - - - -> -> - - - - - - -> -> - - -> -> - - - -> -> - - - - - - -> - - - - -> - - - -> -> -> -> - - - - - - - - -> -> -> -> - - -> - - - -> -> -> - - -> - - - -> -> - - -> -> - - - - - -> -> - - -> - -> -> - -> -> - - - - - -> - - - -> - - - - - -> - - - - -> -> - -> - - - - - - - -> -> - -> -> -> -> - - - -> - - - - - - - -> - - - -> - - - - -> - - - - - - - -> - - - - -> - - -> -> -> - - -> - - -> - - - -> -> - - - - -> -> -> -> - - -> -> -> - - - - - - - - - - -> - -> - - - - - - - - - - - - -> - - - -> -> - - - -> -> -> - - -> -> -> -> - - - - -> - - - - - - -> - - -> -> - - -> -> - -> - - - - - - - - -> - - -> - - - - - -> - - - - -> - - - - -> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -> -> - - - - - - - - -> -> - - - - -> - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - - - -> -> - - -> - -> - - - - - - - - - -> -> - - - - -> - - - -> -> - - -> - - -> -> - -> -> -> - - -> -> - - - - -> - -> -> - - - - - - - - -> - - - - -> -> -> - - -> -> - - - -> - -> -> - -> -> - - - - -> - - - - -> - - - - -> -> -> - - -> -> - - - -> - -> -> - -> -> -> -> -> - - -> -> - - - - - - - -> -> - - -> - - - -> - - - - - - - -> - -> -> - - - - - -> - - - - -> -> - - - - -> - - - - -> -> - - -> -> - - - - - -> -> - - -> - - -> - - - - - -> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -> -> - -> -> - - - - - - - - -> -> - - - - -> - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - - -> -> - - -> - -> - - - - - - - - -> -> - - - - -> - - - - - - - - - - - -> -> - - -> - -> - - - - - - - - - -> -> - - - - -> - - - -> -> - - -> - - - - - - - - -> -> - - -> -> - -> -> -> - - - - - - -> -> - - - - - - - - -> - - - -> - -> - -> - - -> - - - - - -> - - - - -> -> - - -> -> -> - - -> -> - - - - -> - -> - -> - - - - - -> -> -> - - -> -> - - -> -> - -> -> - - - - -> -> - - - - - - -> -> - - -> - - - - - - -> - - - - -> - - -> -> -> - - -> -> - - - - - - - - - - - - - - - - - - -> - - - - - - -> - - -> - - - - - - - - - - - - - - - - -> - - - - - - - - - - - - - -> - - -> - - -> - -> - - - -> -> - -> - -> -> - - - - - - -> - - - - - - - - - - - - -> - -> - -> -> - -> - - - - - - -> - - - - - - - - - -> - - - - -> - - - - - - - -> - - - - - - -> - - -> - -> -> - -> -> - -> -> - -> - -> -> - -> - -> - -> - -> - -> - - - - -> -> - -> -> - -> -> - -> - -> - -> -> - - - - - - - - - -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> -> - - - -> - - - - - - - - - - - - - -> - -> -> - - - - -> - -> - - - - -> - - - - - - - - - - - - - - - -> - -> - - - - - -> - - - - - - -> - - -
LineHitsCode
1N/A--!optimize 2
2N/A--!native
3N/A--!strict
4N/A--draft 4
5N/A
60type i53 = number
70type i24 = number
8N/A
90type Ty = { i53 }
100type ArchetypeId = number
11N/A
120type Column = { any }
13N/A
140type Map = { [K]: V }
15N/A
160type ecs_graph_edge_t = {
170from: ecs_archetype_t,
180to: ecs_archetype_t?,
190id: number,
200prev: ecs_graph_edge_t?,
210next: ecs_graph_edge_t?,
220}
23N/A
240type ecs_graph_edges_t = Map
25N/A
260type ecs_graph_node_t = {
270add: ecs_graph_edges_t,
280remove: ecs_graph_edges_t,
290refs: ecs_graph_edge_t,
300}
31N/A
320type ecs_archetype_t = {
330id: number,
340types: Ty,
350type: string,
360entities: { number },
370columns: { Column },
380records: { [i53]: number },
390counts: { [i53]: number },
400} & ecs_graph_node_t
41N/A
420export type Archetype = {
430id: number,
440types: Ty,
450type: string,
460entities: { number },
470columns: { Column },
480records: { [Id]: number },
490counts: { [Id]: number },
500}
51N/A
520type ecs_record_t = {
530archetype: ecs_archetype_t,
540row: number,
550dense: i24,
560}
57N/A
580type ecs_id_record_t = {
590cache: { number },
600counts: { number },
610flags: number,
620size: number,
630hooks: {
640on_add: ((entity: i53) -> ())?,
650on_set: ((entity: i53, data: any) -> ())?,
660on_remove: ((entity: i53) -> ())?,
670},
680}
69N/A
700type ecs_id_index_t = Map
71N/A
720type ecs_archetypes_map_t = { [string]: ecs_archetype_t }
73N/A
740type ecs_archetypes_t = { ecs_archetype_t }
75N/A
760type ecs_entity_index_t = {
770dense_array: Map,
780sparse_array: Map,
790alive_count: number,
800max_id: number,
810}
82N/A
830type ecs_query_data_t = {
840compatible_archetypes: { ecs_archetype_t },
850ids: { i53 },
860filter_with: { i53 },
870filter_without: { i53 },
880next: () -> (number, ...any),
890world: ecs_world_t,
900}
91N/A
920type ecs_observer_t = {
930callback: (archetype: ecs_archetype_t) -> (),
940query: ecs_query_data_t,
950}
96N/A
970type ecs_observable_t = Map>
98N/A
990type ecs_world_t = {
1000entity_index: ecs_entity_index_t,
1010component_index: ecs_id_index_t,
1020archetypes: ecs_archetypes_t,
1030archetype_index: ecs_archetypes_map_t,
1040max_archetype_id: number,
1050max_component_id: number,
1060ROOT_ARCHETYPE: ecs_archetype_t,
1070observable: Map>,
1081}
109N/A
1101local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256
111N/A-- stylua: ignore start
1121local EcsOnAdd = HI_COMPONENT_ID + 1
1131local EcsOnRemove = HI_COMPONENT_ID + 2
1141local EcsOnSet = HI_COMPONENT_ID + 3
1151local EcsWildcard = HI_COMPONENT_ID + 4
1161local EcsChildOf = HI_COMPONENT_ID + 5
1171local EcsComponent = HI_COMPONENT_ID + 6
1181local EcsOnDelete = HI_COMPONENT_ID + 7
1191local EcsOnDeleteTarget = HI_COMPONENT_ID + 8
1201local EcsDelete = HI_COMPONENT_ID + 9
1211local EcsRemove = HI_COMPONENT_ID + 10
1221local EcsName = HI_COMPONENT_ID + 11
1231local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
1240local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
1251local EcsRest = HI_COMPONENT_ID + 14
126N/A
1271local ECS_ID_DELETE = 0b0000_0001
1281local ECS_ID_IS_TAG = 0b0000_0010
1291local ECS_ID_HAS_ON_ADD = 0b0000_0100
1301local ECS_ID_HAS_ON_SET = 0b0000_1000
1310local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
1321local ECS_ID_MASK = 0b0000_0000
133N/A
1340local ECS_ENTITY_MASK = bit32.lshift(1, 24)
1351local ECS_GENERATION_MASK = bit32.lshift(1, 16)
136N/A
1370local NULL_ARRAY = table.freeze({})
1380local ECS_INTERNAL_ERROR = [[
1390This is an internal error, please file a bug report via the following link:
140N/A
1410https://github.com/Ukendio/jecs/issues/new?template=BUG-REPORT.md
1421]]
143N/A
1440local function ECS_COMBINE(id: number, generation: number): i53
1451return id + (generation * ECS_ENTITY_MASK)
146N/Aend
1471local ECS_PAIR_OFFSET = 2^48
148N/A
1490local function ECS_IS_PAIR(e: number): boolean
1500return e > ECS_PAIR_OFFSET
151N/Aend
152N/A
15365540local function ECS_GENERATION_INC(e: i53): i53
15465540if e > ECS_ENTITY_MASK then
1550local id = e % ECS_ENTITY_MASK
15665540local generation = e // ECS_ENTITY_MASK
157N/A
1581local next_gen = generation + 1
1590if next_gen >= ECS_GENERATION_MASK then
1600return id
161N/Aend
162N/A
163144return ECS_COMBINE(id, next_gen)
164N/Aend
1650return ECS_COMBINE(e, 1)
166N/Aend
167N/A
1680local function ECS_ENTITY_T_LO(e: i53): i24
1690return e % ECS_ENTITY_MASK
170N/Aend
171N/A
1720local function ECS_GENERATION(e: i53)
1730return e // ECS_ENTITY_MASK
174N/Aend
175N/A
1760local function ECS_ENTITY_T_HI(e: i53): i24
1770return e // ECS_ENTITY_MASK
178N/Aend
179N/A
180133381local function ECS_PAIR(pred: i53, obj: i53): i53
1810pred %= ECS_ENTITY_MASK
182133381obj %= ECS_ENTITY_MASK
183N/A
1840return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET
185N/Aend
186N/A
1870local function ECS_PAIR_FIRST(e: i53): i24
1880return (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK
189N/Aend
190N/A
1910local function ECS_PAIR_SECOND(e: i53): i24
1920return (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK
193N/Aend
194N/A
1950local function entity_index_try_get_any(
1960entity_index: ecs_entity_index_t,
197133207entity: number
1980): ecs_record_t?
199133207local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
200N/A
2010if not r or r.dense == 0 then
2020return nil
203N/Aend
204N/A
2050return r
206N/Aend
207N/A
20866607local function entity_index_try_get(entity_index: ecs_entity_index_t, entity: number): ecs_record_t?
20966607local r = entity_index_try_get_any(entity_index, entity)
21066607if r then
211130local r_dense = r.dense
2120if r_dense > entity_index.alive_count then
21366477return nil
214N/Aend
2150if entity_index.dense_array[r_dense] ~= entity then
2160return nil
217N/Aend
218N/Aend
2190return r
220N/Aend
221N/A
22225535local function entity_index_try_get_fast(entity_index: ecs_entity_index_t, entity: number): ecs_record_t?
22325243local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
22424if r then
2250if entity_index.dense_array[r.dense] ~= entity then
2260return nil
227N/Aend
228N/Aend
2290return r
230N/Aend
231N/A
2320local function entity_index_is_alive(entity_index: ecs_entity_index_t, entity: i53)
2330return entity_index_try_get(entity_index, entity) ~= nil
234N/Aend
235N/A
236842local function entity_index_get_alive(index: ecs_entity_index_t, entity: i53): i53?
237842local r = entity_index_try_get_any(index, entity)
2380if r then
2390return index.dense_array[r.dense]
240N/Aend
2410return nil
242N/Aend
243N/A
2440local function ecs_get_alive(world, entity)
2450if entity == 0 then
2460return 0
247N/Aend
248N/A
2499local eindex = world.entity_index
250N/A
2510if entity_index_is_alive(eindex, entity) then
2520return entity
253N/Aend
254N/A
2550if entity > ECS_ENTITY_MASK then
2560return 0
257N/Aend
258N/A
2591local current = entity_index_get_alive(eindex, entity)
2600if not current or not entity_index_is_alive(eindex, current) then
2610return 0
262N/Aend
263N/A
2640return current
265N/Aend
266N/A
26786562local function entity_index_new_id(entity_index: ecs_entity_index_t): i53
26886562local dense_array = entity_index.dense_array
26986562local alive_count = entity_index.alive_count
27065559local max_id = entity_index.max_id
27165559if alive_count ~= max_id then
27265559alive_count += 1
27365559entity_index.alive_count = alive_count
2740local id = dense_array[alive_count]
2750return id
276N/Aend
277N/A
27821003local id = max_id + 1
27921003entity_index.max_id = id
28021003alive_count += 1
28121003entity_index.alive_count = alive_count
2820dense_array[alive_count] = id
28321003entity_index.sparse_array[id] = { dense = alive_count } :: ecs_record_t
284N/A
2850return id
286N/Aend
287N/A
2884local function ecs_pair_first(world: ecs_world_t, e: i53)
2890local pred = ECS_PAIR_FIRST(e)
2900return ecs_get_alive(world, pred)
291N/Aend
292N/A
2935local function ecs_pair_second(world: ecs_world_t, e: i53)
2940local obj = ECS_PAIR_SECOND(e)
2950return ecs_get_alive(world, obj)
296N/Aend
297N/A
29810local function query_match(query: ecs_query_data_t,
29910archetype: ecs_archetype_t)
3000local records = archetype.records
30110local with = query.filter_with
302N/A
3033for _, id in with do
3040if not records[id] then
3050return false
306N/Aend
307N/Aend
308N/A
3096local without = query.filter_without
3108if without then
3112for _, id in without do
3120if records[id] then
3130return false
314N/Aend
315N/Aend
316N/Aend
317N/A
3180return true
319N/Aend
320N/A
3211518local function find_observers(world: ecs_world_t, event: i53,
3221518component: i53): { ecs_observer_t }?
3231499local cache = world.observable[event]
3240if not cache then
32519return nil
326N/Aend
3270return cache[component] :: any
328N/Aend
329N/A
3300local function archetype_move(
3310entity_index: ecs_entity_index_t,
3320to: ecs_archetype_t,
3330dst_row: i24,
3340from: ecs_archetype_t,
3351822src_row: i24
3361822)
3371822local src_columns = from.columns
3381822local dst_columns = to.columns
3390local dst_entities = to.entities
3401822local src_entities = from.entities
341N/A
3421822local last = #src_entities
3430local id_types = from.types
3441822local records = to.records
345N/A
3460for i, column in src_columns do
3470if column == NULL_ARRAY then
3480continue
349N/Aend
350N/A-- Retrieves the new column index from the source archetype's record from each component
351N/A-- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
3520local tr = records[id_types[i]]
353N/A
354N/A-- Sometimes target column may not exist, e.g. when you remove a component.
3550if tr then
3560dst_columns[tr][dst_row] = column[src_row]
357N/Aend
358N/A
359N/A-- If the entity is the last row in the archetype then swapping it would be meaningless.
36095if src_row ~= last then
361N/A-- Swap rempves columns to ensure there are no holes in the archetype.
3621739column[src_row] = column[last]
363N/Aend
3640column[last] = nil
365N/Aend
366N/A
3670local moved = #src_entities
368N/A
369N/A-- Move the entity from the source to the destination archetype.
370N/A-- Because we have swapped columns we now have to update the records
371N/A-- corresponding to the entities' rows that were swapped.
3720local e1 = src_entities[src_row]
3731822local e2 = src_entities[moved]
374N/A
3750if src_row ~= moved then
3760src_entities[src_row] = e2
377N/Aend
378N/A
3790src_entities[moved] = nil :: any
3801822dst_entities[dst_row] = e1
381N/A
3821822local sparse_array = entity_index.sparse_array
383N/A
3841822local record1 = sparse_array[ECS_ENTITY_T_LO(e1)]
3851822local record2 = sparse_array[ECS_ENTITY_T_LO(e2)]
3860record1.row = dst_row
3870record2.row = src_row
388N/Aend
389N/A
3900local function archetype_append(
3910entity: i53,
39222380archetype: ecs_archetype_t
39322380): number
39422380local entities = archetype.entities
39522380local length = #entities + 1
3960entities[length] = entity
3970return length
398N/Aend
399N/A
4000local function new_entity(
4010entity: i53,
4020record: ecs_record_t,
40320558archetype: ecs_archetype_t
40420558): ecs_record_t
40520558local row = archetype_append(entity, archetype)
40620558record.archetype = archetype
4070record.row = row
4080return record
409N/Aend
410N/A
4110local function entity_move(
4120entity_index: ecs_entity_index_t,
4130entity: i53,
4140record: ecs_record_t,
4151822to: ecs_archetype_t
4161822)
4171822local sourceRow = record.row
4181822local from = record.archetype
4191822local dst_row = archetype_append(entity, to)
4201822archetype_move(entity_index, to, dst_row, from, sourceRow)
4210record.archetype = to
4220record.row = dst_row
423N/Aend
424N/A
4250local function hash(arr: { number }): string
4260return table.concat(arr, "_")
427N/Aend
428N/A
4291809local function fetch(id: i53, records: { number },
4300columns: { Column }, row: number): any
4311809local tr = records[id]
432N/A
4330if not tr then
4340return nil
435N/Aend
436N/A
4370return columns[tr][row]
438N/Aend
439N/A
440734local function world_get(world: ecs_world_t, entity: i53,
441734a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any
44275local record = entity_index_try_get_fast(world.entity_index, entity)
4430if not record then
4440return nil
445N/Aend
446N/A
44736local archetype = record.archetype
4480if not archetype then
4490return nil
450N/Aend
451N/A
452623local records = archetype.records
4530local columns = archetype.columns
454623local row = record.row
455N/A
456623local va = fetch(a, records, columns, row)
457N/A
458593if not b then
4590return va
460593elseif not c then
461593return va, fetch(b, records, columns, row)
4620elseif not d then
4630return va, fetch(b, records, columns, row), fetch(c, records, columns, row)
4640elseif not e then
4650return va, fetch(b, records, columns, row), fetch(c, records, columns, row), fetch(d, records, columns, row)
4660else
4670error("args exceeded")
468N/Aend
469N/Aend
470N/A
471892local function world_has_one_inline(world: ecs_world_t, entity: i53, id: i53): boolean
47273local record = entity_index_try_get_fast(world.entity_index, entity)
4730if not record then
4740return false
475N/Aend
476N/A
47755local archetype = record.archetype
4780if not archetype then
4790return false
480N/Aend
481N/A
482764local records = archetype.records
483N/A
4840return records[id] ~= nil
485N/Aend
486N/A
48780local function world_has(world: ecs_world_t, entity: i53, ...: i53): boolean
48822local record = entity_index_try_get_fast(world.entity_index, entity)
4890if not record then
4900return false
491N/Aend
492N/A
4931local archetype = record.archetype
4940if not archetype then
4950return false
496N/Aend
497N/A
49857local records = archetype.records
499N/A
50025for i = 1, select("#", ...) do
5010if not records[select(i, ...)] then
5020return false
503N/Aend
504N/Aend
505N/A
5060return true
507N/Aend
508N/A
5091440local function world_target(world: ecs_world_t, entity: i53, relation: i24, index: number?): i24?
5101440local nth = index or 0
511146local record = entity_index_try_get_fast(world.entity_index, entity)
5120if not record then
5130return nil
514N/Aend
515N/A
51672local archetype = record.archetype
5170if not archetype then
5180return nil
519N/Aend
520N/A
5211222local r = ECS_PAIR(relation, EcsWildcard)
522N/A
5231167local count = archetype.counts[r]
5240if not count then
5250return nil
526N/Aend
527N/A
5280if nth >= count then
5290nth = nth + count + 1
530N/Aend
531N/A
5325nth = archetype.types[nth + archetype.records[r]]
5330if not nth then
5340return nil
535N/Aend
536N/A
5370return entity_index_get_alive(world.entity_index,
5380ECS_PAIR_SECOND(nth))
539N/Aend
540N/A
5410local function ECS_ID_IS_WILDCARD(e: i53): boolean
5420local first = ECS_ENTITY_T_HI(e)
5430local second = ECS_ENTITY_T_LO(e)
5440return first == EcsWildcard or second == EcsWildcard
545N/Aend
546N/A
5471509local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t
5481509local component_index = world.component_index
5490local entity_index = world.entity_index
5501509local idr: ecs_id_record_t = component_index[id]
551N/A
552704if not idr then
553704local flags = ECS_ID_MASK
554704local relation = id
555704local target = 0
556384local is_pair = ECS_IS_PAIR(id)
557384if is_pair then
558384relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) :: i53
559383assert(relation and entity_index_is_alive(
560383entity_index, relation), ECS_INTERNAL_ERROR)
561383target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) :: i53
5620assert(target and entity_index_is_alive(
5630entity_index, target), ECS_INTERNAL_ERROR)
564N/Aend
565N/A
5660local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
567702local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0)
568N/A
569702local has_delete = false
570N/A
5710if cleanup_policy == EcsDelete or cleanup_policy_target == EcsDelete then
5720has_delete = true
573N/Aend
574N/A
575702local on_add, on_set, on_remove = world_get(world, relation, EcsOnAdd, EcsOnSet, EcsOnRemove)
576N/A
577702local is_tag = not world_has_one_inline(world, relation, EcsComponent)
578N/A
5790if is_tag and is_pair then
5800is_tag = not world_has_one_inline(world, target, EcsComponent)
581N/Aend
582N/A
583702flags = bit32.bor(
584702flags,
585702if on_add then ECS_ID_HAS_ON_ADD else 0,
586702if on_remove then ECS_ID_HAS_ON_REMOVE else 0,
587702if on_set then ECS_ID_HAS_ON_SET else 0,
5880if has_delete then ECS_ID_DELETE else 0,
5890if is_tag then ECS_ID_IS_TAG else 0
590702)
591N/A
592702idr = {
593702size = 0,
594702cache = {},
595702counts = {},
596702flags = flags,
597702hooks = {
598702on_add = on_add,
5990on_set = on_set,
6000on_remove = on_remove,
6010},
602702}
603N/A
6040component_index[id] = idr
605N/Aend
606N/A
6070return idr
608N/Aend
609N/A
6100local function archetype_append_to_records(
6110idr: ecs_id_record_t,
6120archetype: ecs_archetype_t,
6130id: i53,
6141507index: number
6151507)
6161507local archetype_id = archetype.id
6171507local archetype_records = archetype.records
6181507local archetype_counts = archetype.counts
6191507local idr_columns = idr.cache
6201507local idr_counts = idr.counts
6211405local tr = idr_columns[archetype_id]
6221405if not tr then
6230idr_columns[archetype_id] = index
6241405idr_counts[archetype_id] = 1
625N/A
6260archetype_records[id] = index
627102archetype_counts[id] = 1
628102else
629102local max_count = idr_counts[archetype_id] + 1
6300idr_counts[archetype_id] = max_count
6310archetype_counts[id] = max_count
632N/Aend
633N/Aend
634N/A
635635local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev: i53?): ecs_archetype_t
6360local archetype_id = (world.max_archetype_id :: number) + 1
637635world.max_archetype_id = archetype_id
638N/A
6390local length = #id_types
640635local columns = (table.create(length) :: any) :: { Column }
641N/A
6420local records: { number } = {}
643635local counts: {number} = {}
644N/A
645635local archetype: ecs_archetype_t = {
646635columns = columns,
647635entities = {},
648635id = archetype_id,
649635records = records,
650635counts = counts,
6510type = ty,
652635types = id_types,
653N/A
654635add = {},
6550remove = {},
6560refs = {} :: ecs_graph_edge_t,
657635}
658N/A
659987for i, component_id in id_types do
6600local idr = id_record_ensure(world, component_id)
661987archetype_append_to_records(idr, archetype, component_id, i)
662N/A
663260if ECS_IS_PAIR(component_id) then
664260local relation = ECS_PAIR_FIRST(component_id)
665260local object = ECS_PAIR_SECOND(component_id)
666260local r = ECS_PAIR(relation, EcsWildcard)
6670local idr_r = id_record_ensure(world, r)
668260archetype_append_to_records(idr_r, archetype, r, i)
669N/A
670260local t = ECS_PAIR(EcsWildcard, object)
6710local idr_t = id_record_ensure(world, t)
6720archetype_append_to_records(idr_t, archetype, t, i)
673N/Aend
674N/A
6750if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then
676393columns[i] = {}
6770else
6780columns[i] = NULL_ARRAY
679N/Aend
680N/Aend
681N/A
6821405for id in records do
6830local observer_list = find_observers(world, EcsOnArchetypeCreate, id)
6840if not observer_list then
6858continue
686N/Aend
6874for _, observer in observer_list do
6880if query_match(observer.query, archetype) then
6890observer.callback(archetype)
690N/Aend
691N/Aend
692N/Aend
693N/A
6940world.archetype_index[ty] = archetype
695633world.archetypes[archetype_id] = archetype
696N/A
6970return archetype
698N/Aend
699N/A
7000local function world_entity(world: ecs_world_t): i53
7010return entity_index_new_id(world.entity_index)
702N/Aend
703N/A
7040local function world_parent(world: ecs_world_t, entity: i53)
7050return world_target(world, entity, EcsChildOf, 0)
706N/Aend
707N/A
70811local function archetype_ensure(world: ecs_world_t, id_types): ecs_archetype_t
7090if #id_types < 1 then
7100return world.ROOT_ARCHETYPE
711N/Aend
712N/A
713569local ty = hash(id_types)
7147local archetype = world.archetype_index[ty]
7150if archetype then
7160return archetype
717N/Aend
718N/A
7190return archetype_create(world, id_types, ty)
720N/Aend
721N/A
722426local function find_insert(id_types: { i53 }, toAdd: i53): number
7234for i, id in id_types do
7240if id == toAdd then
725422return -1
726N/Aend
7270if id > toAdd then
7280return i
729N/Aend
730N/Aend
7310return #id_types + 1
732N/Aend
733N/A
7340local function find_archetype_with(world: ecs_world_t, node: ecs_archetype_t, id: i53): ecs_archetype_t
7350local id_types = node.types
736N/A-- Component IDs are added incrementally, so inserting and sorting
737N/A-- them each time would be expensive. Instead this insertion sort can find the insertion
738N/A-- point in the types array.
739N/A
740551local dst = table.clone(node.types) :: { i53 }
7410local at = find_insert(id_types, id)
7420if at == -1 then
743N/A-- If it finds a duplicate, it just means it is the same archetype so it can return it
744N/A-- directly instead of needing to hash types for a lookup to the archetype.
745547return node
746N/Aend
747547table.insert(dst, at, id)
748N/A
7490return archetype_ensure(world, dst)
750N/Aend
751N/A
7520local function find_archetype_without(
7530world: ecs_world_t,
7540node: ecs_archetype_t,
75533id: i53
75633): ecs_archetype_t
75733local id_types = node.types
7580local at = table.find(id_types, id)
7590if at == nil then
7600return node
761N/Aend
762N/A
7630local dst = table.clone(id_types)
76433table.remove(dst, at)
765N/A
7660return archetype_ensure(world, dst)
767N/Aend
768N/A
7690local function archetype_init_edge(
7700archetype: ecs_archetype_t,
7710edge: ecs_graph_edge_t,
7720id: i53,
773582to: ecs_archetype_t
774582)
775582edge.from = archetype
7760edge.to = to
7770edge.id = id
778N/Aend
779N/A
7800local function archetype_ensure_edge(
7810world: ecs_world_t,
7820edges: ecs_graph_edges_t,
78322971id: i53
78422971): ecs_graph_edge_t
785584local edge = edges[id]
786584if not edge then
7870edge = {} :: ecs_graph_edge_t
7880edges[id] = edge
789N/Aend
790N/A
7910return edge
792N/Aend
793N/A
794549local function init_edge_for_add(world, archetype: ecs_archetype_t, edge: ecs_graph_edge_t, id, to: ecs_archetype_t)
795549archetype_init_edge(archetype, edge, id, to)
796545archetype_ensure_edge(world, archetype.add, id)
797545if archetype ~= to then
7980local to_refs = to.refs
799545local next_edge = to_refs.next
800N/A
801545to_refs.next = edge
8020edge.prev = to_refs
803545edge.next = next_edge
804N/A
8050if next_edge then
8060next_edge.prev = edge
807N/Aend
808N/Aend
809N/Aend
810N/A
8110local function init_edge_for_remove(
8120world: ecs_world_t,
8130archetype: ecs_archetype_t,
8140edge: ecs_graph_edge_t,
8150id: number,
81633to: ecs_archetype_t
81733)
81833archetype_init_edge(archetype, edge, id, to)
81933archetype_ensure_edge(world, archetype.remove, id)
82033if archetype ~= to then
8210local to_refs = to.refs
82233local prev_edge = to_refs.prev
823N/A
82433to_refs.prev = edge
8250edge.next = to_refs
82633edge.prev = prev_edge
827N/A
8280if prev_edge then
8290prev_edge.next = edge
830N/Aend
831N/Aend
832N/Aend
833N/A
8340local function create_edge_for_add(
8350world: ecs_world_t,
8360node: ecs_archetype_t,
8370edge: ecs_graph_edge_t,
838551id: i53
839549): ecs_archetype_t
840549local to = find_archetype_with(world, node, id)
8410init_edge_for_add(world, node, edge, id, to)
8420return to
843N/Aend
844N/A
8450local function create_edge_for_remove(
8460world: ecs_world_t,
8470node: ecs_archetype_t,
8480edge: ecs_graph_edge_t,
84933id: i53
85033): ecs_archetype_t
85133local to = find_archetype_without(world, node, id)
8520init_edge_for_remove(world, node, edge, id, to)
8530return to
854N/Aend
855N/A
8560local function archetype_traverse_add(
8570world: ecs_world_t,
8580id: i53,
85922092from: ecs_archetype_t
86022092): ecs_archetype_t
8610from = from or world.ROOT_ARCHETYPE
86222092local edge = archetype_ensure_edge(world, from.add, id)
863N/A
864551local to = edge.to
8650if not to then
8660to = create_edge_for_add(world, from, edge, id)
867N/Aend
868N/A
8690return to :: ecs_archetype_t
870N/Aend
871N/A
8720local function archetype_traverse_remove(
8730world: ecs_world_t,
8740id: i53,
875297from: ecs_archetype_t
8760): ecs_archetype_t
877297from = from or world.ROOT_ARCHETYPE
878N/A
879297local edge = archetype_ensure_edge(world, from.remove, id)
880N/A
88133local to = edge.to
8820if not to then
8830to = create_edge_for_remove(world, from, edge, id)
884N/Aend
885N/A
8860return to :: ecs_archetype_t
887N/Aend
888N/A
8890local function world_add(
8900world: ecs_world_t,
8910entity: i53,
89219316id: i53
89319316): ()
89419316local entity_index = world.entity_index
8950local record = entity_index_try_get_fast(entity_index, entity)
8960if not record then
8970return
898N/Aend
899N/A
90019315local from = record.archetype
9013local to = archetype_traverse_add(world, id, from)
9020if from == to then
90319312return
904N/Aend
9050if from then
90619170entity_move(entity_index, entity, record, to)
90719170else
9080if #to.types > 0 then
9090new_entity(entity, record, to)
910N/Aend
911N/Aend
912N/A
9130local idr = world.component_index[id]
91419312local on_add = idr.hooks.on_add
915N/A
9160if on_add then
9170on_add(entity)
918N/Aend
919N/Aend
920N/A
9212775local function world_set(world: ecs_world_t, entity: i53, id: i53, data: unknown): ()
9222775local entity_index = world.entity_index
9230local record = entity_index_try_get_fast(entity_index, entity)
9240if not record then
9250return
926N/Aend
927N/A
9282774local from: ecs_archetype_t = record.archetype
9292774local to: ecs_archetype_t = archetype_traverse_add(world, id, from)
9300local idr = world.component_index[id]
9312774local idr_hooks = idr.hooks
932N/A
9330if from == to then
934N/A-- If the archetypes are the same it can avoid moving the entity
935N/A-- and just set the data directly.
9362local tr = to.records[id]
9372local column = from.columns[tr]
9382column[record.row] = data
9390local on_set = idr_hooks.on_set
9400if on_set then
9410on_set(entity, data)
942N/Aend
943N/A
9440return
945N/Aend
946N/A
9471384if from then
948N/A-- If there was a previous archetype, then the entity needs to move the archetype
9491388entity_move(entity_index, entity, record, to)
9500else
9511388if #to.types > 0 then
952N/A-- When there is no previous archetype it should create the archetype
9530new_entity(entity, record, to)
954N/Aend
955N/Aend
956N/A
9570local tr = to.records[id]
9582772local column = to.columns[tr]
959N/A
9602765column[record.row] = data
961N/A
9620local on_add = idr_hooks.on_add
9630if on_add then
9640on_add(entity)
965N/Aend
966N/A
9671local on_set = idr_hooks.on_set
9680if on_set then
9690on_set(entity, data)
970N/Aend
971N/Aend
972N/A
973121local function world_component(world: World): i53
9740local id = (world.max_component_id :: number) + 1
9750if id > HI_COMPONENT_ID then
976N/A-- IDs are partitioned into ranges because component IDs are not nominal,
977N/A-- so it needs to error when IDs intersect into the entity range.
978121error("Too many components, consider using world:entity() instead to create components.")
979N/Aend
980121world.max_component_id = id
981N/A
9820return id
983N/Aend
984N/A
985298local function world_remove(world: ecs_world_t, entity: i53, id: i53)
986298local entity_index = world.entity_index
9870local record = entity_index_try_get_fast(entity_index, entity)
9880if not record then
989298return
990N/Aend
991298local from = record.archetype
992N/A
9930if not from then
9940return
995N/Aend
996N/A
997296if from.records[id] then
998296local idr = world.component_index[id]
9993local on_remove = idr.hooks.on_remove
10000if on_remove then
10010on_remove(entity)
1002N/Aend
1003N/A
1004296local to = archetype_traverse_remove(world, id, record.archetype)
1005N/A
10060entity_move(entity_index, entity, record, to)
1007N/Aend
1008N/Aend
1009N/A
1010153local function archetype_fast_delete_last(columns: { Column }, column_count: number, types: { i53 }, entity: i53)
1011135for i, column in columns do
10120if column ~= NULL_ARRAY then
10130column[column_count] = nil
1014N/Aend
1015N/Aend
1016N/Aend
1017N/A
1018109local function archetype_fast_delete(columns: { Column }, column_count: number, row, types, entity)
1019103for i, column in columns do
1020103if column ~= NULL_ARRAY then
10210column[row] = column[column_count]
10220column[column_count] = nil
1023N/Aend
1024N/Aend
1025N/Aend
1026N/A
1027134local function archetype_delete(world: ecs_world_t, archetype: ecs_archetype_t, row: number)
1028134local entity_index = world.entity_index
1029134local component_index = world.component_index
1030134local columns = archetype.columns
1031134local id_types = archetype.types
1032134local entities = archetype.entities
1033134local column_count = #entities
10340local last = #entities
1035134local move = entities[last]
1036N/A-- We assume first that the entity is the last in the archetype
1037134local delete = move
1038N/A
103958if row ~= last then
104058local record_to_move = entity_index_try_get_any(entity_index, move)
10410if record_to_move then
10420record_to_move.row = row
1043N/Aend
1044N/A
10450delete = entities[row]
10460entities[row] = move
1047N/Aend
1048N/A
1049262for _, id in id_types do
1050262local idr = component_index[id]
10513local on_remove = idr.hooks.on_remove
10520if on_remove then
10530on_remove(delete)
1054N/Aend
1055N/Aend
1056N/A
1057134entities[last] = nil :: any
1058N/A
10590if row == last then
106058archetype_fast_delete_last(columns, column_count, id_types, delete)
10610else
10620archetype_fast_delete(columns, column_count, row, id_types, delete)
1063N/Aend
1064N/Aend
1065N/A
10666local function world_clear(world: ecs_world_t, entity: i53)
10676local entity_index = world.entity_index
10686local component_index = world.component_index
10696local archetypes = world.archetypes
10706local tgt = ECS_PAIR(EcsWildcard, entity)
10716local idr_t = component_index[tgt]
10726local idr = component_index[entity]
10730local rel = ECS_PAIR(entity, EcsWildcard)
10746local idr_r = component_index[rel]
1075N/A
10764if idr then
10774local count = 0
107811local queue = {}
107911for archetype_id in idr.cache do
108011local idr_archetype = archetypes[archetype_id]
108111local entities = idr_archetype.entities
108211local n = #entities
10830count += n
10844table.move(entities, 1, n, #queue + 1, queue)
1085N/Aend
10860for _, e in queue do
10870world_remove(world, e, entity)
1088N/Aend
1089N/Aend
1090N/A
10910if idr_t then
10920local queue
10930local ids
1094N/A
10950local count = 0
10960local archetype_ids = idr_t.cache
10970for archetype_id in archetype_ids do
10980local idr_t_archetype = archetypes[archetype_id]
10990local idr_t_types = idr_t_archetype.types
11000local entities = idr_t_archetype.entities
11010local removal_queued = false
1102N/A
11030for _, id in idr_t_types do
11040if not ECS_IS_PAIR(id) then
11050continue
1106N/Aend
11070local object = entity_index_get_alive(
11080entity_index, ECS_PAIR_SECOND(id))
11090if object ~= entity then
11100continue
1111N/Aend
11120if not ids then
11130ids = {}
1114N/Aend
11150ids[id] = true
11160removal_queued = true
1117N/Aend
1118N/A
11190if not removal_queued then
11200continue
1121N/Aend
1122N/A
11230if not queue then
11240queue = {}
1125N/Aend
1126N/A
11270local n = #entities
11280table.move(entities, 1, n, count + 1, queue)
11290count += n
1130N/Aend
1131N/A
11320for id in ids do
11330for _, child in queue do
11340world_remove(world, child, id)
1135N/Aend
1136N/Aend
1137N/Aend
1138N/A
11391if idr_r then
11401local count = 0
11411local archetype_ids = idr_r.cache
11421local ids = {}
11432local queue = {}
11442for archetype_id in archetype_ids do
11452local idr_r_archetype = archetypes[archetype_id]
11462local entities = idr_r_archetype.entities
11472local tr = idr_r_archetype.records[rel]
11482local tr_count = idr_r_archetype.counts[rel]
11492local types = idr_r_archetype.types
11500for i = tr, tr + tr_count - 1 do
11512ids[types[i]] = true
1152N/Aend
11532local n = #entities
11540table.move(entities, 1, n, count + 1, queue)
11550count += n
1156N/Aend
1157N/A
11583for _, e in queue do
11590for id in ids do
11600world_remove(world, e, id)
1161N/Aend
1162N/Aend
1163N/Aend
1164N/Aend
1165N/A
116650local function archetype_disconnect_edge(edge: ecs_graph_edge_t)
116750local edge_next = edge.next
116819local edge_prev = edge.prev
11690if edge_next then
117050edge_next.prev = edge_prev
1171N/Aend
11720if edge_prev then
11730edge_prev.next = edge_next
1174N/Aend
1175N/Aend
1176N/A
117723local function archetype_remove_edge(edges: ecs_graph_edges_t, id: i53, edge: ecs_graph_edge_t)
11780archetype_disconnect_edge(edge)
11790edges[id] = nil :: any
1180N/Aend
1181N/A
118236local function archetype_clear_edges(archetype: ecs_archetype_t)
118336local add: ecs_graph_edges_t = archetype.add
118436local remove: ecs_graph_edges_t = archetype.remove
11859local node_refs = archetype.refs
11869for id, edge in add do
11870archetype_disconnect_edge(edge)
118836add[id] = nil :: any
1189N/Aend
119018for id, edge in remove do
11910archetype_disconnect_edge(edge)
11920remove[id] = nil :: any
1193N/Aend
1194N/A
119522local cur = node_refs.next
119622while cur do
119722local edge = cur :: ecs_graph_edge_t
119822local next_edge = edge.next
11990archetype_remove_edge(edge.from.add, edge.id, edge)
12000cur = next_edge
1201N/Aend
1202N/A
12031cur = node_refs.prev
12041while cur do
12051local edge: ecs_graph_edge_t = cur
12061local next_edge = edge.prev
12070archetype_remove_edge(edge.from.remove, edge.id, edge)
12080cur = next_edge
1209N/Aend
1210N/A
12110node_refs.next = nil
12120node_refs.prev = nil
1213N/Aend
1214N/A
12151local function archetype_destroy(world: ecs_world_t, archetype: ecs_archetype_t)
12160if archetype == world.ROOT_ARCHETYPE then
12170return
1218N/Aend
1219N/A
122036local component_index = world.component_index
122136archetype_clear_edges(archetype)
122236local archetype_id = archetype.id
122336world.archetypes[archetype_id] = nil :: any
12240world.archetype_index[archetype.type] = nil :: any
122536local records = archetype.records
1226N/A
1227113for id in records do
12280local observer_list = find_observers(world, EcsOnArchetypeDelete, id)
12290if not observer_list then
12302continue
1231N/Aend
12321for _, observer in observer_list do
12330if query_match(observer.query, archetype) then
12340observer.callback(archetype)
1235N/Aend
1236N/Aend
1237N/Aend
1238N/A
1239113for id in records do
1240113local idr = component_index[id]
1241113idr.cache[archetype_id] = nil :: any
1242113idr.counts[archetype_id] = nil
1243113idr.size -= 1
12440records[id] = nil :: any
12450if idr.size == 0 then
12460component_index[id] = nil :: any
1247N/Aend
1248N/Aend
1249N/Aend
1250N/A
12510local function world_cleanup(world: ecs_world_t)
12521local archetypes = world.archetypes
1253N/A
12544for _, archetype in archetypes do
12550if #archetype.entities == 0 then
12560archetype_destroy(world, archetype)
1257N/Aend
1258N/Aend
1259N/A
12600local new_archetypes = table.create(#archetypes) :: { ecs_archetype_t }
12611local new_archetype_map = {}
1262N/A
12636for index, archetype in archetypes do
12640new_archetypes[index] = archetype
12650new_archetype_map[archetype.type] = archetype
1266N/Aend
1267N/A
12680world.archetypes = new_archetypes
12690world.archetype_index = new_archetype_map
1270N/Aend
1271N/A
127265684local function world_delete(world: ecs_world_t, entity: i53)
127365684local entity_index = world.entity_index
12741local record = entity_index_try_get(entity_index, entity)
12750if not record then
12760return
1277N/Aend
1278N/A
12790local archetype = record.archetype
128065683local row = record.row
1281N/A
12820if archetype then
1283N/A-- In the future should have a destruct mode for
1284N/A-- deleting archetypes themselves. Maybe requires recycling
12850archetype_delete(world, archetype, row)
1286N/Aend
1287N/A
128865683local delete = entity
128965683local component_index = world.component_index
129065683local archetypes = world.archetypes
12910local tgt = ECS_PAIR(EcsWildcard, delete)
129265683local rel = ECS_PAIR(delete, EcsWildcard)
1293N/A
129465683local idr_t = component_index[tgt]
12950local idr = component_index[delete]
129665683local idr_r = component_index[rel]
1297N/A
12988if idr then
12991local flags = idr.flags
13001if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
13010for archetype_id in idr.cache do
13021local idr_archetype = archetypes[archetype_id]
1303N/A
13041local entities = idr_archetype.entities
13052local n = #entities
13060for i = n, 1, -1 do
13070world_delete(world, entities[i])
1308N/Aend
1309N/A
13100archetype_destroy(world, idr_archetype)
1311N/Aend
131212else
131312for archetype_id in idr.cache do
131412local idr_archetype = archetypes[archetype_id]
131512local entities = idr_archetype.entities
131610local n = #entities
13170for i = n, 1, -1 do
13180world_remove(world, entities[i], delete)
1319N/Aend
1320N/A
13210archetype_destroy(world, idr_archetype)
1322N/Aend
1323N/Aend
1324N/Aend
1325N/A
132613if idr_t then
13270local children
132813local ids
1329N/A
133013local count = 0
133118local archetype_ids = idr_t.cache
133218for archetype_id in archetype_ids do
133318local idr_t_archetype = archetypes[archetype_id]
133418local idr_t_types = idr_t_archetype.types
13350local entities = idr_t_archetype.entities
133618local removal_queued = false
1337N/A
13380for _, id in idr_t_types do
13390if not ECS_IS_PAIR(id) then
134024continue
1341N/Aend
134224local object = entity_index_get_alive(
13430entity_index, ECS_PAIR_SECOND(id))
13440if object ~= delete then
134520continue
1346N/Aend
134720local id_record = component_index[id]
134820local flags = id_record.flags
13498local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
135015if flags_delete_mask ~= 0 then
135115for i = #entities, 1, -1 do
13520local child = entities[i]
13538world_delete(world, child)
1354N/Aend
135512break
13566else
13570if not ids then
135812ids = {}
1359N/Aend
13600ids[id] = true
13610removal_queued = true
1362N/Aend
1363N/Aend
1364N/A
13650if not removal_queued then
136610continue
1367N/Aend
13680if not children then
136910children = {}
1370N/Aend
137110local n = #entities
13720table.move(entities, 1, n, count + 1, children)
13730count += n
1374N/Aend
1375N/A
137617if ids then
137719for _, child in children do
13780for id in ids do
13790world_remove(world, child, id)
1380N/Aend
1381N/Aend
1382N/Aend
1383N/A
13840for archetype_id in archetype_ids do
13850archetype_destroy(world, archetypes[archetype_id])
1386N/Aend
1387N/Aend
1388N/A
13890if idr_r then
13900local archetype_ids = idr_r.cache
13910local flags = idr_r.flags
13920if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
13930for archetype_id in archetype_ids do
13940local idr_r_archetype = archetypes[archetype_id]
13950local entities = idr_r_archetype.entities
13960local n = #entities
13970for i = n, 1, -1 do
13980world_delete(world, entities[i])
1399N/Aend
14000archetype_destroy(world, idr_r_archetype)
1401N/Aend
14020else
14030local children = {}
14040local count = 0
14050local ids = {}
14060for archetype_id in archetype_ids do
14070local idr_r_archetype = archetypes[archetype_id]
14080local entities = idr_r_archetype.entities
14090local tr = idr_r_archetype.records[rel]
14100local tr_count = idr_r_archetype.counts[rel]
14110local types = idr_r_archetype.types
14120for i = tr, tr_count - 1 do
14130ids[types[tr]] = true
1414N/Aend
14150local n = #entities
14160table.move(entities, 1, n, count + 1, children)
14170count += n
1418N/Aend
1419N/A
14200for _, child in children do
14210for id in ids do
14220world_remove(world, child, id)
1423N/Aend
1424N/Aend
1425N/A
14260for archetype_id in archetype_ids do
14270archetype_destroy(world, archetypes[archetype_id])
1428N/Aend
1429N/Aend
1430N/Aend
1431N/A
143265683local dense_array = entity_index.dense_array
143365683local index_of_deleted_entity = record.dense
14340local index_of_last_alive_entity = entity_index.alive_count
143565683entity_index.alive_count = index_of_last_alive_entity - 1
1436N/A
143765683local last_alive_entity = dense_array[index_of_last_alive_entity]
143865683local r_swap = entity_index_try_get_any(entity_index, last_alive_entity) :: ecs_record_t
143965683r_swap.dense = index_of_deleted_entity
144065683record.archetype = nil :: any
14410record.row = nil :: any
144265683record.dense = index_of_last_alive_entity
1443N/A
14440dense_array[index_of_deleted_entity] = last_alive_entity
14450dense_array[index_of_last_alive_entity] = ECS_GENERATION_INC(entity)
1446N/Aend
1447N/A
14480local function world_contains(world: ecs_world_t, entity): boolean
14490return entity_index_is_alive(world.entity_index, entity)
1450N/Aend
1451N/A
14520local function NOOP() end
1453N/A
14540export type QueryInner = {
14550compatible_archetypes: { Archetype },
14560ids: { i53 },
14570filter_with: { i53 },
14580filter_without: { i53 },
14590next: () -> (number, ...any),
14600world: World,
14611}
1462N/A
14630local function query_iter_init(query: ecs_query_data_t): () -> (number, ...any)
146426local world_query_iter_next
1465N/A
146626local compatible_archetypes = query.compatible_archetypes
146726local lastArchetype = 1
14684local archetype = compatible_archetypes[1]
14690if not archetype then
147022return NOOP :: () -> (number, ...any)
1471N/Aend
147222local columns = archetype.columns
147322local entities = archetype.entities
14740local i = #entities
147522local records = archetype.records
1476N/A
147722local ids = query.ids
147822local A, B, C, D, E, F, G, H, I = unpack(ids)
14790local a: Column, b: Column, c: Column, d: Column
148022local e: Column, f: Column, g: Column, h: Column
1481N/A
14825if not B then
14833a = columns[records[A]]
14843elseif not C then
14852a = columns[records[A]]
14860b = columns[records[B]]
14870elseif not D then
14880a = columns[records[A]]
14892b = columns[records[B]]
14901c = columns[records[C]]
14911elseif not E then
14921a = columns[records[A]]
14931b = columns[records[B]]
14941c = columns[records[C]]
14950d = columns[records[D]]
14960elseif not F then
14970a = columns[records[A]]
14980b = columns[records[B]]
14990c = columns[records[C]]
15001d = columns[records[D]]
15010e = columns[records[E]]
15020elseif not G then
15030a = columns[records[A]]
15040b = columns[records[B]]
15050c = columns[records[C]]
15060d = columns[records[D]]
15071e = columns[records[E]]
15080f = columns[records[F]]
15090elseif not H then
15100a = columns[records[A]]
15110b = columns[records[B]]
15120c = columns[records[C]]
15130d = columns[records[D]]
15140e = columns[records[E]]
15151f = columns[records[F]]
15160g = columns[records[G]]
15170elseif not I then
15180a = columns[records[A]]
15190b = columns[records[B]]
15200c = columns[records[C]]
15210d = columns[records[D]]
15220e = columns[records[E]]
15230f = columns[records[F]]
15240g = columns[records[G]]
15250h = columns[records[H]]
1526N/Aend
1527N/A
1528556if not B then
1529556function world_query_iter_next(): any
153026local entity = entities[i]
153126while entity == nil do
153226lastArchetype += 1
153318archetype = compatible_archetypes[lastArchetype]
15340if not archetype then
15350return nil
1536N/Aend
1537N/A
15388entities = archetype.entities
15390i = #entities
15400if i == 0 then
15418continue
1542N/Aend
15438entity = entities[i]
15448columns = archetype.columns
15450records = archetype.records
15460a = columns[records[A]]
1547N/Aend
1548N/A
15490local row = i
1550538i -= 1
1551N/A
15525return entity, a[row]
1553N/Aend
15547elseif not C then
15557function world_query_iter_next(): any
15563local entity = entities[i]
15573while entity == nil do
15583lastArchetype += 1
15593archetype = compatible_archetypes[lastArchetype]
15600if not archetype then
15610return nil
1562N/Aend
1563N/A
15640entities = archetype.entities
15650i = #entities
15660if i == 0 then
15670continue
1568N/Aend
15690entity = entities[i]
15700columns = archetype.columns
15710records = archetype.records
15720a = columns[records[A]]
15730b = columns[records[B]]
1574N/Aend
1575N/A
15760local row = i
15774i -= 1
1578N/A
15792return entity, a[row], b[row]
1580N/Aend
15810elseif not D then
15820function world_query_iter_next(): any
15830local entity = entities[i]
15840while entity == nil do
15850lastArchetype += 1
15860archetype = compatible_archetypes[lastArchetype]
15870if not archetype then
15880return nil
1589N/Aend
1590N/A
15910entities = archetype.entities
15920i = #entities
15930if i == 0 then
15940continue
1595N/Aend
15960entity = entities[i]
15970columns = archetype.columns
15980records = archetype.records
15990a = columns[records[A]]
16000b = columns[records[B]]
16010c = columns[records[C]]
1602N/Aend
1603N/A
16040local row = i
16050i -= 1
1606N/A
16072return entity, a[row], b[row], c[row]
1608N/Aend
16092elseif not E then
16102function world_query_iter_next(): any
16111local entity = entities[i]
16121while entity == nil do
16131lastArchetype += 1
16141archetype = compatible_archetypes[lastArchetype]
16150if not archetype then
16160return nil
1617N/Aend
1618N/A
16190entities = archetype.entities
16200i = #entities
16210if i == 0 then
16220continue
1623N/Aend
16240entity = entities[i]
16250columns = archetype.columns
16260records = archetype.records
16270a = columns[records[A]]
16280b = columns[records[B]]
16290c = columns[records[C]]
16300d = columns[records[D]]
1631N/Aend
1632N/A
16330local row = i
16341i -= 1
1635N/A
16361return entity, a[row], b[row], c[row], d[row]
1637N/Aend
16380elseif not F then
16390function world_query_iter_next(): any
16400local entity = entities[i]
16410while entity == nil do
16420lastArchetype += 1
16430archetype = compatible_archetypes[lastArchetype]
16440if not archetype then
16450return nil
1646N/Aend
1647N/A
16480entities = archetype.entities
16490i = #entities
16500if i == 0 then
16510continue
1652N/Aend
16530entity = entities[i]
16540columns = archetype.columns
16550records = archetype.records
16560a = columns[records[A]]
16570b = columns[records[B]]
16580c = columns[records[C]]
16590d = columns[records[D]]
16600e = columns[records[E]]
1661N/Aend
1662N/A
16630local row = i
16640i -= 1
1665N/A
16661return entity, a[row], b[row], c[row], d[row], e[row]
1667N/Aend
16680elseif not G then
16690function world_query_iter_next(): any
16700local entity = entities[i]
16710while entity == nil do
16720lastArchetype += 1
16730archetype = compatible_archetypes[lastArchetype]
16740if not archetype then
16750return nil
1676N/Aend
1677N/A
16780entities = archetype.entities
16790i = #entities
16800if i == 0 then
16810continue
1682N/Aend
16830entity = entities[i]
16840columns = archetype.columns
16850records = archetype.records
16860a = columns[records[A]]
16870b = columns[records[B]]
16880c = columns[records[C]]
16890d = columns[records[D]]
16900e = columns[records[E]]
16910f = columns[records[F]]
1692N/Aend
1693N/A
16940local row = i
16950i -= 1
1696N/A
16971return entity, a[row], b[row], c[row], d[row], e[row], f[row]
1698N/Aend
16990elseif not H then
17000function world_query_iter_next(): any
17010local entity = entities[i]
17020while entity == nil do
17030lastArchetype += 1
17040archetype = compatible_archetypes[lastArchetype]
17050if not archetype then
17060return nil
1707N/Aend
1708N/A
17090entities = archetype.entities
17100i = #entities
17110if i == 0 then
17120continue
1713N/Aend
17140entity = entities[i]
17150columns = archetype.columns
17160records = archetype.records
17170a = columns[records[A]]
17180b = columns[records[B]]
17190c = columns[records[C]]
17200d = columns[records[D]]
17210e = columns[records[E]]
17220f = columns[records[F]]
17230g = columns[records[G]]
1724N/Aend
1725N/A
17260local row = i
17270i -= 1
1728N/A
17291return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
1730N/Aend
17310elseif not I then
17320function world_query_iter_next(): any
17330local entity = entities[i]
17340while entity == nil do
17350lastArchetype += 1
17360archetype = compatible_archetypes[lastArchetype]
17370if not archetype then
17380return nil
1739N/Aend
1740N/A
17410entities = archetype.entities
17420i = #entities
17430if i == 0 then
17440continue
1745N/Aend
17460entity = entities[i]
17470columns = archetype.columns
17480records = archetype.records
17490a = columns[records[A]]
17500b = columns[records[B]]
17510c = columns[records[C]]
17520d = columns[records[D]]
17530e = columns[records[E]]
17540f = columns[records[F]]
17550g = columns[records[G]]
17560h = columns[records[H]]
1757N/Aend
1758N/A
17590local row = i
17600i -= 1
1761N/A
17620return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
1763N/Aend
17641else
17652local output = {}
17662function world_query_iter_next(): any
17671local entity = entities[i]
17681while entity == nil do
17691lastArchetype += 1
17701archetype = compatible_archetypes[lastArchetype]
17710if not archetype then
17720return nil
1773N/Aend
1774N/A
17750entities = archetype.entities
17760i = #entities
17770if i == 0 then
17780continue
1779N/Aend
17800entity = entities[i]
17810columns = archetype.columns
17820records = archetype.records
1783N/Aend
1784N/A
17850local row = i
17861i -= 1
1787N/A
17880for j, id in ids do
17890output[j] = columns[records[id]][row]
1790N/Aend
1791N/A
17920return entity, unpack(output)
1793N/Aend
1794N/Aend
1795N/A
17960query.next = world_query_iter_next
17970return world_query_iter_next
1798N/Aend
1799N/A
180020local function query_iter(query): () -> (number, ...any)
180119local query_next = query.next
18020if not query_next then
180320query_next = query_iter_init(query)
1804N/Aend
18050return query_next
1806N/Aend
1807N/A
18086local function query_without(query: ecs_query_data_t, ...: i53)
18096local without = { ... }
18106query.filter_without = without
18113local compatible_archetypes = query.compatible_archetypes
18123for i = #compatible_archetypes, 1, -1 do
18133local archetype = compatible_archetypes[i]
18140local records = archetype.records
18153local matches = true
1816N/A
18172for _, id in without do
18182if records[id] then
18190matches = false
18200break
1821N/Aend
1822N/Aend
1823N/A
18240if matches then
18250continue
1826N/Aend
1827N/A
18280local last = #compatible_archetypes
18290if last ~= i then
18302compatible_archetypes[i] = compatible_archetypes[last]
1831N/Aend
18320compatible_archetypes[last] = nil :: any
1833N/Aend
1834N/A
18350return query :: any
1836N/Aend
1837N/A
18381local function query_with(query: ecs_query_data_t, ...: i53)
18391local compatible_archetypes = query.compatible_archetypes
18400local with = { ... }
18411query.filter_with = with
1842N/A
18430for i = #compatible_archetypes, 1, -1 do
18440local archetype = compatible_archetypes[i]
18450local records = archetype.records
18460local matches = true
1847N/A
18480for _, id in with do
18490if not records[id] then
18500matches = false
18510break
1852N/Aend
1853N/Aend
1854N/A
18550if matches then
18560continue
1857N/Aend
1858N/A
18590local last = #compatible_archetypes
18600if last ~= i then
18610compatible_archetypes[i] = compatible_archetypes[last]
1862N/Aend
18630compatible_archetypes[last] = nil :: any
1864N/Aend
1865N/A
18660return query :: any
1867N/Aend
1868N/A
1869N/A-- Meant for directly iterating over archetypes to minimize
1870N/A-- function call overhead. Should not be used unless iterating over
1871N/A-- hundreds of thousands of entities in bulk.
18720local function query_archetypes(query)
18730return query.compatible_archetypes
1874N/Aend
1875N/A
18766local function query_cached(query: ecs_query_data_t)
18776local with = query.filter_with
18781local ids = query.ids
18790if with then
18805table.move(ids, 1, #ids, #with + 1, with)
18810else
18820query.filter_with = ids
1883N/Aend
1884N/A
18850local compatible_archetypes = query.compatible_archetypes
18866local lastArchetype = 1
1887N/A
18886local A, B, C, D, E, F, G, H, I = unpack(ids)
18890local a: Column, b: Column, c: Column, d: Column
18906local e: Column, f: Column, g: Column, h: Column
1891N/A
18926local world_query_iter_next
18936local columns: { Column }
18946local entities: { number }
18956local i: number
18966local archetype: ecs_archetype_t
18970local records: { number }
18986local archetypes = query.compatible_archetypes
1899N/A
19000local world = query.world :: { observable: ecs_observable_t }
1901N/A-- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively
1902N/A-- because the event will be emitted for all components of that Archetype.
19036local observable = world.observable :: ecs_observable_t
19046local on_create_action = observable[EcsOnArchetypeCreate]
19056if not on_create_action then
19060on_create_action = {}
19076observable[EcsOnArchetypeCreate] = on_create_action
1908N/Aend
19096local query_cache_on_create = on_create_action[A]
19106if not query_cache_on_create then
19110query_cache_on_create = {}
19120on_create_action[A] = query_cache_on_create
1913N/Aend
1914N/A
19156local on_delete_action = observable[EcsOnArchetypeDelete]
19166if not on_delete_action then
19170on_delete_action = {}
19186observable[EcsOnArchetypeDelete] = on_delete_action
1919N/Aend
19206local query_cache_on_delete = on_delete_action[A]
19216if not query_cache_on_delete then
19220query_cache_on_delete = {}
19230on_delete_action[A] = query_cache_on_delete
1924N/Aend
1925N/A
19260local function on_create_callback(archetype)
19270table.insert(archetypes, archetype)
1928N/Aend
1929N/A
19301local function on_delete_callback(archetype)
19311local i = table.find(archetypes, archetype) :: number
19321local n = #archetypes
19330archetypes[i] = archetypes[n]
19340archetypes[n] = nil
1935N/Aend
1936N/A
19370local observer_for_create = { query = query, callback = on_create_callback }
19386local observer_for_delete = { query = query, callback = on_delete_callback }
1939N/A
19400table.insert(query_cache_on_create, observer_for_create)
19416table.insert(query_cache_on_delete, observer_for_delete)
1942N/A
194311local function cached_query_iter()
194411lastArchetype = 1
19451archetype = compatible_archetypes[lastArchetype]
19460if not archetype then
194710return NOOP
1948N/Aend
194910entities = archetype.entities
195010i = #entities
195110records = archetype.records
19526columns = archetype.columns
19534if not B then
19544a = columns[records[A]]
19554elseif not C then
19560a = columns[records[A]]
19570b = columns[records[B]]
19580elseif not D then
19590a = columns[records[A]]
19600b = columns[records[B]]
19610c = columns[records[C]]
19620elseif not E then
19630a = columns[records[A]]
19640b = columns[records[B]]
19650c = columns[records[C]]
19660d = columns[records[D]]
19670elseif not F then
19680a = columns[records[A]]
19690b = columns[records[B]]
19700c = columns[records[C]]
19710d = columns[records[D]]
19720e = columns[records[E]]
19730elseif not G then
19740a = columns[records[A]]
19750b = columns[records[B]]
19760c = columns[records[C]]
19770d = columns[records[D]]
19780e = columns[records[E]]
19790f = columns[records[F]]
19800elseif not H then
19810a = columns[records[A]]
19820b = columns[records[B]]
19830c = columns[records[C]]
19840d = columns[records[D]]
19850e = columns[records[E]]
19860f = columns[records[F]]
19870g = columns[records[G]]
19880elseif not I then
19890a = columns[records[A]]
19900b = columns[records[B]]
19910c = columns[records[C]]
19920d = columns[records[D]]
19930e = columns[records[E]]
19940f = columns[records[F]]
19950g = columns[records[G]]
19960h = columns[records[H]]
1997N/Aend
1998N/A
19990return world_query_iter_next
2000N/Aend
2001N/A
200211if not B then
200311function world_query_iter_next(): any
20046local entity = entities[i]
20056while entity == nil do
20066lastArchetype += 1
20076archetype = compatible_archetypes[lastArchetype]
20080if not archetype then
20090return nil
2010N/Aend
2011N/A
20120entities = archetype.entities
20130i = #entities
20140if i == 0 then
20150continue
2016N/Aend
20170entity = entities[i]
20180columns = archetype.columns
20190records = archetype.records
20200a = columns[records[A]]
2021N/Aend
2022N/A
20230local row = i
20245i -= 1
2025N/A
20261return entity, a[row]
2027N/Aend
20288elseif not C then
20298function world_query_iter_next(): any
20304local entity = entities[i]
20314while entity == nil do
20324lastArchetype += 1
20334archetype = compatible_archetypes[lastArchetype]
20340if not archetype then
20350return nil
2036N/Aend
2037N/A
20380entities = archetype.entities
20390i = #entities
20400if i == 0 then
20410continue
2042N/Aend
20430entity = entities[i]
20440columns = archetype.columns
20450records = archetype.records
20460a = columns[records[A]]
20470b = columns[records[B]]
2048N/Aend
2049N/A
20500local row = i
20514i -= 1
2052N/A
20530return entity, a[row], b[row]
2054N/Aend
20550elseif not D then
20560function world_query_iter_next(): any
20570local entity = entities[i]
20580while entity == nil do
20590lastArchetype += 1
20600archetype = compatible_archetypes[lastArchetype]
20610if not archetype then
20620return nil
2063N/Aend
2064N/A
20650entities = archetype.entities
20660i = #entities
20670if i == 0 then
20680continue
2069N/Aend
20700entity = entities[i]
20710columns = archetype.columns
20720records = archetype.records
20730a = columns[records[A]]
20740b = columns[records[B]]
20750c = columns[records[C]]
2076N/Aend
2077N/A
20780local row = i
20790i -= 1
2080N/A
20810return entity, a[row], b[row], c[row]
2082N/Aend
20830elseif not E then
20840function world_query_iter_next(): any
20850local entity = entities[i]
20860while entity == nil do
20870lastArchetype += 1
20880archetype = compatible_archetypes[lastArchetype]
20890if not archetype then
20900return nil
2091N/Aend
2092N/A
20930entities = archetype.entities
20940i = #entities
20950if i == 0 then
20960continue
2097N/Aend
20980entity = entities[i]
20990columns = archetype.columns
21000records = archetype.records
21010a = columns[records[A]]
21020b = columns[records[B]]
21030c = columns[records[C]]
21040d = columns[records[D]]
2105N/Aend
2106N/A
21070local row = i
21080i -= 1
2109N/A
21100return entity, a[row], b[row], c[row], d[row]
2111N/Aend
21120elseif not F then
21130function world_query_iter_next(): any
21140local entity = entities[i]
21150while entity == nil do
21160lastArchetype += 1
21170archetype = compatible_archetypes[lastArchetype]
21180if not archetype then
21190return nil
2120N/Aend
2121N/A
21220entities = archetype.entities
21230i = #entities
21240if i == 0 then
21250continue
2126N/Aend
21270entity = entities[i]
21280columns = archetype.columns
21290records = archetype.records
21300a = columns[records[A]]
21310b = columns[records[B]]
21320c = columns[records[C]]
21330d = columns[records[D]]
21340e = columns[records[E]]
2135N/Aend
2136N/A
21370local row = i
21380i -= 1
2139N/A
21400return entity, a[row], b[row], c[row], d[row], e[row]
2141N/Aend
21420elseif not G then
21430function world_query_iter_next(): any
21440local entity = entities[i]
21450while entity == nil do
21460lastArchetype += 1
21470archetype = compatible_archetypes[lastArchetype]
21480if not archetype then
21490return nil
2150N/Aend
2151N/A
21520entities = archetype.entities
21530i = #entities
21540if i == 0 then
21550continue
2156N/Aend
21570entity = entities[i]
21580columns = archetype.columns
21590records = archetype.records
21600a = columns[records[A]]
21610b = columns[records[B]]
21620c = columns[records[C]]
21630d = columns[records[D]]
21640e = columns[records[E]]
21650f = columns[records[F]]
2166N/Aend
2167N/A
21680local row = i
21690i -= 1
2170N/A
21710return entity, a[row], b[row], c[row], d[row], e[row], f[row]
2172N/Aend
21730elseif not H then
21740function world_query_iter_next(): any
21750local entity = entities[i]
21760while entity == nil do
21770lastArchetype += 1
21780archetype = compatible_archetypes[lastArchetype]
21790if not archetype then
21800return nil
2181N/Aend
2182N/A
21830entities = archetype.entities
21840i = #entities
21850if i == 0 then
21860continue
2187N/Aend
21880entity = entities[i]
21890columns = archetype.columns
21900records = archetype.records
21910a = columns[records[A]]
21920b = columns[records[B]]
21930c = columns[records[C]]
21940d = columns[records[D]]
21950e = columns[records[E]]
21960f = columns[records[F]]
21970g = columns[records[G]]
2198N/Aend
2199N/A
22000local row = i
22010i -= 1
2202N/A
22030return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
2204N/Aend
22050elseif not I then
22060function world_query_iter_next(): any
22070local entity = entities[i]
22080while entity == nil do
22090lastArchetype += 1
22100archetype = compatible_archetypes[lastArchetype]
22110if not archetype then
22120return nil
2213N/Aend
2214N/A
22150entities = archetype.entities
22160i = #entities
22170if i == 0 then
22180continue
2219N/Aend
22200entity = entities[i]
22210columns = archetype.columns
22220records = archetype.records
22230a = columns[records[A]]
22240b = columns[records[B]]
22250c = columns[records[C]]
22260d = columns[records[D]]
22270e = columns[records[E]]
22280f = columns[records[F]]
22290g = columns[records[G]]
22300h = columns[records[H]]
2231N/Aend
2232N/A
22330local row = i
22340i -= 1
2235N/A
22360return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
2237N/Aend
22380else
22390local queryOutput = {}
22400function world_query_iter_next(): any
22410local entity = entities[i]
22420while entity == nil do
22430lastArchetype += 1
22440archetype = compatible_archetypes[lastArchetype]
22450if not archetype then
22460return nil
2247N/Aend
2248N/A
22490entities = archetype.entities
22500i = #entities
22510if i == 0 then
22520continue
2253N/Aend
22540entity = entities[i]
22550columns = archetype.columns
22560records = archetype.records
2257N/Aend
2258N/A
22590local row = i
22600i -= 1
2261N/A
22620if not F then
22630return entity, a[row], b[row], c[row], d[row], e[row]
22640elseif not G then
22650return entity, a[row], b[row], c[row], d[row], e[row], f[row]
22660elseif not H then
22670return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
22680elseif not I then
22690return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
2270N/Aend
2271N/A
22720for j, id in ids do
22730queryOutput[j] = columns[records[id]][row]
2274N/Aend
2275N/A
22760return entity, unpack(queryOutput)
2277N/Aend
2278N/Aend
2279N/A
22806local cached_query = query :: any
22816cached_query.archetypes = query_archetypes
22826cached_query.__iter = cached_query_iter
22836cached_query.iter = cached_query_iter
22840setmetatable(cached_query, cached_query)
22850return cached_query
2286N/Aend
2287N/A
22881local Query = {}
22891Query.__index = Query
22901Query.__iter = query_iter
22911Query.iter = query_iter_init
22921Query.without = query_without
22931Query.with = query_with
22940Query.archetypes = query_archetypes
22951Query.cached = query_cached
2296N/A
229733local function world_query(world: ecs_world_t, ...)
22980local compatible_archetypes = {}
229933local length = 0
2300N/A
230133local ids = { ... }
2302N/A
230333local archetypes = world.archetypes
2304N/A
23050local idr: ecs_id_record_t?
230633local component_index = world.component_index
2307N/A
230833local q = setmetatable({
230933ids = ids,
231033compatible_archetypes = compatible_archetypes,
23110world = world,
231233}, Query)
2313N/A
231447for _, id in ids do
23157local map = component_index[id]
23160if not map then
23170return q
2318N/Aend
2319N/A
23200if idr == nil or map.size < idr.size then
23210idr = map
2322N/Aend
2323N/Aend
2324N/A
23250if not idr then
23260return q
2327N/Aend
2328N/A
232948for archetype_id in idr.cache do
23300local compatibleArchetype = archetypes[archetype_id]
23310if #compatibleArchetype.entities == 0 then
233234continue
2333N/Aend
233434local records = compatibleArchetype.records
2335N/A
233634local skip = false
2337N/A
233847for i, id in ids do
23390local tr = records[id]
23400if not tr then
23410skip = true
23420break
2343N/Aend
2344N/Aend
2345N/A
23460if skip then
23470continue
2348N/Aend
2349N/A
23500length += 1
23510compatible_archetypes[length] = compatibleArchetype
2352N/Aend
2353N/A
23540return q
2355N/Aend
2356N/A
23574local function world_each(world: ecs_world_t, id: i53): () -> ()
23580local idr = world.component_index[id]
23590if not idr then
23600return NOOP
2361N/Aend
2362N/A
23634local idr_cache = idr.cache
23644local archetypes = world.archetypes
23654local archetype_id = next(idr_cache, nil) :: number
23660local archetype = archetypes[archetype_id]
23670if not archetype then
23680return NOOP
2369N/Aend
2370N/A
23710local entities = archetype.entities
23724local row = #entities
2373N/A
237412return function(): any
237511local entity = entities[row]
237611while not entity do
23774archetype_id = next(idr_cache, archetype_id) :: number
23780if not archetype_id then
23797return
2380N/Aend
23817archetype = archetypes[archetype_id]
23827entities = archetype.entities
23830row = #entities
23848entity = entities[row]
2385N/Aend
23860row -= 1
23870return entity
2388N/Aend
2389N/Aend
2390N/A
23910local function world_children(world: ecs_world_t, parent: i53)
23920return world_each(world, ECS_PAIR(EcsChildOf, parent))
2393N/Aend
2394N/A
23950export type Record = {
23960archetype: Archetype,
23970row: number,
23980dense: i24,
23990}
24000export type ComponentRecord = {
24010cache: { [Id]: number },
24020counts: { [Id]: number },
24030flags: number,
24040size: number,
24050hooks: {
24060on_add: ((entity: Entity) -> ())?,
24070on_set: ((entity: Entity, data: any) -> ())?,
24080on_remove: ((entity: Entity) -> ())?,
24090},
24100}
24110export type ComponentIndex = Map
24120export type Archetypes = { [Id]: Archetype }
2413N/A
24140export type EntityIndex = {
24150dense_array: Map,
24160sparse_array: Map,
24170alive_count: number,
24180max_id: number,
24191}
2420N/A
24210local World = {}
24221World.__index = World
2423N/A
24241World.entity = world_entity
24251World.query = world_query
24261World.remove = world_remove
24271World.clear = world_clear
24281World.delete = world_delete
24291World.component = world_component
24301World.add = world_add
24311World.set = world_set
24321World.get = world_get
24331World.has = world_has
24341World.target = world_target
24351World.parent = world_parent
24361World.contains = world_contains
24371World.cleanup = world_cleanup
24380World.each = world_each
24391World.children = world_children
2440N/A
244173local function world_new()
244273local entity_index = {
244373dense_array = {},
244473sparse_array = {},
24450alive_count = 0,
244673max_id = 0,
244773} :: ecs_entity_index_t
244873local self = setmetatable({
244973archetype_index = {} :: { [string]: Archetype },
245073archetypes = {} :: Archetypes,
245173component_index = {} :: ComponentIndex,
24520entity_index = entity_index,
245373ROOT_ARCHETYPE = (nil :: any) :: Archetype,
2454N/A
24550max_archetype_id = 0,
245673max_component_id = 0,
2457N/A
24580observable = {} :: Observable,
245973}, World) :: any
2460N/A
246173self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
2462N/A
246318688for i = 1, HI_COMPONENT_ID do
24640local e = entity_index_new_id(entity_index)
24650world_add(self, e, EcsComponent)
2466N/Aend
2467N/A
24681022for i = HI_COMPONENT_ID + 1, EcsRest do
2469N/A-- Initialize built-in components
24700entity_index_new_id(entity_index)
2471N/Aend
2472N/A
247373world_add(self, EcsName, EcsComponent)
247473world_add(self, EcsOnSet, EcsComponent)
247573world_add(self, EcsOnAdd, EcsComponent)
247673world_add(self, EcsOnRemove, EcsComponent)
24770world_add(self, EcsWildcard, EcsComponent)
247873world_add(self, EcsRest, EcsComponent)
2479N/A
248073world_set(self, EcsOnAdd, EcsName, "jecs.OnAdd")
248173world_set(self, EcsOnRemove, EcsName, "jecs.OnRemove")
248273world_set(self, EcsOnSet, EcsName, "jecs.OnSet")
248373world_set(self, EcsWildcard, EcsName, "jecs.Wildcard")
248473world_set(self, EcsChildOf, EcsName, "jecs.ChildOf")
248573world_set(self, EcsComponent, EcsName, "jecs.Component")
248673world_set(self, EcsOnDelete, EcsName, "jecs.OnDelete")
248773world_set(self, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget")
248873world_set(self, EcsDelete, EcsName, "jecs.Delete")
248973world_set(self, EcsRemove, EcsName, "jecs.Remove")
24900world_set(self, EcsName, EcsName, "jecs.Name")
249173world_set(self, EcsRest, EcsRest, "jecs.Rest")
2492N/A
249373world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
2494N/A
24950return self
2496N/Aend
2497N/A
24980World.new = world_new
2499N/A
25000export type Entity = { __T: T }
25010export type Id = { __T: T }
25020export type Pair = Id

25030type ecs_id_t = Id | Pair | Pair<"Tag", T>
25040export type Item = (self: Query) -> (Entity, T...)
25050export type Iter = (query: Query) -> () -> (Entity, T...)
2506N/A
25070export type Query = typeof(setmetatable({}, {
25080__iter = (nil :: any) :: Iter,
25090})) & {
25100iter: Iter,
25110with: (self: Query, ...Id) -> Query,
25120without: (self: Query, ...Id) -> Query,
25130archetypes: (self: Query) -> { Archetype },
25140cached: (self: Query) -> Query,
25150}
2516N/A
25170export type Observer = {
25180callback: (archetype: Archetype) -> (),
25190query: QueryInner,
25200}
2521N/A
25220export type Observable = {
25230[Id]: {
25240[Id]: {
25250{ Observer }
25260}
25270}
25280}
2529N/A
25300export type World = {
25310archetype_index: { [string]: Archetype },
25320archetypes: Archetypes,
25330component_index: ComponentIndex,
25340entity_index: EntityIndex,
25350ROOT_ARCHETYPE: Archetype,
2536N/A
25370max_component_id: number,
25380max_archetype_id: number,
2539N/A
25400observable: any,
2541N/A
2542N/A--- Creates a new entity
25430entity: (self: World, id: Entity?) -> Entity,
2544N/A--- Creates a new entity located in the first 256 ids.
2545N/A--- These should be used for static components for fast access.
25460component: (self: World) -> Entity,
2547N/A--- Gets the target of an relationship. For example, when a user calls
2548N/A--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
25490target: (self: World, id: Entity, relation: Id, index: number?) -> Entity?,
2550N/A--- Deletes an entity and all it's related components and relationships.
25510delete: (self: World, id: Entity) -> (),
2552N/A
2553N/A--- Adds a component to the entity with no value
25540add: (self: World, id: Entity, component: Id) -> (),
2555N/A--- Assigns a value to a component on the given entity
25560set: (self: World, id: Entity, component: Id, data: T) -> (),
2557N/A
25580cleanup: (self: World) -> (),
2559N/A-- Clears an entity from the world
25600clear: (self: World, id: Entity) -> (),
2561N/A--- Removes a component from the given entity
25620remove: (self: World, id: Entity, component: Id) -> (),
2563N/A--- Retrieves the value of up to 4 components. These values may be nil.
25640get: ((self: World, id: Entity, Id) -> A?)
25650& ((self: World, id: Entity, Id, Id) -> (A?, B?))
25660& ((self: World, id: Entity, Id, Id, Id) -> (A?, B?, C?))
25670& (self: World, id: Entity, Id, Id, Id, Id) -> (A?, B?, C?, D?),
2568N/A
2569N/A--- Returns whether the entity has the ID.
25700has: (self: World, entity: Entity, ...Id) -> boolean,
2571N/A
2572N/A--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
25730parent:(self: World, entity: Entity) -> Entity,
2574N/A
2575N/A--- Checks if the world contains the given entity
25760contains:(self: World, entity: Entity) -> boolean,
2577N/A
25780each: (self: World, id: Id) -> () -> Entity,
2579N/A
25800children: (self: World, id: Id) -> () -> Entity,
2581N/A
2582N/A--- Searches the world for entities that match a given query
25830query: ((World, Id) -> Query)
25840& ((World, Id, Id) -> Query)
25850& ((World, Id, Id, Id) -> Query)
25860& ((World, Id, Id, Id, Id) -> Query)
25870& ((World, Id, Id, Id, Id, Id) -> Query)
25880& ((World, Id, Id, Id, Id, Id, Id) -> Query)
25890& ((World, Id, Id, Id, Id, Id, Id, Id) -> Query)
25900& ((World, Id, Id, Id, Id, Id, Id, Id, Id, ...Id) -> Query)
25910}
2592N/A-- type function ecs_id_t(entity)
2593N/A-- local ty = entity:components()[2]
2594N/A-- local __T = ty:readproperty(types.singleton("__T"))
2595N/A-- if not __T then
2596N/A-- return ty:readproperty(types.singleton("__jecs_pair_value"))
2597N/A-- end
2598N/A-- return __T
2599N/A-- end
2600N/A
2601N/A-- type function ecs_pair_t(first, second)
2602N/A-- if ecs_id_t(first):is("nil") then
2603N/A-- return second
2604N/A-- else
2605N/A-- return first
2606N/A-- end
2607N/A-- end
2608N/A
26091return {
26100World = World :: { new: () -> World },
26111world = World.new :: () -> World,
2612N/A
26131OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>,
26141OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>,
26151OnSet = EcsOnSet :: Entity<(entity: Entity, data: any) -> ()>,
26161ChildOf = EcsChildOf :: Entity,
26171Component = EcsComponent :: Entity,
26181Wildcard = EcsWildcard :: Entity,
26191w = EcsWildcard :: Entity,
26201OnDelete = EcsOnDelete :: Entity,
26211OnDeleteTarget = EcsOnDeleteTarget :: Entity,
26221Delete = EcsDelete :: Entity,
26231Remove = EcsRemove :: Entity,
26240Name = EcsName :: Entity,
26251Rest = EcsRest :: Entity,
2626N/A
26270pair = (ECS_PAIR :: any) :: (first: Id

, second: Id) -> Pair,

2628N/A
2629N/A-- Inwards facing API for testing
26301ECS_ID = ECS_ENTITY_T_LO,
26311ECS_GENERATION_INC = ECS_GENERATION_INC,
26320ECS_GENERATION = ECS_GENERATION,
26331ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
2634N/A
26351ECS_ID_DELETE = ECS_ID_DELETE,
2636N/A
26371IS_PAIR = ECS_IS_PAIR,
26381pair_first = ecs_pair_first,
26390pair_second = ecs_pair_second,
26401entity_index_get_alive = entity_index_get_alive,
2641N/A
26421archetype_append_to_records = archetype_append_to_records,
26431id_record_ensure = id_record_ensure,
26441archetype_create = archetype_create,
26451archetype_ensure = archetype_ensure,
26461find_insert = find_insert,
26471find_archetype_with = find_archetype_with,
26481find_archetype_without = find_archetype_without,
26491archetype_init_edge = archetype_init_edge,
26501archetype_ensure_edge = archetype_ensure_edge,
26511init_edge_for_add = init_edge_for_add,
26521init_edge_for_remove = init_edge_for_remove,
26531create_edge_for_add = create_edge_for_add,
26541create_edge_for_remove = create_edge_for_remove,
26550archetype_traverse_add = archetype_traverse_add,
26561archetype_traverse_remove = archetype_traverse_remove,
2657N/A
26581entity_move = entity_move,
2659N/A
26601entity_index_try_get = entity_index_try_get,
26611entity_index_try_get_any = entity_index_try_get_any,
26621entity_index_try_get_fast = entity_index_try_get_fast,
26630entity_index_is_alive = entity_index_is_alive,
26641entity_index_new_id = entity_index_new_id,
2665N/A
26661query_iter = query_iter,
26671query_iter_init = query_iter_init,
26681query_with = query_with,
26691query_without = query_without,
26700query_archetypes = query_archetypes,
26711query_match = query_match,
2672N/A
26730find_observers = find_observers,
26740}
\ No newline at end of file diff --git a/docs/public/coverage/lifetime_tracker.luau.html b/docs/public/coverage/lifetime_tracker.luau.html deleted file mode 100644 index 2c66b17..0000000 --- a/docs/public/coverage/lifetime_tracker.luau.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - -

lifetime_tracker.luau Coverage

-

Total Execution Hits: 1

-

Function Coverage Overview: 9.09%

- -
-

Function Coverage:

- - - - - - - - - - - -
FunctionHits
1
print_centered_entity:120
name:260
pad:300
lifetime_tracker_add:360
:480
:620
:890
:1350
:1640
:1750
-

Source Code:

- - - - - -> - -> - - -> - - - -> - -> - - -> - -> - -> -> - - -> - - - - -> -> -> - - - - -> - -> - -> - -> - - - - -> - - - - - - -> - - -> - - - - - -> - - - - - - - -> - -> -> - - - - - -> -> - -> - - - - - -> - - - -> -> - - - - -> - - - -> -> - - - - - - - -> - -> -> -> - -> -> - - - - - -> - -> -> -> -> - - -> -> -> - - - - - - -> - - - - - - - - -> -> - -> - - -> - - - - - - - - - -> -> -> - - - -> - - - - - - - -> - - - -> - - - - - - - -> - - - - - - -> -> -> -> -> -> - - -> - -> -> - -
LineHitsCode
11local jecs = require("@jecs")
21local ECS_GENERATION = jecs.ECS_GENERATION
31local ECS_ID = jecs.ECS_ID
41local __ = jecs.Wildcard
51local pair = jecs.pair
6N/A
71local prettify = require("@tools/entity_visualiser").prettify
8N/A
91local pe = prettify
101local ansi = require("@tools/ansi")
11N/A
121function print_centered_entity(entity, width: number)
130local entity_str = tostring(entity)
140local entity_length = #entity_str
15N/A
160local padding_total = width - 2 - entity_length
17N/A
180local padding_left = math.floor(padding_total / 2)
190local padding_right = padding_total - padding_left
20N/A
210local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right)
22N/A
230print("|" .. centered_str .. "|")
24N/Aend
25N/A
261local function name(world, e)
270return world:get(world, e, jecs.Name) or pe(e)
28N/Aend
291local padding_enabled = false
301local function pad()
310if padding_enabled then
320print("")
33N/Aend
34N/Aend
35N/A
361local function lifetime_tracker_add(world: jecs.World, opt)
370local entity_index = world.entity_index
380local dense_array = entity_index.dense_array
390local component_index = world.component_index
40N/A
410local ENTITY_RANGE = (jecs.Rest :: any) + 1
42N/A
430local w = setmetatable({}, { __index = world })
44N/A
450padding_enabled = opt.padding_enabled
46N/A
470local world_entity = world.entity
480w.entity = function(self, entity)
490if entity then
500return world_entity(world, entity)
51N/Aend
520local will_recycle = entity_index.max_id ~= entity_index.alive_count
530local e = world_entity(world)
540if will_recycle then
550print(`*recycled {pe(e)}`)
560else
570print(`*created {pe(e)}`)
58N/Aend
590pad()
600return e
61N/Aend
620w.print_entity_index = function(self)
630local max_id = entity_index.max_id
640local alive_count = entity_index.alive_count
650local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {})
660local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
67N/A
680local sep = "|--------|"
690if #alive > 0 then
700print("|-alive--|")
710for i = 1, #alive do
720local e = pe(alive[i])
730print_centered_entity(e, 32)
740print(sep)
75N/Aend
760print("\n")
77N/Aend
78N/A
790if #dead > 0 then
800print("|--dead--|")
810for i = 1, #dead do
820print_centered_entity(pe(dead[i]), 32)
830print(sep)
84N/Aend
85N/Aend
860pad()
87N/Aend
880local timelines = {}
890w.print_snapshot = function(self)
900local timeline = #timelines + 1
910local entity_column_width = 10
920local status_column_width = 8
93N/A
940local header = string.format("| %-" .. entity_column_width .. "s |", "Entity")
950for i = 1, timeline do
960header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i))
97N/Aend
98N/A
990local max_id = entity_index.max_id
1000local alive_count = entity_index.alive_count
1010local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {})
1020local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
103N/A
1040local data = {}
1050print("-------------------------------------------------------------------")
1060print(header)
107N/A
108N/A-- Store the snapshot data for this timeline
1090for i = ENTITY_RANGE, max_id do
1100if dense_array[i] then
1110local entity = dense_array[i]
1120local id = ECS_ID(entity)
1130local status = "alive"
1140if not world:contains(entity) then
1150status = "dead"
116N/Aend
1170data[id] = status
118N/Aend
119N/Aend
120N/A
1210table.insert(timelines, data)
122N/A
123N/A-- Create a table to hold entity data for sorting
1240local entities = {}
1250for i = ENTITY_RANGE, max_id do
1260if dense_array[i] then
1270local entity = dense_array[i]
1280local id = ECS_ID(entity)
129N/A-- Push entity and id into the new `entities` table
1300table.insert(entities, { entity = entity, id = id })
131N/Aend
132N/Aend
133N/A
134N/A-- Sort the entities by ECS_ID
1350table.sort(entities, function(a, b)
1360return a.id < b.id
137N/Aend)
138N/A
139N/A-- Print the sorted rows
1400for _, entity_data in ipairs(entities) do
1410local entity = entity_data.entity
1420local id = entity_data.id
1430local status = "alive"
1440if id > alive_count then
1450status = "dead"
146N/Aend
1470local row = string.format("| %-" .. entity_column_width .. "s |", pe(entity))
1480for j = 1, timeline do
1490local timeline_data = timelines[j]
1500local entity_data = timeline_data[id]
1510if entity_data then
1520row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data)
1530else
1540row = row .. string.format(" %-" .. status_column_width .. "s |", "-")
155N/Aend
156N/Aend
1570print(row)
158N/Aend
1590print("-------------------------------------------------------------------")
1600pad()
161N/Aend
1620local world_add = world.add
1630local relations = {}
1640w.add = function(self, entity: any, component: any)
1650world_add(world, entity, component)
1660if jecs.IS_PAIR(component) then
1670local relation = jecs.pair_first(world, component)
1680local target = jecs.pair_second(world, component)
1690print(`*added ({pe(relation)}, {pe(target)}) to {pe(entity)}`)
1700pad()
171N/Aend
172N/Aend
173N/A
1740local world_delete = world.delete
1750w.delete = function(self, e)
1760world_delete(world, e)
177N/A
1780local idr_t = component_index[pair(__, e)]
1790if idr_t then
1800for archetype_id in idr_t.cache do
1810local archetype = world.archetypes[archetype_id]
1820for _, id in archetype.types do
1830if not jecs.IS_PAIR(id) then
1840continue
185N/Aend
1860local object = jecs.pair_second(world, id)
1870if object ~= e then
1880continue
189N/Aend
1900local id_record = component_index[id]
1910local flags = id_record.flags
1920local flags_delete_mask: number = bit32.band(flags, jecs.ECS_ID_DELETE)
1930if flags_delete_mask ~= 0 then
1940for _, entity in archetype.entities do
1950print(`*deleted dependant {pe(entity)} of {pe(e)}`)
1960pad()
197N/Aend
1980break
1990else
2000for _, entity in archetype.entities do
2010print(
2020`*removed dependency ({pe(jecs.pair_first(world, id))}, {pe(object)}) from {pe(entity)}`
2030)
204N/Aend
205N/Aend
206N/Aend
207N/Aend
208N/Aend
209N/A
2100print(`*deleted {pe(e)}`)
2110pad()
212N/Aend
2130return w
214N/Aend
215N/A
2161return lifetime_tracker_add
\ No newline at end of file diff --git a/docs/public/coverage/testkit.luau.html b/docs/public/coverage/testkit.luau.html deleted file mode 100644 index bb57907..0000000 --- a/docs/public/coverage/testkit.luau.html +++ /dev/null @@ -1,617 +0,0 @@ - - - - -

testkit.luau Coverage

-

Total Execution Hits: 1826

-

Function Coverage Overview: 64.52%

- -
-

Function Coverage:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FunctionHits
1
white_underline:110
white:1524
green:1977
red:23146
yellow:2776
red_highlight:310
green_highlight:350
gray:3984
orange:4373
convert_units:486
output_test_result:13124
CASE:16973
CHECK_EXPECT_ERR:1839
CHECK:2011195
TEST:22424
FOCUS:2370
FINISH:2481
:2640
SKIP:3141
START:3301
BENCH:3423
:3540
round:3726
print2:3960
tos:4010
shallow_eq:4800
deep_eq:5000
test:5331
benchmark:5451
disable_formatting:5490
-

Source Code:

-> -> -> -> -> -> -> - -> - - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> - -> - - - -> - - - - - - - - - - - -> - - - - - - - - - - - -> - -> - - - -> -> - - - -> -> - - - - - - -> -> - -> -> - -> -> -> -> -> - - - - - - - - - - - -> - - - - - - -> - -> - - - - -> - - - - - -> -> - - -> -> -> - -> - - - - - - - - -> - - - -> - -> -> - - - - - -> -> -> - - - -> - - - - - -> - - -> -> - - - - - - -> - - - - - - -> - -> -> -> - - -> - -> - - - -> -> - -> - - - - -> - -> -> - -> -> - -> - - - - - - - -> - -> -> - - -> - - - - - -> -> -> - - - - - - - -> - - - -> - - - - - - -> - -> - - -> - -> - - - -> - -> -> - - - - - - -> - - - -> - - - -> -> -> - -> -> - - - -> -> - -> - -> - - -> -> - - -> -> -> -> -> -> - - - - - -> - -> - - - - - -> - - - - -> -> - - - -> - - - -> - - - -> - - -> -> - - -> - - - - - - -> - - - -> - - - -> -> - - - - - - - - - - -> -> - -> -> -> -> -> -> - - - -> -> - - - -> - - - - - - - - - - - - - - - - -> - - - - - - - -> -> - - - - -> - - - - -> - - - - - - - - -> -> - - -> - -> - -> - - - -> -> - - - - -> -> -> - - - - -> -> -> -> -> -> - - - -> -> - - - -> -> -> - - - -> -> -> - -> -> - - - -> -> - - - - -> - - -> -> -> - - - - -> - - -> -> -> - -> -> -> -> -> -> - - - - - - - - - - - -> -> - - -> -> - - -> -> - -> - - -> - - -
LineHitsCode
1N/A--------------------------------------------------------------------------------
2N/A-- testkit.luau
3N/A-- v0.7.3
4N/A-- MIT License
5N/A-- Copyright (c) 2022 centau
6N/A--------------------------------------------------------------------------------
7N/A
81local disable_ansi = false
9N/A
101local color = {
111white_underline = function(s: string): string
120return if disable_ansi then s else `\27[1;4m{s}\27[0m`
13N/Aend,
14N/A
151white = function(s: string): string
1624return if disable_ansi then s else `\27[37;1m{s}\27[0m`
17N/Aend,
18N/A
191green = function(s: string): string
2077return if disable_ansi then s else `\27[32;1m{s}\27[0m`
21N/Aend,
22N/A
231red = function(s: string): string
24146return if disable_ansi then s else `\27[31;1m{s}\27[0m`
25N/Aend,
26N/A
271yellow = function(s: string): string
2876return if disable_ansi then s else `\27[33;1m{s}\27[0m`
29N/Aend,
30N/A
311red_highlight = function(s: string): string
320return if disable_ansi then s else `\27[41;1;30m{s}\27[0m`
33N/Aend,
34N/A
351green_highlight = function(s: string): string
360return if disable_ansi then s else `\27[42;1;30m{s}\27[0m`
37N/Aend,
38N/A
391gray = function(s: string): string
4084return if disable_ansi then s else `\27[38;1m{s}\27[0m`
41N/Aend,
42N/A
431orange = function(s: string): string
4473return if disable_ansi then s else `\27[38;5;208m{s}\27[0m`
45N/Aend,
460}
47N/A
481local function convert_units(unit: string, value: number): (number, string)
496local sign = math.sign(value)
506value = math.abs(value)
51N/A
526local prefix_colors = {
536[4] = color.red,
546[3] = color.red,
556[2] = color.yellow,
566[1] = color.yellow,
576[0] = color.green,
586[-1] = color.red,
596[-2] = color.yellow,
606[-3] = color.green,
616[-4] = color.red,
620}
63N/A
646local prefixes = {
656[4] = "T",
666[3] = "G",
676[2] = "M",
686[1] = "k",
696[0] = " ",
706[-1] = "m",
716[-2] = "u",
726[-3] = "n",
736[-4] = "p",
740}
75N/A
766local order = 0
77N/A
787while value >= 1000 do
791order += 1
801value /= 1000
81N/Aend
82N/A
8311while value ~= 0 and value < 1 do
847order -= 1
857value *= 1000
86N/Aend
87N/A
886if value >= 100 then
891value = math.floor(value)
905elseif value >= 10 then
912value = math.floor(value * 1e1) / 1e1
923elseif value >= 1 then
931value = math.floor(value * 1e2) / 1e2
94N/Aend
95N/A
966return value * sign, prefix_colors[order](prefixes[order] .. unit)
97N/Aend
98N/A
991local WALL = color.gray("β”‚")
100N/A
101N/A--------------------------------------------------------------------------------
102N/A-- Testing
103N/A--------------------------------------------------------------------------------
104N/A
1050type Test = {
1060name: string,
1070case: Case?,
1080cases: { Case },
1090duration: number,
1100error: {
1110message: string,
1120trace: string,
1130}?,
1140focus: boolean,
1150}
116N/A
1170type Case = {
1180name: string,
1190result: number,
1200line: number?,
1210focus: boolean,
1220}
123N/A
1241local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5
125N/A
1261local check_for_focused = false
1271local skip = false
1281local test: Test?
1291local tests: { Test } = {}
130N/A
1311local function output_test_result(test: Test)
13224if check_for_focused then
1330local any_focused = test.focus
1340for _, case in test.cases do
1350any_focused = any_focused or case.focus
136N/Aend
137N/A
1380if not any_focused then
1390return
140N/Aend
141N/Aend
142N/A
14324print(color.white(test.name))
144N/A
14524for _, case in test.cases do
14673local status = ({
14773[PASS] = color.green("PASS"),
14873[FAIL] = color.red("FAIL"),
14973[NONE] = color.orange("NONE"),
15073[ERROR] = color.red("FAIL"),
15173[SKIPPED] = color.yellow("SKIP"),
15273})[case.result]
153N/A
15473local line = case.result == FAIL and color.red(`{case.line}:`) or ""
15573if check_for_focused and case.focus == false and test.focus == false then
1560continue
157N/Aend
15873print(`{status}{WALL} {line}{color.gray(case.name)}`)
159N/Aend
160N/A
16124if test.error then
1620print(color.gray("error: ") .. color.red(test.error.message))
1630print(color.gray("trace: ") .. color.red(test.error.trace))
1640else
16524print()
166N/Aend
167N/Aend
168N/A
1691local function CASE(name: string)
17073skip = false
17173assert(test, "no active test")
172N/A
17373local case = {
17473name = name,
17573result = NONE,
17673focus = false,
1770}
178N/A
17973test.case = case
18073table.insert(test.cases, case)
181N/Aend
182N/A
1831local function CHECK_EXPECT_ERR(fn, ...)
1849assert(test, "no active test")
1859local case = test.case
1869if not case then
1870CASE("")
1880case = test.case
189N/Aend
1909assert(case, "no active case")
1919if case.result ~= FAIL then
1929local ok, err = pcall(fn, ...)
1939case.result = if ok then FAIL else PASS
1949if skip then
1950case.result = SKIPPED
196N/Aend
1979case.line = debug.info(stack and stack + 1 or 2, "l")
198N/Aend
199N/Aend
200N/A
2011local function CHECK(value: T, stack: number?): T?
2021195assert(test, "no active test")
203N/A
2041195local case = test.case
205N/A
2061195if not case then
2079CASE("")
2089case = test.case
209N/Aend
210N/A
2111195assert(case, "no active case")
212N/A
2131195if case.result ~= FAIL then
2141195case.result = value and PASS or FAIL
2151195if skip then
2161case.result = SKIPPED
217N/Aend
2181195case.line = debug.info(stack and stack + 1 or 2, "l")
219N/Aend
220N/A
2211195return value
222N/Aend
223N/A
2241local function TEST(name: string, fn: () -> ())
225N/A
22624test = {
22724name = name,
22824cases = {},
22924duration = 0,
23024focus = false,
23124fn = fn
2320}
233N/A
23424table.insert(tests, test)
235N/Aend
236N/A
2371local function FOCUS()
2380assert(test, "no active test")
239N/A
2400check_for_focused = true
2410if test.case then
2420test.case.focus = true
2430else
2440test.focus = true
245N/Aend
246N/Aend
247N/A
2481local function FINISH(): boolean
2491local success = true
2501local total_cases = 0
2511local passed_cases = 0
2521local passed_focus_cases = 0
2531local total_focus_cases = 0
2541local duration = 0
255N/A
2561for _, t in tests do
25724if check_for_focused and not t.focus then
2580continue
259N/Aend
26024test = t
26124fn = t.fn
26224local start = os.clock()
26324local err
26424local success = xpcall(fn, function(m: string)
2650err = { message = m, trace = debug.traceback(nil, 2) }
266N/Aend)
26724test.duration = os.clock() - start
268N/A
26924if not test.case then
2700CASE("")
271N/Aend
27224assert(test.case, "no active case")
273N/A
27424if not success then
2750test.case.result = ERROR
2760test.error = err
277N/Aend
27824collectgarbage()
279N/Aend
280N/A
2811for _, test in tests do
28224duration += test.duration
28324for _, case in test.cases do
28473total_cases += 1
28573if case.focus or test.focus then
2860total_focus_cases += 1
287N/Aend
28873if case.result == PASS or case.result == NONE or case.result == SKIPPED then
28973if case.focus or test.focus then
2900passed_focus_cases += 1
291N/Aend
29273passed_cases += 1
2930else
2940success = false
295N/Aend
296N/Aend
297N/A
29824output_test_result(test)
299N/Aend
300N/A
3011print(color.gray(string.format(`{passed_cases}/{total_cases} test cases passed in %.3f ms.`, duration * 1e3)))
3021if check_for_focused then
3030print(color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`))
304N/Aend
305N/A
3061local fails = total_cases - passed_cases
307N/A
3081print((fails > 0 and color.red or color.green)(`{fails} {fails == 1 and "fail" or "fails"}`))
309N/A
3101check_for_focused = false
3111return success, table.clear(tests)
312N/Aend
313N/A
3141local function SKIP()
3151skip = true
316N/Aend
317N/A
318N/A--------------------------------------------------------------------------------
319N/A-- Benchmarking
320N/A--------------------------------------------------------------------------------
321N/A
3220type Bench = {
3230time_start: number?,
3240memory_start: number?,
3250iterations: number?,
3260}
327N/A
3281local bench: Bench?
329N/A
3301function START(iter: number?): number
3311local n = iter or 1
3321assert(n > 0, "iterations must be greater than 0")
3331assert(bench, "no active benchmark")
3341assert(not bench.time_start, "clock was already started")
335N/A
3361bench.iterations = n
3371bench.memory_start = gcinfo()
3381bench.time_start = os.clock()
3391return n
340N/Aend
341N/A
3421local function BENCH(name: string, fn: () -> ())
3433local active = bench
3443assert(not active, "a benchmark is already in progress")
345N/A
3463bench = {}
3473assert(bench);
3483(collectgarbage :: any)("collect")
349N/A
3503local mem_start = gcinfo()
3513local time_start = os.clock()
3523local err_msg: string?
353N/A
3543local success = xpcall(fn, function(m: string)
3550err_msg = m .. debug.traceback(nil, 2)
356N/Aend)
357N/A
3583local time_stop = os.clock()
3593local mem_stop = gcinfo()
360N/A
3613if not success then
3620print(`{WALL}{color.red("ERROR")}{WALL} {name}`)
3630print(color.gray(err_msg :: string))
3640else
3653time_start = bench.time_start or time_start
3663mem_start = bench.memory_start or mem_start
367N/A
3683local n = bench.iterations or 1
3693local d, d_unit = convert_units("s", (time_stop - time_start) / n)
3703local a, a_unit = convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
371N/A
3723local function round(x: number): string
3736return x > 0 and x < 10 and (x - math.floor(x)) > 0 and string.format("%2.1f", x)
3746or string.format("%3.f", x)
375N/Aend
376N/A
3773print(
3783string.format(
3793`%s %s %s %s{WALL} %s`,
3803color.gray(round(d)),
3813d_unit,
3823color.gray(round(a)),
3833a_unit,
3843color.gray(name)
3850)
3860)
387N/Aend
388N/A
3893bench = nil
390N/Aend
391N/A
392N/A--------------------------------------------------------------------------------
393N/A-- Printing
394N/A--------------------------------------------------------------------------------
395N/A
3961local function print2(v: unknown)
3970type Buffer = { n: number, [number]: string }
3980type Cyclic = { n: number, [{}]: number }
399N/A
400N/A-- overkill concatenationless string buffer
4010local function tos(value: any, stack: number, str: Buffer, cyclic: Cyclic)
4020local TAB = " "
4030local indent = table.concat(table.create(stack, TAB))
404N/A
4050if type(value) == "string" then
4060local n = str.n
4070str[n + 1] = "\""
4080str[n + 2] = value
4090str[n + 3] = "\""
4100str.n = n + 3
4110elseif type(value) ~= "table" then
4120local n = str.n
4130str[n + 1] = value == nil and "nil" or tostring(value)
4140str.n = n + 1
4150elseif next(value) == nil then
4160local n = str.n
4170str[n + 1] = "{}"
4180str.n = n + 1
4190else -- is table
4200local tabbed_indent = indent .. TAB
421N/A
4220if cyclic[value] then
4230str.n += 1
4240str[str.n] = color.gray(`CYCLIC REF {cyclic[value]}`)
4250return
4260else
4270cyclic.n += 1
4280cyclic[value] = cyclic.n
429N/Aend
430N/A
4310str.n += 3
4320str[str.n - 2] = "{ "
4330str[str.n - 1] = color.gray(tostring(cyclic[value]))
4340str[str.n - 0] = "\n"
435N/A
4360local i, v = next(value, nil)
4370while v ~= nil do
4380local n = str.n
4390str[n + 1] = tabbed_indent
440N/A
4410if type(i) ~= "string" then
4420str[n + 2] = "["
4430str[n + 3] = tostring(i)
4440str[n + 4] = "]"
4450n += 4
4460else
4470str[n + 2] = tostring(i)
4480n += 2
449N/Aend
450N/A
4510str[n + 1] = " = "
4520str.n = n + 1
453N/A
4540tos(v, stack + 1, str, cyclic)
455N/A
4560i, v = next(value, i)
457N/A
4580n = str.n
4590str[n + 1] = v ~= nil and ",\n" or "\n"
4600str.n = n + 1
461N/Aend
462N/A
4630local n = str.n
4640str[n + 1] = indent
4650str[n + 2] = "}"
4660str.n = n + 2
467N/Aend
468N/Aend
469N/A
4700local str = { n = 0 }
4710local cyclic = { n = 0 }
4720tos(v, 0, str, cyclic)
4730print(table.concat(str))
474N/Aend
475N/A
476N/A--------------------------------------------------------------------------------
477N/A-- Equality
478N/A--------------------------------------------------------------------------------
479N/A
4801local function shallow_eq(a: {}, b: {}): boolean
4810if #a ~= #b then
4820return false
483N/Aend
484N/A
4850for i, v in next, a do
4860if b[i] ~= v then
4870return false
488N/Aend
489N/Aend
490N/A
4910for i, v in next, b do
4920if a[i] ~= v then
4930return false
494N/Aend
495N/Aend
496N/A
4970return true
498N/Aend
499N/A
5001local function deep_eq(a: {}, b: {}): boolean
5010if #a ~= #b then
5020return false
503N/Aend
504N/A
5050for i, v in next, a do
5060if type(b[i]) == "table" and type(v) == "table" then
5070if deep_eq(b[i], v) == false then
5080return false
509N/Aend
5100elseif b[i] ~= v then
5110return false
512N/Aend
513N/Aend
514N/A
5150for i, v in next, b do
5160if type(a[i]) == "table" and type(v) == "table" then
5170if deep_eq(a[i], v) == false then
5180return false
519N/Aend
5200elseif a[i] ~= v then
5210return false
522N/Aend
523N/Aend
524N/A
5250return true
526N/Aend
527N/A
528N/A--------------------------------------------------------------------------------
529N/A-- Return
530N/A--------------------------------------------------------------------------------
531N/A
5321return {
5331test = function()
5341return {
5351TEST = TEST,
5361CASE = CASE,
5371CHECK = CHECK,
5381FINISH = FINISH,
5391SKIP = SKIP,
5401FOCUS = FOCUS,
5411CHECK_EXPECT_ERR = CHECK_EXPECT_ERR,
5420}
543N/Aend,
544N/A
5451benchmark = function()
5461return BENCH, START
547N/Aend,
548N/A
5491disable_formatting = function()
5500disable_ansi = true
551N/Aend,
552N/A
5531print = print2,
554N/A
5551seq = shallow_eq,
5561deq = deep_eq,
557N/A
5581color = color,
5590}
\ No newline at end of file diff --git a/docs/public/coverage/tests.luau.html b/docs/public/coverage/tests.luau.html deleted file mode 100644 index e2818e0..0000000 --- a/docs/public/coverage/tests.luau.html +++ /dev/null @@ -1,2044 +0,0 @@ - - - - -

tests.luau Coverage

-

Total Execution Hits: 100

-

Function Coverage Overview: 83.58%

- -
-

Function Coverage:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FunctionHits
1
white_underline:300
white:340
green:380
red:420
yellow:460
red_highlight:500
green_highlight:540
gray:580
pe:630
pp:680
debug_world_inspect:737
record:7417
tbl:7712
archetype:804
records:831
columns:861
row:895
tuple:941
name:1160
:1201
:1361
:1741
:1841
:1891
getTargets:1922
setAttacksAndEats:2123
:2401
:2561
:3121
:3861
:4351
:4461
:4531
:4751
:5141
:5721
:5961
:8961
:10201
:10481
:10851
:11841
:12131
:12311
:12421
:12501
:13251
:13301
:13701
:13911
:15121
:15411
:16391
:16511
:16561
:16671
:16811
:16971
:17111
:17701
updateCooldowns:17772
:18201
:18891
:18971
:19191
:19391
-

Source Code:

- -> - - - - - - - - - - - - - - -> - - - - - -> - -> - - -> - - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> -> - - -> - -> - - - -> -> - - - -> -> - - - -> - - -> - - -> - - -> - - -> - - -> -> -> - - - - -> -> - -> -> - - - - - - - - - -> -> - -> - - -> -> - - - -> - - - - - - - - - -> -> -> - - - - - -> - - - - -> - -> -> - - - - -> -> - - - - - - -> - - -> -> -> - - -> -> - -> - - -> - - - - - - - - - -> -> -> - - -> - - - - - - - - - - - -> -> -> - -> -> - - -> - - - -> -> - - - - - - - - - - -> -> - - -> - - -> - - -> -> -> - - - -> - - - - - -> - - - - -> -> - - - - - -> - - - -> - -> - - -> -> - - - -> - -> - - - -> - - - -> - -> - -> - - - -> - - - - - - - - - - - - - -> -> - - -> - - - - - - - - - -> -> - - - - - - - - -> -> - - - - - -> -> - -> - - -> - - -> - - - -> - -> -> - - - - - - - - - - - - -> -> - - - - - - -> - - - - - - - -> -> -> - - - - -> - -> - - - -> -> - - -> - -> -> - - - - - - - -> - - -> - -> - -> -> -> - - -> - - - - -> - -> - - - -> - - -> -> - - - - -> - -> - - -> - -> -> -> - - - - -> - - -> - - - - -> -> - - -> - - -> -> -> - - - - - - -> - - - - - -> -> - - - - -> - -> - - -> -> - - -> - -> - - -> - - -> -> -> -> - - - - - - - - - - - -> - - - - -> - - - - -> - - - -> - - - -> - -> - - - - -> - - - - - - - - - - - -> - - -> - -> - - - - - - -> - - - - -> - -> - - -> - - - - -> - -> - - - - - - -> -> - - - - - -> -> - - - - - - -> - - - -> - - - -> - -> -> - - -> -> - -> -> - - - - - - - - - - - -> -> - -> - - - - -> - - -> - - -> -> -> - - - - - - -> - - -> - - - - -> - - - -> - -> -> - - - - -> - - - -> - - - -> -> - - - - - - - - - - -> -> -> - - - - - - - - - - - - - -> - -> - - - - - - - - -> -> - -> - -> -> - - - - - -> - - - -> -> -> - - - - -> - -> -> - - - - -> -> - -> -> - - - - - -> - - - -> - - - - - -> -> -> - - -> -> - -> -> - - - - - - -> - - - - -> -> -> - - - - - - -> - -> - - - - -> - - - -> -> -> - - - - - - - - -> - - -> - - - - - - - - -> -> -> - - -> - - - -> - -> -> - - - - - - - -> - - - -> - - -> - - - -> -> - -> - - -> -> -> - - - - - - -> - - - -> -> -> - - - - -> - -> - - - -> - - - - - -> - - - -> - -> -> - - - - - -> - - - - - -> - - - - -> - -> -> - - - - - - -> - - - - - -> - - - -> - -> -> - -> -> - - - - -> - - - - - -> - - - - -> -> - -> -> -> - - -> - - -> - - - -> - - - -> -> - -> -> - -> - - -> - - - -> - -> -> -> - - - - - -> - - - -> - -> - - -> - - - -> - - - - -> - -> -> -> - - - - -> - - -> - -> - - -> - - -> - - - - - - -> - -> - -> - -> - - - -> -> - -> -> - - - - - - - -> - - - -> - - -> - - -> - - -> - - - - - -> -> - - - - - -> - - - - -> - -> - - -> - - - -> - -> - - - -> - - -> - -> - - -> - - - - -> -> - - - - -> - -> - - - -> - - - -> - - - -> - - - - - - -> -> -> -> -> -> - - - -> - -> - - -> - -> -> - - -> - - - - -> - - - - -> - -> -> -> - - - - - -> - - -> -> - - - - - - - - - - -> -> - - - - -> -> -> - - - -> - - -> - - - -> -> - - -> - -> - - - - -> - - - - - - - - - -> - - - - - -> - - - -> - - -> - - - - - - -> - - -> - - -> - - - - - - -> - - - - -> - - -> -> - - -> - - - -> - - - - -> - - - - -> -> - - - -> - - - -> - - - - - -> -> -> - - -> - - - -> - - - -> - - - - - - - -> -> - - -> -> - - - - -> -> - -> - - - - - - - -> -> - - -> -> - - - - - -> -> -> - - - - - - - - - -> - -> - - -> - - - -> - -> - - - -> - - -> - -> - - -> - - -> -> - - - - - - -> - - - -> - -> - - -> - - -> - -> - - - -> -> -> - - - - - - - -> -> - - -> - - - -> - -> - - - -> - - -> - - -> -> -> - - - -> - - - -> - - - - - -> -> - - - - -> -> -> - - -> -> -> - - - - - - - -> - - -> - - - -> -> -> - - - - - - - - - - - - - - -> - - - - - - - -> - - - -> - - - - - - - - - - - - - - - -> - - - - -> - - -> -> - - - - - - - - -> - - - - - -> - - - -> -> - - - -> - - - -> - - - - -> -> - - - - - - -> -> - -> -> -> - - - - -> - - - - -> -> -> - - - - - - - -> - -> -> - - - - -> - - - - -> - -> -> - - -> - - - - - - -> - -> - - -> - -> - - - - -> - - - - -> -> - - - - -> -> -> -> - - - - - -> - - - - -> - - - - -> - - - - -> -> - - - - - -> -> - -> -> - - - - - -> - -> - -> - -> - - - - -> -> - -> - - -> -> -> -> -> - - - - - - -> - - -> - - - -> - - -> - - -> -> -> - - - -> -> -> - - -> -> - - - - -> - -> - - -> - - - -> - -> -> -> - - - - -> - - - -> - - -> - -> - - - - - -> - - - -> - -> - - - -> - - - -> - -> - -> - -> - - - -> - -> - - - -> - - - -> - -> - -> - - -> - - - -> - -> -> -> - - - -> - - -> - - - -> -> - - -> - -> - - - -> - - - -> - -> - -> - - - -> -> - - -> - -> - -> - - - -> - - - -> - - - -> -> - -> - -> - -> -> - -
LineHitsCode
11local jecs = require("@jecs")
2N/A
31local testkit = require("@testkit")
41local BENCH, START = testkit.benchmark()
51local __ = jecs.Wildcard
61local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION
71local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC
81local IS_PAIR = jecs.IS_PAIR
91local pair = jecs.pair
101local ecs_pair_first = jecs.pair_first
111local ecs_pair_second = jecs.pair_second
121local entity_index_try_get_any = jecs.entity_index_try_get_any
131local entity_index_get_alive = jecs.entity_index_get_alive
141local entity_index_is_alive = jecs.entity_index_is_alive
151local ChildOf = jecs.ChildOf
161local world_new = jecs.World.new
17N/A
181local it = testkit.test()
191local TEST, CASE = it.TEST, it.CASE
201local CHECK, FINISH = it.CHECK, it.FINISH
211local SKIP, FOCUS = it.SKIP, it.FOCUS
221local CHECK_EXPECT_ERR = it.CHECK_EXPECT_ERR
23N/A
241local N = 2 ^ 8
25N/A
260type World = jecs.World
270type Entity = jecs.Entity
28N/A
291local c = {
301white_underline = function(s: any)
310return `\27[1;4m{s}\27[0m`
32N/Aend,
33N/A
341white = function(s: any)
350return `\27[37;1m{s}\27[0m`
36N/Aend,
37N/A
381green = function(s: any)
390return `\27[32;1m{s}\27[0m`
40N/Aend,
41N/A
421red = function(s: any)
430return `\27[31;1m{s}\27[0m`
44N/Aend,
45N/A
461yellow = function(s: any)
470return `\27[33;1m{s}\27[0m`
48N/Aend,
49N/A
501red_highlight = function(s: any)
510return `\27[41;1;30m{s}\27[0m`
52N/Aend,
53N/A
541green_highlight = function(s: any)
550return `\27[42;1;30m{s}\27[0m`
56N/Aend,
57N/A
581gray = function(s: any)
590return `\27[30;1m{s}\27[0m`
60N/Aend,
610}
62N/A
631local function pe(e)
640local gen = ECS_GENERATION(e)
650return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
66N/Aend
67N/A
681local function pp(e)
690local gen = ECS_GENERATION(e)
700return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{jecs.ECS_ENTITY_T_HI(e)}`)
71N/Aend
72N/A
731local function debug_world_inspect(world: World)
747local function record(e): jecs.Record
7517return entity_index_try_get_any(world.entity_index, e) :: any
76N/Aend
777local function tbl(e)
7812return record(e).archetype
79N/Aend
807local function archetype(e)
814return tbl(e).type
82N/Aend
837local function records(e)
841return tbl(e).records
85N/Aend
867local function columns(e)
871return tbl(e).columns
88N/Aend
897local function row(e)
905return record(e).row
91N/Aend
92N/A
93N/A-- Important to order them in the order of their columns
947local function tuple(e, ...)
951for i, column in columns(e) do
962if select(i, ...) ~= column[row(e)] then
970return false
98N/Aend
99N/Aend
1001return true
101N/Aend
102N/A
1037return {
1047record = record,
1057tbl = tbl,
1067archetype = archetype,
1077records = records,
1087row = row,
1097tuple = tuple,
1107columns = columns
1110}
112N/Aend
113N/A
1141local dwi = debug_world_inspect
115N/A
1161local function name(world, e)
1170return world:get(e, jecs.Name)
118N/Aend
119N/A
1201TEST("#adding a recycled target", function()
1211local world = world_new()
1221local R = world:component()
123N/A
1241local e = world:entity()
1251local T = world:entity()
1261world:add(e, pair(R, T))
1271world:delete(T)
1281CHECK(not world:has(e, pair(R, T)))
1291local T2 = world:entity()
1301world:add(e, pair(R, T2))
1311CHECK(world:target(e, R) ~= T)
1321CHECK(world:target(e, R) ~= 0)
133N/A
134N/Aend)
135N/A
1361TEST("#repro2", function()
1371local world = world_new()
1381local Lifetime = world:component() :: jecs.Id
1391local Particle = world:entity()
1401local Beam = world:entity()
141N/A
1421local entity = world:entity()
1431world:set(entity, pair(Lifetime, Particle), 1)
1441world:set(entity, pair(Lifetime, Beam), 2)
1451world:set(entity, pair(4, 5), 6) -- noise
146N/A
1471local entity_visualizer = require("@tools/entity_visualiser")
148N/A-- entity_visualizer.components(world, entity)
149N/A
1501for e in world:each(pair(Lifetime, __)) do
1512local i = 0
1522local nth = world:target(e, Lifetime, i)
1532while nth do
154N/A-- entity_visualizer.components(world, e)
155N/A
1562local data = world:get(e, pair(Lifetime, nth))
1572data -= 1
1582if data <= 0 then
1591world:remove(e, pair(Lifetime, nth))
1600else
1611world:set(e, pair(Lifetime, nth), data)
162N/Aend
1632i += 1
1642nth = world:target(e, Lifetime, i)
165N/Aend
166N/Aend
167N/A
1681CHECK(not world:has(entity, pair(Lifetime, Particle)))
1691CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
170N/Aend)
171N/A
1721local lifetime_tracker_add = require("@tools/lifetime_tracker")
173N/A
1741TEST("another", function()
1751local world = world_new()
176N/A-- world = lifetime_tracker_add(world, {padding_enabled=false})
1771local e1 = world:entity()
1781local e2 = world:entity()
1791local e3 = world:entity()
1801world:delete(e2)
1811local e2_e3 = pair(e2, e3)
1821CHECK(jecs.pair_first(world, e2_e3) == 0)
1831CHECK(jecs.pair_second(world, e2_e3) == e3)
1841CHECK_EXPECT_ERR(function()
1851world:add(e1, pair(e2, e3))
186N/Aend)
187N/Aend)
188N/A
1891TEST("#repro", function()
1901local world = world_new()
191N/A
1921local function getTargets(relation)
1932local tgts = {}
1942local pairwildcard = pair(relation, jecs.Wildcard)
1952for _, archetype in world:query(pairwildcard):archetypes() do
1962local tr = archetype.records[pairwildcard]
1972local count = archetype.counts[pairwildcard]
1982local types = archetype.types
1992for _, entity in archetype.entities do
2002for i = 0, count - 1 do
2012local tgt = jecs.pair_second(world, types[i + tr])
2022table.insert(tgts, tgt)
203N/Aend
204N/Aend
205N/Aend
2062return tgts
207N/Aend
208N/A
2091local Attacks = world:component()
2101local Eats = world:component()
211N/A
2121local function setAttacksAndEats(entity1, entity2)
2133world:add(entity1, pair(Attacks, entity2))
2143world:add(entity1, pair(Eats, entity2))
215N/Aend
216N/A
2171local e1 = world:entity()
2181local e2 = world:entity()
2191local e3 = world:entity()
2201setAttacksAndEats(e3, e1)
2211setAttacksAndEats(e3, e2)
2221setAttacksAndEats(e1, e2)
2231local d = dwi(world)
2241world:delete(e2)
2251local types1 = { pair(Attacks, e1), pair(Eats, e1) }
2261table.sort(types1)
227N/A
228N/A
2291CHECK(d.tbl(e1).type == "")
2301CHECK(d.tbl(e3).type == table.concat(types1, "_"))
231N/A
2321for _, entity in getTargets(Attacks) do
2331CHECK(entity == e1)
234N/Aend
2351for _, entity in getTargets(Eats) do
2361CHECK(entity == e1)
237N/Aend
238N/Aend)
239N/A
2401TEST("archetype", function()
2411local archetype_traverse_add = jecs.archetype_traverse_add
2421local archetype_traverse_remove = jecs.archetype_traverse_remove
243N/A
2441local world = world_new()
2451local root = world.ROOT_ARCHETYPE
2461local c1 = world:component()
2471local c2 = world:component()
2481local c3 = world:component()
249N/A
2501local a1 = archetype_traverse_add(world, c1, nil :: any)
2511local a2 = archetype_traverse_remove(world, c1, a1)
2521CHECK(root.add[c1].to == a1)
2531CHECK(root == a2)
254N/Aend)
255N/A
2561TEST("world:cleanup()", function()
2571local world = world_new()
2581local A = world:component() :: jecs.Id
2591local B = world:component() :: jecs.Id
2601local C = world:component() :: jecs.Id
261N/A
2621local e1 = world:entity()
2631local e2 = world:entity()
2641local e3 = world:entity()
265N/A
2661world:set(e1, A, true)
267N/A
2681world:set(e2, A, true)
2691world:set(e2, B, true)
270N/A
271N/A
2721world:set(e3, A, true)
2731world:set(e3, B, true)
2741world:set(e3, C, true)
275N/A
2761local archetype_index = world.archetype_index
277N/A
2781CHECK(#archetype_index["1"].entities == 1)
2791CHECK(#archetype_index["1_2"].entities == 1)
2801CHECK(#archetype_index["1_2_3"].entities == 1)
281N/A
2821world:delete(e1)
2831world:delete(e2)
2841world:delete(e3)
285N/A
2861world:cleanup()
287N/A
2881archetype_index = world.archetype_index
289N/A
2901CHECK((archetype_index["1"] :: jecs.Archetype?) == nil)
2911CHECK((archetype_index["1_2"] :: jecs.Archetype?) == nil)
2921CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
293N/A
2941local e4 = world:entity()
2951world:set(e4, A, true)
2961CHECK(#archetype_index["1"].entities == 1)
2971CHECK((archetype_index["1_2"] :: jecs.Archetype?) == nil)
2981CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
2991world:set(e4, B, true)
3001CHECK(#archetype_index["1"].entities == 0)
3011CHECK(#archetype_index["1_2"].entities == 1)
3021CHECK((archetype_index["1_2_3"] :: jecs.Archetype?) == nil)
3031world:set(e4, C, true)
3041CHECK(#archetype_index["1"].entities == 0)
3051CHECK(#archetype_index["1_2"].entities == 0)
3061CHECK(#archetype_index["1_2_3"].entities == 1)
307N/Aend)
308N/A
3091local pe = require("@tools/entity_visualiser").prettify
3101local lifetime_tracker_add = require("@tools/lifetime_tracker")
311N/A
3121TEST("world:entity()", function()
3130do
3141CASE("unique IDs")
3151local world = jecs.World.new()
3161local set = {}
3171for i = 1, N do
318256local e = world:entity()
319256CHECK(not set[e])
320256set[e] = true
321N/Aend
322N/Aend
3230do
3241CASE("generations")
3251local world = jecs.World.new()
3261local e = world:entity() :: number
3271CHECK(ECS_ID(e) == 1 + jecs.Rest :: number)
3281CHECK(ECS_GENERATION(e) == 0) -- 0
3291e = ECS_GENERATION_INC(e)
3301CHECK(ECS_GENERATION(e) == 1) -- 1
331N/Aend
332N/A
3331do CASE "pairs"
3341local world = jecs.World.new()
3351local _e = world:entity()
3361local e2 = world:entity()
3371local e3 = world:entity()
338N/A
339N/A-- Incomplete pair, must have a bit flag that notes it is a pair
3401CHECK(IS_PAIR(world:entity()) == false)
341N/A
3421local p = pair(e2, e3)
3431CHECK(IS_PAIR(p) == true)
344N/A
3451CHECK(ecs_pair_first(world, p) == e2 :: number)
3461CHECK(ecs_pair_second(world, p) == e3 :: number)
347N/A
3481world:delete(e2)
3491local e2v2 = world:entity()
3501CHECK(IS_PAIR(e2v2) == false)
351N/A
3521CHECK(IS_PAIR(pair(e2v2, e3)) == true)
353N/Aend
354N/A
3551do CASE "Recycling"
3561local world = world_new()
3571local e = world:entity()
3581world:delete(e)
3591local e1 = world:entity()
3601world:delete(e1)
3611local e2 = world:entity()
3621CHECK(ECS_ID(e2) == e :: number)
3631CHECK(ECS_GENERATION(e2) == 2)
3641CHECK(world:contains(e2))
3651CHECK(not world:contains(e1))
3661CHECK(not world:contains(e))
367N/Aend
368N/A
3691do CASE "Recycling max generation"
3701local world = world_new()
3711local pin = (jecs.Rest :: any) :: number + 1
3721for i = 1, 2^16-1 do
37365535local e = world:entity()
37465535world:delete(e)
375N/Aend
3761local e = world:entity()
3771CHECK(ECS_ID(e) == pin)
3781CHECK(ECS_GENERATION(e) == 2^16-1)
3791world:delete(e)
3801e = world:entity()
3811CHECK(ECS_ID(e) == pin)
3821CHECK(ECS_GENERATION(e) == 0)
383N/Aend
384N/Aend)
385N/A
3861TEST("world:set()", function()
3871do CASE "archetype move"
3880do
3891local world = jecs.World.new()
390N/A
3911local d = debug_world_inspect(world)
392N/A
3931local _1 = world:component()
3941local _2 = world:component()
3951local e = world:entity()
396N/A-- An entity starts without an archetype or row
397N/A-- should therefore not need to copy over data
3981CHECK(d.tbl(e) == nil)
3991CHECK(d.row(e) == nil)
400N/A
4011local archetypes = #world.archetypes
402N/A-- This should create a new archetype since it is the first
403N/A-- entity to have moved there
4041world:set(e, _1, 1)
4051local oldRow = d.row(e)
4061local oldArchetype = d.archetype(e)
4071CHECK(#world.archetypes == archetypes + 1)
4081CHECK(oldArchetype == "1")
4091CHECK(d.tbl(e))
4101CHECK(oldRow == 1)
411N/A
4121world:set(e, _2, 2)
4131CHECK(d.archetype(e) == "1_2")
414N/A-- Should have tuple of fields to the next archetype and set the component data
4151CHECK(d.tuple(e, 1, 2))
416N/A-- Should have moved the data from the old archetype
4171CHECK(world.archetype_index[oldArchetype].columns[_1][oldRow] == nil)
418N/Aend
419N/Aend
420N/A
4211do CASE "pairs"
4221local world = jecs.World.new()
423N/A
4241local C1 = world:component()
4251local C2 = world:component()
4261local T1 = world:entity()
4271local T2 = world:entity()
428N/A
4291local e = world:entity()
430N/A
4311world:set(e, pair(C1, C2), true)
4321world:set(e, pair(C1, T1), true)
4331world:set(e, pair(T1, C1), true)
434N/A
4351CHECK_EXPECT_ERR(function()
4361world:set(e, pair(T1, T2), true :: any)
437N/Aend)
438N/A
4391CHECK(world:get(e, pair(C1, C2)))
4401CHECK(world:get(e, pair(C1, T1)))
4411CHECK(world:get(e, pair(T1, C1)))
4421CHECK(not world:get(e, pair(T1, T2)))
443N/A
4441local e2 = world:entity()
445N/A
4461CHECK_EXPECT_ERR(function()
4471world:set(e2, pair(jecs.ChildOf, e), true :: any)
448N/Aend)
4491CHECK(not world:get(e2, pair(jecs.ChildOf, e)))
450N/Aend
451N/Aend)
452N/A
4531TEST("world:remove()", function()
4540do
4551CASE("should allow remove a component that doesn't exist on entity")
4561local world = jecs.World.new()
457N/A
4581local Health = world:component()
4591local Poison = world:component()
460N/A
4611local id = world:entity()
4620do
4631world:remove(id, Poison)
4641CHECK(true) -- Didn't error
465N/Aend
466N/A
4671world:set(id, Health, 50)
4681world:remove(id, Poison)
469N/A
4701CHECK(world:get(id, Poison) == nil)
4711CHECK(world:get(id, Health) == 50)
472N/Aend
473N/Aend)
474N/A
4751TEST("world:add()", function()
4760do
4771CASE("idempotent")
4781local world = jecs.World.new()
4791local d = debug_world_inspect(world)
4801local _1, _2 = world:component(), world:component()
481N/A
4821local e = world:entity()
4831world:add(e, _1)
4841world:add(e, _2)
4851world:add(e, _2) -- should have 0 effects
4861CHECK(d.archetype(e) == "1_2")
487N/Aend
488N/A
4890do
4901CASE("archetype move")
4910do
4921local world = jecs.World.new()
493N/A
4941local d = debug_world_inspect(world)
495N/A
4961local _1 = world:component()
4971local e = world:entity()
498N/A-- An entity starts without an archetype or row
499N/A-- should therefore not need to copy over data
5001CHECK(d.tbl(e) == nil)
5011CHECK(d.row(e) == nil)
502N/A
5031local archetypes = #world.archetypes
504N/A-- This should create a new archetype
5051world:add(e, _1)
5061CHECK(#world.archetypes == archetypes + 1)
507N/A
5081CHECK(d.archetype(e) == "1")
5091CHECK(d.tbl(e))
510N/Aend
511N/Aend
512N/Aend)
513N/A
5141TEST("world:query()", function()
5151do CASE "cached"
5161local world = world_new()
5171local Foo = world:component()
5181local Bar = world:component()
5191local Baz = world:component()
5201local e = world:entity()
5211local q = world:query(Foo, Bar):without(Baz):cached()
5221world:set(e, Foo, true)
5231world:set(e, Bar, false)
5241local i = 0
525N/A
5261local iter = 0
5271for _, e in q:iter() do
5281iter += 1
5291i=1
530N/Aend
5311CHECK (iter == 1)
5321CHECK(i == 1)
5331for _, e in q:iter() do
5341i=2
535N/Aend
5361CHECK(i == 2)
5371for _, e in q :: any do
5381i=3
539N/Aend
5401CHECK(i == 3)
5411for _, e in q :: any do
5421i=4
543N/Aend
5441CHECK(i == 4)
545N/A
5461CHECK(#q:archetypes() == 1)
5471CHECK(not table.find(q:archetypes(), world.archetype_index[table.concat({Foo, Bar, Baz}, "_")]))
5481world:delete(Foo)
5491CHECK(#q:archetypes() == 0)
550N/Aend
5511do CASE "multiple iter"
5521local world = jecs.World.new()
5531local A = world:component()
5541local B = world:component()
5551local e = world:entity()
5561world:add(e, A)
5571world:add(e, B)
5581local q = world:query(A, B)
5591local counter = 0
5601for x in q:iter() do
5611counter += 1
562N/Aend
5631for x in q:iter() do
5641counter += 1
565N/Aend
5661CHECK(counter == 2)
567N/Aend
5681do CASE "tag"
5691local world = jecs.World.new()
5701local A = world:entity()
5711local e = world:entity()
5721CHECK_EXPECT_ERR(function()
5731world:set(e, A, "test" :: any)
574N/Aend)
5751local count = 0
5761for id, a in world:query(A) :: any do
5771count += 1
5781CHECK(a == nil)
579N/Aend
5801CHECK(count == 1)
581N/Aend
5821do CASE "pairs"
5831local world = jecs.World.new()
584N/A
5851local C1 = world:component() :: jecs.Id
5861local C2 = world:component() :: jecs.Id
5871local T1 = world:entity()
5881local T2 = world:entity()
589N/A
5901local e = world:entity()
591N/A
5921local C1_C2 = pair(C1, C2)
5931world:set(e, C1_C2, true)
5941world:set(e, pair(C1, T1), true)
5951world:set(e, pair(T1, C1), true)
5961CHECK_EXPECT_ERR(function()
5971world:set(e, pair(T1, T2), true :: any)
598N/Aend)
599N/A
6001for id, a, b, c, d in world:query(pair(C1, C2), pair(C1, T1), pair(T1, C1), pair(T1, T2)):iter() do
6011CHECK(a == true)
6021CHECK(b == true)
6031CHECK(c == true)
6041CHECK(d == nil)
605N/Aend
606N/Aend
6070do
6081CASE("query single component")
6090do
6101local world = jecs.World.new()
6111local A = world:component()
6121local B = world:component()
613N/A
6141local entities = {}
6151for i = 1, N do
616256local id = world:entity()
617N/A
618256world:set(id, A, true)
619256if i > 5 then
620251world:set(id, B, true)
621N/Aend
622256entities[i] = id
623N/Aend
624N/A
6251for id in world:query(A) :: any do
626256table.remove(entities, CHECK(table.find(entities, id)))
627N/Aend
628N/A
6291CHECK(#entities == 0)
630N/Aend
631N/A
6320do
6331local world = jecs.World.new() :: World
6341local A = world:component()
6351local B = world:component()
6361local eA = world:entity()
6371world:set(eA, A, true)
6381local eB = world:entity()
6391world:set(eB, B, true)
6401local eAB = world:entity()
6411world:set(eAB, A, true)
6421world:set(eAB, B, true)
643N/A
644N/A-- Should drain the iterator
6451local q = world:query(A)
646N/A
6471local i = 0
6481local j = 0
6491for _ in q :: any do
6502i += 1
651N/Aend
6521for _ in q :: any do
6530j += 1
654N/Aend
6551CHECK(i == 2)
6561CHECK(j == 0)
657N/Aend
658N/Aend
659N/A
6600do
6611CASE("query missing component")
6621local world = jecs.World.new()
6631local A = world:component()
6641local B = world:component()
6651local C = world:component()
666N/A
6671local e1 = world:entity()
6681local e2 = world:entity()
669N/A
6701world:set(e1, A, "abc")
6711world:set(e2, A, "def")
6721world:set(e1, B, 123)
6731world:set(e2, B, 457)
674N/A
6751local counter = 0
6761for _ in world:query(B, C) :: any do
6770counter += 1
678N/Aend
6791CHECK(counter == 0)
680N/Aend
681N/A
6820do
6831CASE("query more than 8 components")
6841local world = jecs.World.new()
6851local components = {}
686N/A
6871for i = 1, 9 do
6889local id = world:component()
6899components[i] = id
690N/Aend
6911local e = world:entity()
6921for i, id in components do
6939world:set(e, id, 13 ^ i)
694N/Aend
695N/A
6961for entity, a, b, c, d, e, f, g, h, i in world:query(unpack(components)) :: any do
6971CHECK(a == 13 ^ 1)
6981CHECK(b == 13 ^ 2)
6991CHECK(c == 13 ^ 3)
7001CHECK(d == 13 ^ 4)
7011CHECK(e == 13 ^ 5)
7021CHECK(f == 13 ^ 6)
7031CHECK(g == 13 ^ 7)
7041CHECK(h == 13 ^ 8)
7051CHECK(i == 13 ^ 9)
706N/Aend
707N/Aend
708N/A
7090do
7101CASE("should be able to get next results")
7111local world = jecs.World.new() :: World
7121world:component()
7131local A = world:component()
7141local B = world:component()
7151local eA = world:entity()
7161world:set(eA, A, true)
7171local eB = world:entity()
7181world:set(eB, B, true)
7191local eAB = world:entity()
7201world:set(eAB, A, true)
7211world:set(eAB, B, true)
722N/A
7231local it = world:query(A):iter()
724N/A
7251local e: number, data = it()
7261while e do
7272if e == eA :: number then
7281CHECK(data)
7291elseif e == eAB :: number then
7301CHECK(data)
7310else
7320CHECK(false)
733N/Aend
734N/A
7352e, data = it()
736N/Aend
7371CHECK(true)
738N/Aend
739N/A
7401do CASE "should query all matching entities when irrelevant component is removed"
7411local world = jecs.World.new()
7421local A = world:component()
7431local B = world:component()
7441local C = world:component()
745N/A
7461local entities = {}
7471for i = 1, N do
748256local id = world:entity()
749N/A
750N/A-- specifically put them in disorder to track regression
751N/A-- https://github.com/Ukendio/jecs/pull/15
752256world:set(id, B, true)
753256world:set(id, A, true)
754256if i > 5 then
755251world:remove(id, B)
756N/Aend
757256entities[i] = id
758N/Aend
759N/A
7601local added = 0
7611for id in world:query(A) :: any do
762256added += 1
763256table.remove(entities, CHECK(table.find(entities, id)))
764N/Aend
765N/A
7661CHECK(added == N)
767N/Aend
768N/A
7690do
7701CASE("should query all entities without B")
7711local world = jecs.World.new()
7721local A = world:component()
7731local B = world:component()
774N/A
7751local entities = {}
7761for i = 1, N do
777256local id = world:entity()
778N/A
779256world:set(id, A, true)
780256if i < 5 then
7814entities[i] = id
7820else
783252world:set(id, B, true)
784N/Aend
785N/Aend
786N/A
7871for id in world:query(A):without(B) :: any do
7884table.remove(entities, CHECK(table.find(entities, id)))
789N/Aend
790N/A
7911CHECK(#entities == 0)
792N/Aend
793N/A
7940do
7951CASE("should allow querying for relations")
7961local world = jecs.World.new()
7971local Eats = world:component()
7981local Apples = world:component()
7991local bob = world:entity()
800N/A
8011world:set(bob, pair(Eats, Apples), true)
8021for e, bool in world:query(pair(Eats, Apples)) :: any do
8031CHECK(e == bob)
8041CHECK(bool)
805N/Aend
806N/Aend
807N/A
8080do
8091CASE("should allow wildcards in queries")
8101local world = jecs.World.new()
8111local Eats = world:component()
8121local Apples = world:entity()
8131local bob = world:entity()
814N/A
8151world:set(bob, pair(Eats, Apples), "bob eats apples")
816N/A
8171local w = jecs.Wildcard
8181for e, data in world:query(pair(Eats, w)) :: any do
8191CHECK(e == bob)
8201CHECK(data == "bob eats apples")
821N/Aend
8221for e, data in world:query(pair(w, Apples)) :: any do
8231CHECK(e == bob)
8241CHECK(data == "bob eats apples")
825N/Aend
826N/Aend
827N/A
8280do
8291CASE("should match against multiple pairs")
8301local world = jecs.World.new()
8311local Eats = world:component()
8321local Apples = world:entity()
8331local Oranges = world:entity()
8341local bob = world:entity()
8351local alice = world:entity()
836N/A
8371world:set(bob, pair(Eats, Apples), "bob eats apples")
8381world:set(alice, pair(Eats, Oranges), "alice eats oranges")
839N/A
8401local w = jecs.Wildcard
8411local count = 0
8421for e, data in world:query(pair(Eats, w)) :: any do
8432count += 1
8442if e == bob then
8451CHECK(data == "bob eats apples")
8460else
8471CHECK(data == "alice eats oranges")
848N/Aend
849N/Aend
850N/A
8511CHECK(count == 2)
8521count = 0
853N/A
8541for e, data in world:query(pair(w, Apples)) :: any do
8551count += 1
8561CHECK(data == "bob eats apples")
857N/Aend
8581CHECK(count == 1)
859N/Aend
860N/A
8611do CASE "should only relate alive entities"
8621local world = jecs.World.new()
8631local Eats = world:entity()
8641local Apples = world:component()
8651local Oranges = world:component()
8661local bob = world:entity()
8671local alice = world:entity()
868N/A
8691world:set(bob, Apples, "apples")
8701world:set(bob, pair(Eats, Apples), "bob eats apples")
8711world:set(alice, pair(Eats, Oranges) :: Entity, "alice eats oranges")
872N/A
8731world:delete(Apples)
8741local Wildcard = jecs.Wildcard
875N/A
8761local count = 0
8771for _, data in world:query(pair(Wildcard, Apples)) :: any do
8780count += 1
879N/Aend
880N/A
8811world:delete(pair(Eats, Apples))
882N/A
8831CHECK(count == 0)
8841CHECK(world:get(bob, pair(Eats, Apples)) == nil)
885N/A
886N/Aend
887N/A
8880do
8891CASE("should error when setting invalid pair")
8901local world = jecs.World.new()
8911local Eats = world:component()
8921local Apples = world:component()
8931local bob = world:entity()
894N/A
8951world:delete(Apples)
8961CHECK_EXPECT_ERR(function()
8971world:set(bob, pair(Eats, Apples), "bob eats apples")
898N/Aend)
899N/Aend
900N/A
9010do
9021CASE("should find target for ChildOf")
9031local world = jecs.World.new()
9041local ChildOf = jecs.ChildOf
905N/A
9061local Name = world:component()
907N/A
9081local bob = world:entity()
9091local alice = world:entity()
9101local sara = world:entity()
911N/A
9121world:add(bob, pair(ChildOf, alice))
9131world:set(bob, Name, "bob")
9141world:add(sara, pair(ChildOf, alice))
9151world:set(sara, Name, "sara")
9161CHECK(world:parent(bob) :: number == alice :: number) -- O(1)
917N/A
9181local count = 0
9191for _, name in world:query(Name, pair(ChildOf, alice)) :: any do
9202count += 1
921N/Aend
9221CHECK(count == 2)
923N/Aend
924N/A
9250do
9261CASE("despawning while iterating")
9271local world = jecs.World.new()
9281local A = world:component()
9291local B = world:component()
930N/A
9311local e1 = world:entity()
9321local e2 = world:entity()
9331world:add(e1, A)
9341world:add(e2, A)
9351world:add(e2, B)
936N/A
9371local count = 0
9381for id in world:query(A) :: any do
9392world:clear(id)
9402count += 1
941N/Aend
9421CHECK(count == 2)
943N/Aend
944N/A
9451do CASE("iterator invalidation")
9461do CASE("adding")
9471SKIP()
9481local world = jecs.World.new()
9491local A = world:component()
9501local B = world:component()
951N/A
9521local e1 = world:entity()
9531local e2 = world:entity()
9541world:add(e1, A)
9551world:add(e2, A)
9561world:add(e2, B)
957N/A
9581local count = 0
9591for id in world:query(A) :: any do
9603world:add(id, B)
961N/A
9623count += 1
963N/Aend
964N/A
9651CHECK(count == 2)
966N/Aend
967N/A
9681do CASE("spawning")
9691local world = jecs.World.new()
9701local A = world:component()
9711local B = world:component()
972N/A
9731local e1 = world:entity()
9741local e2 = world:entity()
9751world:add(e1, A)
9761world:add(e2, A)
9771world:add(e2, B)
978N/A
9791for id in world:query(A) :: any do
9803local e = world:entity()
9813world:add(e, A)
9823world:add(e, B)
983N/Aend
984N/A
9851CHECK(true)
986N/Aend
987N/Aend
988N/A
9891do CASE("should not find any entities")
9901local world = jecs.World.new()
991N/A
9921local Hello = world:component()
9931local Bob = world:component()
994N/A
9951local helloBob = world:entity()
9961world:add(helloBob, pair(Hello, Bob))
9971world:add(helloBob, Bob)
998N/A
9991local withoutCount = 0
10001for _ in world:query(pair(Hello, Bob)):without(Bob) :: any do
10010withoutCount += 1
1002N/Aend
1003N/A
10041CHECK(withoutCount == 0)
1005N/Aend
1006N/A
10071do CASE("without")
1008N/A-- REGRESSION TEST
10091local world = jecs.World.new()
10101local _1, _2, _3 = world:component(), world:component(), world:component()
1011N/A
10121local counter = 0
10131for e, a, b in world:query(_1, _2):without(_3) :: any do
10140counter += 1
1015N/Aend
10161CHECK(counter == 0)
1017N/Aend
1018N/Aend)
1019N/A
10201TEST("world:each", function()
10211local world = world_new()
10221local A = world:component()
10231local B = world:component()
10241local C = world:component()
1025N/A
10261local e3 = world:entity()
10271local e1 = world:entity()
10281local e2 = world:entity()
1029N/A
10301world:set(e1, A, true)
1031N/A
10321world:set(e2, A, true)
10331world:set(e2, B, true)
1034N/A
10351world:set(e3, A, true)
10361world:set(e3, B, true)
10371world:set(e3, C, true)
1038N/A
10391for entity: number in world:each(A) do
10403if entity == e1 :: number or entity == e2 :: number or entity == e3 :: number then
10413CHECK(true)
10423continue
1043N/Aend
10440CHECK(false)
1045N/Aend
1046N/Aend)
1047N/A
10481TEST("world:children", function()
10491local world = world_new()
10501local C = world:component()
10511local T = world:entity()
1052N/A
10531local e1 = world:entity()
10541world:set(e1, C, true)
1055N/A
10561local e2 = world:entity() :: number
1057N/A
10581world:add(e2, T)
10591world:add(e2, pair(ChildOf, e1))
1060N/A
10611local e3 = world:entity() :: number
10621world:add(e3, pair(ChildOf, e1))
1063N/A
10641local count = 0
10651for entity: number in world:children(e1) do
10662count += 1
10672if entity == e2 or entity == e3 then
10682CHECK(true)
10692continue
1070N/Aend
10710CHECK(false)
1072N/Aend
10731CHECK(count == 2)
1074N/A
10751world:remove(e2, pair(ChildOf, e1))
1076N/A
10771count = 0
10781for entity in world:children(e1) do
10791count += 1
1080N/Aend
1081N/A
10821CHECK(count == 1)
1083N/Aend)
1084N/A
10851TEST("world:clear()", function()
10861do CASE("should remove its components")
10871local world = jecs.World.new() :: World
10881local A = world:component()
10891local B = world:component()
10901local C = world:component()
10911local D = world:component()
1092N/A
10931local e = world:entity()
10941local e1 = world:entity()
10951local e2 = world:entity()
1096N/A
10971world:set(e, A, true)
10981world:set(e, B, true)
1099N/A
11001world:set(e1, A, true)
11011world:set(e1, B, true)
1102N/A
11031CHECK(world:get(e, A))
11041CHECK(world:get(e, B))
1105N/A
11061world:clear(A)
11071CHECK(world:get(e, A) == nil)
11081CHECK(world:get(e, B))
11091CHECK(world:get(e1, A) == nil)
11101CHECK(world:get(e1, B))
1111N/Aend
1112N/A
11131do CASE("remove cleared ID from entities")
11141local world = world_new()
11151local A = world:component()
11161local B = world:component()
11171local C = world:component()
1118N/A
11190do
11201local id1 = world:entity()
11211local id2 = world:entity()
11221local id3 = world:entity()
1123N/A
11241world:set(id1, A, true)
1125N/A
11261world:set(id2, A, true)
11271world:set(id2, B, true)
1128N/A
11291world:set(id3, A, true)
11301world:set(id3, B, true)
11311world:set(id3, C, true)
1132N/A
11331world:clear(A)
1134N/A
11351CHECK(not world:has(id1, A))
11361CHECK(not world:has(id2, A))
11371CHECK(not world:has(id3, A))
1138N/A
11391CHECK(world:has(id2, B))
11401CHECK(world:has(id3, B, C))
1141N/A
11421world:clear(C)
1143N/A
11441CHECK(world:has(id2, B))
11451CHECK(world:has(id3, B))
1146N/A
11471CHECK(world:contains(A))
11481CHECK(world:contains(C))
11491CHECK(world:has(A, jecs.Component))
11501CHECK(world:has(B, jecs.Component))
1151N/Aend
1152N/A
11530do
11541local id1 = world:entity()
11551local id2 = world:entity()
11561local id3 = world:entity()
1157N/A
11581local tgt = world:entity()
1159N/A
11601world:add(id1, pair(A, tgt))
11611world:add(id1, pair(B, tgt))
11621world:add(id1, pair(C, tgt))
1163N/A
11641world:add(id2, pair(A, tgt))
11651world:add(id2, pair(B, tgt))
11661world:add(id2, pair(C, tgt))
1167N/A
11681world:add(id3, pair(A, tgt))
11691world:add(id3, pair(B, tgt))
11701world:add(id3, pair(C, tgt))
1171N/A
11721world:clear(B)
11731CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
11741CHECK(not world:has(id1, pair(B, tgt)))
11751CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
11761CHECK(not world:has(id1, pair(B, tgt)))
11771CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
1178N/A
1179N/Aend
1180N/A
1181N/Aend
1182N/Aend)
1183N/A
11841TEST("world:has()", function()
11851do CASE("should find Tag on entity")
11861local world = jecs.World.new()
1187N/A
11881local Tag = world:entity()
1189N/A
11901local e = world:entity()
11911world:add(e, Tag)
1192N/A
11931CHECK(world:has(e, Tag))
1194N/Aend
1195N/A
11961do CASE("should return false when missing one tag")
11971local world = jecs.World.new()
1198N/A
11991local A = world:entity()
12001local B = world:entity()
12011local C = world:entity()
12021local D = world:entity()
1203N/A
12041local e = world:entity()
12051world:add(e, A)
12061world:add(e, C)
12071world:add(e, D)
1208N/A
12091CHECK(world:has(e, A, B, C, D) == false)
1210N/Aend
1211N/Aend)
1212N/A
12131TEST("world:component()", function()
12141do CASE("only components should have EcsComponent trait")
12151local world = jecs.World.new() :: World
12161local A = world:component()
12171local e = world:entity()
1218N/A
12191CHECK(world:has(A, jecs.Component))
12201CHECK(not world:has(e, jecs.Component))
1221N/Aend
1222N/A
12231do CASE("tag")
12241local world = jecs.World.new() :: World
12251local A = world:component()
12261local B = world:entity()
12271local C = world:entity()
12281local e = world:entity()
12291world:set(e, A, "test")
12301world:add(e, B)
12311CHECK_EXPECT_ERR(function()
12321world:set(e, C, 11 :: any)
1233N/Aend)
1234N/A
12351CHECK(world:has(e, A))
12361CHECK(world:get(e, A) == "test")
12371CHECK(world:get(e, B) == nil)
12381CHECK(world:get(e, C) == nil)
1239N/Aend
1240N/Aend)
1241N/A
12421TEST("world:delete", function()
12431do CASE "invoke OnRemove hooks"
12441local world = world_new()
1245N/A
12461local e1 = world:entity()
12471local e2 = world:entity()
1248N/A
12491local Stable = world:component()
12501world:set(Stable, jecs.OnRemove, function(e)
12511CHECK(e == e1)
1252N/Aend)
1253N/A
12541world:set(e1, Stable, true)
12551world:set(e2, Stable, true)
1256N/A
12571world:delete(e1)
1258N/Aend
12591do CASE "delete recycled entity id used as component"
12601local world = world_new()
12611local id = world:entity()
12621world:add(id, jecs.Component)
1263N/A
12641local e = world:entity()
12651world:set(e, id, 1)
12661CHECK(world:get(e, id) == 1)
12671world:delete(id)
12681local recycled = world:entity()
12691world:add(recycled, jecs.Component)
12701world:set(e, recycled, 1)
12711CHECK(world:has(recycled, jecs.Component))
12721CHECK(world:get(e, recycled) == 1)
1273N/Aend
12740do
12751CASE("bug: Empty entity does not respect cleanup policy")
12761local world = world_new()
12771local parent = world:entity()
12781local tag = world:entity()
1279N/A
12801local child = world:entity()
12811world:add(child, jecs.pair(jecs.ChildOf, parent))
12821world:delete(parent)
1283N/A
12841CHECK(not world:contains(parent))
12851CHECK(not world:contains(child))
1286N/A
12871local entity = world:entity()
12881world:add(entity, tag)
12891world:delete(tag)
12901CHECK(world:contains(entity))
12911CHECK(not world:contains(tag))
12921CHECK(not world:has(entity, tag)) -- => true
1293N/Aend
12941do CASE("should allow deleting components")
12951local world = jecs.World.new()
1296N/A
12971local Health = world:component()
12981local Poison = world:component()
1299N/A
13001local id = world:entity()
13011world:set(id, Poison, 5)
13021world:set(id, Health, 50)
13031local id1 = world:entity()
13041world:set(id1, Poison, 500)
13051world:set(id1, Health, 50)
1306N/A
13071world:delete(id)
13081CHECK(not world:contains(id))
13091CHECK(world:get(id, Poison) == nil)
13101CHECK(world:get(id, Health) == nil)
1311N/A
13121CHECK(world:get(id1, Poison) == 500)
13131CHECK(world:get(id1, Health) == 50)
1314N/Aend
1315N/A
13161do CASE("delete entities using another Entity as component with Delete cleanup action")
13171local world = jecs.World.new()
1318N/A
13191local Health = world:entity()
13201world:add(Health, pair(jecs.OnDelete, jecs.Delete))
13211local Poison = world:component()
1322N/A
13231local id = world:entity()
13241world:set(id, Poison, 5)
13251CHECK_EXPECT_ERR(function()
13261world:set(id, Health, 50 :: any)
1327N/Aend)
13281local id1 = world:entity()
13291world:set(id1, Poison, 500)
13301CHECK_EXPECT_ERR(function()
13311world:set(id1, Health, 50 :: any)
1332N/Aend)
1333N/A
13341CHECK(world:has(id, Poison, Health))
13351CHECK(world:has(id1, Poison, Health))
13361world:delete(Poison)
1337N/A
13381CHECK(world:contains(id))
13391CHECK(not world:has(id, Poison))
13401CHECK(not world:has(id1, Poison))
1341N/A
13421world:delete(Health)
13431CHECK(not world:contains(id))
13441CHECK(not world:contains(id1))
13451CHECK(not world:has(id, Health))
13461CHECK(not world:has(id1, Health))
1347N/Aend
1348N/A
1349N/A
13501do CASE("delete children")
13511local world = jecs.World.new()
1352N/A
13531local Health = world:component()
13541local Poison = world:component()
13551local FriendsWith = world:component()
1356N/A
13571local e = world:entity()
13581world:set(e, Poison, 5)
13591world:set(e, Health, 50)
1360N/A
13611local children = {}
13621for i = 1, 10 do
136310local child = world:entity()
136410world:set(child, Poison, 9999)
136510world:set(child, Health, 100)
136610world:add(child, pair(jecs.ChildOf, e))
136710table.insert(children, child)
1368N/Aend
1369N/A
13701BENCH("delete children of entity", function()
13711world:delete(e)
1372N/Aend)
1373N/A
13741for i, child in children do
137510CHECK(not world:contains(child))
137610CHECK(not world:has(child, pair(jecs.ChildOf, e)))
137710CHECK(not world:has(child, Health))
1378N/Aend
1379N/A
13801e = world:entity()
1381N/A
13821local friends = {}
13831for i = 1, 10 do
138410local friend = world:entity()
138510world:set(friend, Poison, 9999)
138610world:set(friend, Health, 100)
138710world:add(friend, pair(FriendsWith, e))
138810table.insert(friends, friend)
1389N/Aend
1390N/A
13911BENCH("remove friends of entity", function()
13921world:delete(e)
1393N/Aend)
1394N/A
13951local d = debug_world_inspect(world)
13961for i, friend in friends do
139710CHECK(not world:has(friend, pair(FriendsWith, e)))
139810CHECK(world:has(friend, Health))
139910CHECK(world:contains(friend))
1400N/Aend
1401N/Aend
1402N/A
14031do CASE("remove deleted ID from entities")
14041local world = world_new()
14050do
14061local A = world:component()
14071local B = world:component()
14081local C = world:component()
14091local id1 = world:entity()
14101local id2 = world:entity()
14111local id3 = world:entity()
1412N/A
14131world:set(id1, A, true)
1414N/A
14151world:set(id2, A, true)
14161world:set(id2, B, true)
1417N/A
14181world:set(id3, A, true)
14191world:set(id3, B, true)
14201world:set(id3, C, true)
1421N/A
14221world:delete(A)
1423N/A
14241CHECK(not world:has(id1, A))
14251CHECK(not world:has(id2, A))
14261CHECK(not world:has(id3, A))
1427N/A
14281CHECK(world:has(id2, B))
14291CHECK(world:has(id3, B, C))
1430N/A
14311world:delete(C)
1432N/A
14331CHECK(world:has(id2, B))
14341CHECK(world:has(id3, B))
1435N/A
14361CHECK(not world:contains(A))
14371CHECK(not world:contains(C))
1438N/Aend
1439N/A
14400do
14411local A = world:component()
14421world:add(A, pair(jecs.OnDeleteTarget, jecs.Delete))
14431local B = world:component()
14441local C = world:component()
14451world:add(C, pair(jecs.OnDeleteTarget, jecs.Delete))
1446N/A
14471local id1 = world:entity()
14481local id2 = world:entity()
14491local id3 = world:entity()
1450N/A
14511world:set(id1, C, true)
1452N/A
14531world:set(id2, pair(A, id1), true)
14541world:set(id2, B, true)
1455N/A
14561world:set(id3, B, true)
14571world:set(id3, pair(C, id2), true)
1458N/A
14591world:delete(id1)
1460N/A
14611CHECK(not world:contains(id1))
14621CHECK(not world:contains(id2))
14631CHECK(not world:contains(id3))
1464N/Aend
1465N/A
1466N/A
14670do
14681local A = world:component()
14691local B = world:component()
14701local C = world:component()
14711local id1 = world:entity()
14721local id2 = world:entity()
14731local id3 = world:entity()
1474N/A
1475N/A
14761world:set(id2, A, true)
14771world:set(id2, pair(B, id1), true)
1478N/A
14791world:set(id3, A, true)
14801world:set(id3, pair(B, id1), true)
14811world:set(id3, C, true)
1482N/A
14831world:delete(id1)
1484N/A
14851CHECK(not world:contains(id1))
14861CHECK(world:contains(id2))
14871CHECK(world:contains(id3))
1488N/A
14891CHECK(world:has(id2, A))
14901CHECK(world:has(id3, A, C))
1491N/A
14921CHECK(not world:target(id2, B))
14931CHECK(not world:target(id3, B))
1494N/Aend
1495N/Aend
1496N/A
14970do
14981CASE("fast delete")
14991local world = jecs.World.new()
1500N/A
15011local entities = {}
15021local Health = world:component()
15031local Poison = world:component()
1504N/A
15051for i = 1, 100 do
1506100local child = world:entity()
1507100world:set(child, Poison, 9999)
1508100world:set(child, Health, 100)
1509100table.insert(entities, child)
1510N/Aend
1511N/A
15121BENCH("simple deletion of entity", function()
15131for i = 1, START(100) do
1514100local e = entities[i]
1515100world:delete(e)
1516N/Aend
1517N/Aend)
1518N/A
15191for _, entity in entities do
1520100CHECK(not world:contains(entity))
1521N/Aend
1522N/Aend
1523N/A
15240do
15251CASE("cycle")
15261local world = jecs.World.new()
15271local Likes = world:component()
15281world:add(Likes, pair(jecs.OnDeleteTarget, jecs.Delete))
15291local bob = world:entity()
15301local alice = world:entity()
1531N/A
15321world:add(bob, pair(Likes, alice))
15331world:add(alice, pair(Likes, bob))
1534N/A
15351world:delete(bob)
15361CHECK(not world:contains(bob))
15371CHECK(not world:contains(alice))
1538N/Aend
1539N/Aend)
1540N/A
15411TEST("world:target", function()
15421do CASE("nth index")
15431local world = world_new()
15441local A = world:component()
15451world:set(A, jecs.Name, "A")
15461local B = world:component()
15471world:set(B, jecs.Name, "B")
15481local C = world:component()
15491world:set(C, jecs.Name, "C")
15501local D = world:component()
15511world:set(D, jecs.Name, "D")
15521local E = world:component()
15531world:set(E, jecs.Name, "E")
15541local e = world:entity()
1555N/A
15561world:add(e, pair(A, B))
15571world:add(e, pair(A, C))
15581world:add(e, pair(A, D))
15591world:add(e, pair(A, E))
15601world:add(e, pair(B, C))
15611world:add(e, pair(B, D))
15621world:add(e, pair(C, D))
1563N/A
15641CHECK(pair(A, B) < pair(A, C))
15651CHECK(pair(A, C) < pair(A, D))
15661CHECK(pair(C, A) < pair(C, D))
1567N/A
15681local records = debug_world_inspect(world).records(e)
15691CHECK(jecs.pair_first(world, pair(B, C)) == B)
15701local r = jecs.entity_index_try_get(world.entity_index, e)
15711local archetype = r.archetype
15721local counts = archetype.counts
15731CHECK(counts[pair(A, __)] == 4)
15741CHECK(records[pair(B, C)] > records[pair(A, E)])
15751CHECK(world:target(e, A, 0) == B)
15761CHECK(world:target(e, A, 1) == C)
15771CHECK(world:target(e, A, 2) == D)
15781CHECK(world:target(e, A, 3) == E)
15791CHECK(world:target(e, B, 0) == C)
15801CHECK(world:target(e, B, 1) == D)
15811CHECK(world:target(e, C, 0) == D)
15821CHECK(world:target(e, C, 1) == nil)
1583N/A
15841CHECK(archetype.records[pair(A, B)] == 1)
15851CHECK(archetype.records[pair(A, C)] == 2)
15861CHECK(archetype.records[pair(A, D)] == 3)
15871CHECK(archetype.records[pair(A, E)] == 4)
1588N/A
15891CHECK(world:target(e, C, 0) == D)
15901CHECK(world:target(e, C, 1) == nil)
1591N/Aend
1592N/A
15930do
15941CASE("infer index when unspecified")
15951local world = world_new()
15961local A = world:component()
15971local B = world:component()
15981local C = world:component()
15991local D = world:component()
16001local e = world:entity()
1601N/A
16021world:add(e, pair(A, B))
16031world:add(e, pair(A, C))
16041world:add(e, pair(B, C))
16051world:add(e, pair(B, D))
16061world:add(e, pair(C, D))
1607N/A
16081CHECK(world:target(e, A) == world:target(e, A, 0))
16091CHECK(world:target(e, B) == world:target(e, B, 0))
16101CHECK(world:target(e, C) == world:target(e, C, 0))
1611N/Aend
1612N/A
16130do
16141CASE("loop until no target")
16151local world = world_new()
1616N/A
16171local ROOT = world:entity()
16181local e1 = world:entity()
16191local targets = {}
1620N/A
16211for i = 1, 10 do
162210local target = world:entity()
162310targets[i] = target
162410world:add(e1, pair(ROOT, target))
1625N/Aend
1626N/A
16271local i = 0
16281local target = world:target(e1, ROOT, 0)
16291while target do
163010i += 1
163110CHECK(targets[i] == target)
163210target = world:target(e1, ROOT, i)
1633N/Aend
1634N/A
16351CHECK(i == 10)
1636N/Aend
1637N/Aend)
1638N/A
16391TEST("world:contains", function()
16401local world = jecs.World.new()
16411local id = world:entity()
16421CHECK(world:contains(id))
1643N/A
16440do
16451CASE("should not exist after delete")
16461world:delete(id)
16471CHECK(not world:contains(id))
1648N/Aend
1649N/Aend)
1650N/A
16511TEST("Hooks", function()
16521do CASE "OnAdd"
16531local world = jecs.World.new()
16541local Transform = world:component()
16551local e1 = world:entity()
16561world:set(Transform, jecs.OnAdd, function(entity)
16571CHECK(e1 == entity)
1658N/Aend)
16591world:add(e1, Transform)
1660N/Aend
1661N/A
16621do CASE "OnSet"
16631local world = jecs.World.new()
16641local Number = world:component()
16651local e1 = world:entity()
1666N/A
16671world:set(Number, jecs.OnSet, function(entity, data)
16681CHECK(e1 == entity)
16691CHECK(data == world:get(entity, Number))
16701CHECK(data == 1)
1671N/Aend)
16721world:set(e1, Number, 1)
1673N/Aend
1674N/A
16751do CASE("OnRemove")
16760do
1677N/A-- basic
16781local world = jecs.World.new()
16791local A = world:component() :: Entity
16801local e1 = world:entity()
16811world:set(A, jecs.OnRemove, function(entity)
16821CHECK(e1 == entity)
16831CHECK(world:has(e1, A))
1684N/Aend)
16851world:add(e1, A)
1686N/A
16871world:remove(e1, A)
16881CHECK(not world:has(e1, A))
1689N/Aend
16900do
1691N/A-- [BUG] https://github.com/Ukendio/jecs/issues/118
16921local world = world_new()
16931local A = world:component()
16941local B = world:component()
16951local e = world:entity()
1696N/A
16971world:set(A, jecs.OnRemove, function(entity)
16981world:set(entity, B, true)
16991CHECK(world:get(entity, A))
17001CHECK(world:get(entity, B))
1701N/Aend)
1702N/A
17031world:set(e, A, true)
17041world:remove(e, A)
17051CHECK(not world:get(e, A))
17061CHECK(world:get(e, B))
1707N/Aend
1708N/Aend
1709N/Aend)
1710N/A
17111TEST("change tracking", function()
17121do CASE "#1"
17131local world = world_new()
17141local Foo = world:component() :: Entity
17151local Previous = jecs.Rest
1716N/A
17171local q1 = world
17181:query(Foo)
17191:without(pair(Previous, Foo))
17200:cached()
1721N/A
17221local e1 = world:entity()
17231world:set(e1, Foo, 1)
17241local e2 = world:entity()
17251world:set(e2, Foo, 2)
1726N/A
17271local i = 0
17281for e, new in q1 :: any do
17292i += 1
17302world:set(e, pair(Previous, Foo), new)
1731N/Aend
1732N/A
17331CHECK(i == 2)
17341local j = 0
17351for e, new in q1 :: any do
17360j += 1
17370world:set(e, pair(Previous, Foo), new)
1738N/Aend
1739N/A
17401CHECK(j == 0)
1741N/Aend
1742N/A
17431do CASE "#2"
17441local world = world_new()
17451local component = world:component() :: Entity
17461local tag = world:entity()
17471local previous = jecs.Rest
1748N/A
17491local q1 = world:query(component):without(pair(previous, component), tag):cached()
1750N/A
17511local testEntity = world:entity()
1752N/A
17531world:set(testEntity, component, 10)
1754N/A
17551local i = 0
17561for entity, number in q1 :: any do
17571i += 1
17581world:add(testEntity, tag)
1759N/Aend
1760N/A
17611CHECK(i == 1)
1762N/A
17631for e, n in q1 :: any do
17640world:set(e, pair(previous, component), n)
1765N/Aend
1766N/Aend
1767N/A
1768N/Aend)
1769N/A
17701TEST("repro", function()
17711do CASE "#1"
17721local world = world_new()
17731local reproEntity = world:component()
17741local components = { Cooldown = world:component() :: jecs.Entity }
17751world:set(reproEntity, components.Cooldown, 2)
1776N/A
17771local function updateCooldowns(dt: number)
17782local toRemove = {}
1779N/A
17802local it = world:query(components.Cooldown):iter()
17812for id, cooldown in it do
17822cooldown -= dt
1783N/A
17842if cooldown <= 0 then
17851table.insert(toRemove, id)
1786N/A-- world:remove(id, components.Cooldown)
17870else
17881world:set(id, components.Cooldown, cooldown)
1789N/Aend
1790N/Aend
1791N/A
17922for _, id in toRemove do
17931world:remove(id, components.Cooldown)
17941CHECK(not world:get(id, components.Cooldown))
1795N/Aend
1796N/Aend
1797N/A
17981updateCooldowns(1.5)
17991updateCooldowns(1.5)
1800N/Aend
1801N/A
18021do CASE "#2" -- ISSUE #171
18031local world = world_new()
18041local component1 = world:component()
18051local tag1 = world:entity()
1806N/A
18071local query = world:query(component1):with(tag1):cached()
1808N/A
18091local entity = world:entity()
18101world:set(entity, component1, "some data")
1811N/A
18121local counter = 0
18131for x in query:iter() do
18140counter += 1
1815N/Aend
18161CHECK(counter == 0)
1817N/Aend
1818N/Aend)
1819N/A
18201TEST("wildcard query", function()
18211do CASE "#1"
18221local world = world_new()
18231local pair = jecs.pair
1824N/A
18251local Relation = world:entity()
18261local Wildcard = jecs.Wildcard
18271local A = world:entity()
1828N/A
18291local relationship = pair(Relation, Wildcard)
18301local query = world:query(relationship):cached()
1831N/A
18321local entity = world:entity()
1833N/A
18341local p = pair(Relation, A)
18351CHECK(jecs.pair_first(world, p) == Relation)
18361CHECK(jecs.pair_second(world, p) == A)
18371local w = dwi(world)
18381world:add(entity, pair(Relation, A))
1839N/A
18401local counter = 0
18411for e in query:iter() do
18421counter += 1
1843N/Aend
18441CHECK(counter == 1)
1845N/Aend
18461do CASE "#2"
18471local world = world_new()
18481local pair = jecs.pair
1849N/A
18501local Relation = world:entity()
18511local Wildcard = jecs.Wildcard
18521local A = world:entity()
1853N/A
18541local relationship = pair(Relation, Wildcard)
1855N/A
18561local entity = world:entity()
1857N/A
18581world:add(entity, pair(Relation, A))
1859N/A
18601local counter = 0
18611for e in world:query(relationship):iter() do
18621counter += 1
1863N/Aend
18641CHECK(counter == 1)
1865N/Aend
18661do CASE "#3"
18671local world = world_new()
18681local pair = jecs.pair
1869N/A
18701local Relation = world:entity()
18711local Wildcard = jecs.Wildcard
18721local A = world:entity()
1873N/A
18741local entity = world:entity()
1875N/A
18761world:add(entity, pair(Relation, A))
1877N/A
18781local relationship = pair(Relation, Wildcard)
18791local query = world:query(relationship):cached()
1880N/A
18811local counter = 0
18821for e in query:iter() do
18831counter += 1
1884N/Aend
18851CHECK(counter == 1)
1886N/Aend
1887N/Aend)
1888N/A
18891TEST("world:delete() invokes OnRemove hook", function()
18901do CASE "#1"
18911local world = world_new()
1892N/A
18931local A = world:entity()
18941local entity = world:entity()
1895N/A
18961local called = false
18971world:set(A, jecs.OnRemove, function(e)
18981called = true
1899N/Aend)
1900N/A
19011world:add(entity, A)
19021world:delete(entity)
1903N/A
19041CHECK(called)
1905N/Aend
19061do CASE "#2"
19071local world = world_new()
19081local pair = jecs.pair
1909N/A
19101local Relation = world:entity()
19111local A = world:entity()
19121local B = world:entity()
1913N/A
19141world:add(Relation, pair(jecs.OnDelete, jecs.Delete))
1915N/A
19161local entity = world:entity()
1917N/A
19181local called = false
19191world:set(A, jecs.OnRemove, function(e)
19201called = true
1921N/Aend)
1922N/A
19231world:add(entity, A)
19241world:add(entity, pair(Relation, B))
1925N/A
19261world:delete(B)
1927N/A
19281CHECK(called)
1929N/Aend
19301do CASE "#3"
19311local world = world_new()
19321local pair = jecs.pair
1933N/A
19341local viewingContainer = world:entity()
19351local character = world:entity()
19361local container = world:entity()
1937N/A
19381local called = false
19391world:set(viewingContainer, jecs.OnRemove, function(e)
19401called = true
1941N/Aend)
1942N/A
19431world:add(character, pair(viewingContainer, container))
1944N/A
19451world:delete(container)
1946N/A
19471CHECK(called)
1948N/Aend
1949N/Aend)
19501FINISH()
\ No newline at end of file diff --git a/docs/public/jecs_logo.svg b/docs/public/jecs_logo.svg deleted file mode 100644 index befe822..0000000 --- a/docs/public/jecs_logo.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - -Created with Fabric.js 5.2.4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/resources.md b/docs/resources.md deleted file mode 100644 index 8ce8116..0000000 --- a/docs/resources.md +++ /dev/null @@ -1,78 +0,0 @@ -## Learning - -- [Jecs Demo](https://github.com/Ukendio/jecs/tree/main/demo) -- [Jecs Examples](https://github.com/Ukendio/jecs/tree/main/examples) -- [An Introduction to ECS for Robloxians - @Ukendio](https://devforum.roblox.com/t/all-about-entity-component-system/1664447) -- [Entities, Components and Systems - Mark Jordan](https://medium.com/ingeniouslysimple/entities-components-and-systems-89c31464240d) -- [Why Vanilla ECS is not enough - Sander Mertens](https://ajmmertens.medium.com/why-vanilla-ecs-is-not-enough-d7ed4e3bebe5) -- [Formalisation of Concepts behind ECS and Entitas - Maxim Zaks](https://medium.com/@icex33/formalisation-of-concepts-behind-ecs-and-entitas-8efe535d9516) -- [Entity Component System and Rendering - Our Machinery](https://ourmachinery.com/post/ecs-and-rendering/) -- [Specs and Legion, two very different approaches to ECS - Cora Sherrat](https://csherratt.github.io/blog/posts/specs-and-legion/) -- [Where are my Entities and Components - Sander Mertens](https://ajmmertens.medium.com/building-an-ecs-1-where-are-my-entities-and-components-63d07c7da742) -- [Archetypes and Vectorization - Sander Mertens](https://medium.com/@ajmmertens/building-an-ecs-2-archetypes-and-vectorization-fe21690805f9) -- [Building Games with Entity Relationships - Sander Mertens](https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c) -- [Why it is time to start thinking of games as databases - Sander Mertens](https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3) -- [A Roadmap to Entity Relationships - Sander Mertens](https://ajmmertens.medium.com/a-roadmap-to-entity-relationships-5b1d11ebb4eb) -- [Making the most of Entity Identifiers - Sander Mertens](https://ajmmertens.medium.com/doing-a-lot-with-a-little-ecs-identifiers-25a72bd2647) -- [Why Storing State Machines in ECS is a Bad Idea - Sander Mertens](https://ajmmertens.medium.com/why-storing-state-machines-in-ecs-is-a-bad-idea-742de7a18e59) -- [ECS back & forth - Michele Caini](https://skypjack.github.io/2019-02-14-ecs-baf-part-1/) -- [Sparse Set - Geeks for Geeks](https://www.geeksforgeeks.org/sparse-set/) -- [Taking the Entity-Component-System Architecture Seriously - @alice-i-cecile](https://www.youtube.com/watch?v=VpiprNBEZsk) -- [Overwatch Gameplay Architecture and Netcode - Blizzard, GDC](https://www.youtube.com/watch?v=W3aieHjyNvw) -- [Data-Oriented Design and C++ - Mike Acton, CppCon](https://www.youtube.com/watch?v=rX0ItVEVjHc) -- [Using Rust for Game Development - Catherine West, RustConf](https://www.youtube.com/watch?v=aKLntZcp27M) -- [CPU caches and why you should care - Scott Meyers, NDC](https://vimeo.com/97337258) -- [Building a fast ECS on top of a slow ECS - @UnitOfTime](https://youtu.be/71RSWVyOMEY) -- [Culling the Battlefield: Data Oriented Design in Practice - DICE, GDC](https://www.gdcvault.com/play/1014491/Culling-the-Battlefield-Data-Oriented) -- [Game Engine Entity/Object Systems - Bobby Anguelov](https://www.youtube.com/watch?v=jjEsB611kxs) -- [Understanding Data Oriented Design for Entity Component Systems - Unity GDC](https://www.youtube.com/watch?v=0_Byw9UMn9g) -- [Understanding Data Oriented Design - Unity](https://learn.unity.com/tutorial/part-1-understand-data-oriented-design?courseId=60132919edbc2a56f9d439c3&signup=true&uv=2020.1) -- [Data Oriented Design - Richard Fabian](https://www.dataorienteddesign.com/dodbook/dodmain.html) -- [Interactive app for browsing systems of City Skylines 2 - @Captain-Of-Coit](https://captain-of-coit.github.io/cs2-ecs-explorer/) -- [Awesome Entity Component System (link collection related to ECS) - Jeongseok Lee](https://github.com/jslee02/awesome-entity-component-system) -- [Hibitset - DOCS.RS](https://docs.rs/hibitset/0.6.3/hibitset/) - -## Addons - -A collection of third-party jecs addons made by the community. If you would like to share what you're working on, [submit a pull request](/learn/contributing/pull-requests#addons)! - -### Development tools - -#### [jabby](https://github.com/alicesaidhi/jabby) -A jecs debugger with a string-based query language and entity editing capabilities. - -#### [jecs_entity_visualiser](https://github.com/Ukendio/jecs/blob/main/tools/entity_visualiser.luau) -A simple entity and component visualiser in the output - -#### [jecs_lifetime_tracker](https://github.com/Ukendio/jecs/blob/main/tools/lifetime_tracker.luau) -A tool for inspecting entity lifetimes - -### Helpers - -#### [jecs_observers](https://github.com/Ukendio/jecs/blob/main/addons/observers.luau) -Observers for queries and signals for components - -### Schedulers - -#### [lockstep scheduler](https://gist.github.com/1Axen/6d4f78b3454cf455e93794505588354b) -A simple fixed step system scheduler. - -#### [rubine](https://github.com/Mark-Marks/rubine) -An ergonomic, runtime agnostic scheduler for Jecs - -#### [jam](https://github.com/revvy02/Jam) -Provides hooks and a scheduler that implements jabby and a topographical runtime - -#### [planck](https://github.com/YetAnotherClown/planck) -An agnostic scheduler inspired by Bevy and Flecs, with core features including phases, pipelines, run conditions, and startup systems. -Planck also provides plugins for Jabby, Matter Hooks, and more. - -### Networking - -#### [feces](https://github.com/NeonD00m/feces) -A generalized replication system for jecs - -### Input - -#### [Axis](https://github.com/NeonD00m/axis) -An agnostic, simple and versatile input library for ECS