mirror of
https://github.com/Ukendio/jecs.git
synced 2026-03-18 00:44:32 +00:00
Compare commits
No commits in common. "main" and "v0.10.2" have entirely different histories.
13 changed files with 427 additions and 453 deletions
143
.github/workflows/release.yaml
vendored
143
.github/workflows/release.yaml
vendored
|
|
@ -1,95 +1,92 @@
|
||||||
name: release
|
name: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags: ["v*", "workflow_dispatch"]
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Project
|
- name: Checkout Project
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rokit
|
- name: Install Rokit
|
||||||
uses: CompeyDev/setup-rokit@v0.1.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: wally install
|
run: wally install
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: rojo build --output build.rbxm default.project.json
|
run: rojo build --output build.rbxm default.project.json
|
||||||
|
|
||||||
- name: Upload Build Artifact
|
- name: Upload Build Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: build.rbxm
|
path: build.rbxm
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
needs: build
|
needs: [build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
permissions:
|
||||||
- name: Checkout Project
|
contents: write
|
||||||
uses: actions/checkout@v4
|
steps:
|
||||||
|
- name: Checkout Project
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download Build
|
- name: Download Jecs Build
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: build
|
path: build
|
||||||
|
|
||||||
- name: Rename Build
|
- name: Rename Build
|
||||||
run: mv build/build.rbxm jecs.rbxm
|
run: mv build/build.rbxm jecs.rbxm
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
name: Jecs ${{ github.ref_name }}
|
name: Jecs ${{ github.ref_name }}
|
||||||
tag_name: ${{ github.ref_name }}
|
files: |
|
||||||
files: jecs.rbxm
|
jecs.rbxm
|
||||||
|
|
||||||
publish-wally:
|
publish-wally:
|
||||||
name: Publish to Wally
|
name: Publish to Wally
|
||||||
needs: release
|
needs: [release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Project
|
- name: Checkout Project
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rokit
|
- name: Install Rokit
|
||||||
uses: CompeyDev/setup-rokit@v0.1.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Wally Login
|
- name: Wally Login
|
||||||
run: wally login --token ${{ secrets.WALLY_AUTH_TOKEN }}
|
run: wally login --token ${{ secrets.WALLY_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: wally publish
|
run: wally publish
|
||||||
|
|
||||||
publish-npm:
|
publish-npm:
|
||||||
name: Publish to NPM
|
name: Publish to NPM
|
||||||
needs: release
|
needs: [release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Project
|
- name: Checkout Project
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
node-version: "24"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Install
|
- run: npm install
|
||||||
run: npm install
|
- run: npm publish
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: npm publish
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ end
|
||||||
|
|
||||||
-- rel = ecs_ensure_entity(world, rel)
|
-- rel = ecs_ensure_entity(world, rel)
|
||||||
--
|
--
|
||||||
|
-- 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
|
||||||
-- tgt = ecs_ensure_entity(world, tgt)
|
-- tgt = ecs_ensure_entity(world, tgt)
|
||||||
|
|
||||||
-- return jecs.pair(rel, tgt)
|
-- return jecs.pair(rel, tgt)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ about the code base is lost, ability to work on it at the same level of quality
|
||||||
is lost. Over time code quality will decline as code size grows.
|
is lost. Over time code quality will decline as code size grows.
|
||||||
|
|
||||||
- Tacit knowledge is very hard to recover by looking at a maze of code,
|
- Tacit knowledge is very hard to recover by looking at a maze of code,
|
||||||
and it takes a long time to do so.
|
and it takes
|
||||||
|
|
||||||
- You will often hear that "every semantic distinction deserves its own
|
- You will often hear that "every semantic distinction deserves its own
|
||||||
component or tag". Sometimes this is correct. A well chosen component boundary
|
component or tag". Sometimes this is correct. A well chosen component boundary
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||||
if not r then return end
|
if not r then return end
|
||||||
|
|
||||||
local src = r.archetype
|
local src = r.archetype
|
||||||
|
if not src then return end
|
||||||
|
|
||||||
if not archetypes[src.id] then return end
|
if not archetypes[src.id] then return end
|
||||||
|
|
||||||
|
|
@ -286,6 +287,9 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
if not archetype then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||||
last_old_archetype = nil
|
last_old_archetype = nil
|
||||||
|
|
@ -305,6 +309,9 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||||
|
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
if not archetype then
|
||||||
|
return
|
||||||
|
end
|
||||||
if last_old_archetype == archetype then
|
if last_old_archetype == archetype then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -325,6 +332,9 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
if not archetype then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||||
callback_removed(entity)
|
callback_removed(entity)
|
||||||
|
|
@ -339,6 +349,9 @@ local function monitors_new(query: jecs.Query<...any>): Monitor
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
if not archetype then
|
||||||
|
return
|
||||||
|
end
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
|
||||||
if archetypes[dst.id] then
|
if archetypes[dst.id] then
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
local jecs = require("@jecs")
|
|
||||||
|
|
||||||
local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity<any>, ctx: {[jecs.Entity<any>]: jecs.Entity<any>})
|
|
||||||
local e = 0
|
|
||||||
|
|
||||||
local ser_id = id
|
|
||||||
|
|
||||||
local deser_id = ctx[ser_id]
|
|
||||||
if not deser_id then
|
|
||||||
if not world:exists(ser_id)
|
|
||||||
or (world:contains(ser_id) and not world:get(ser_id, jecs.Name))
|
|
||||||
then
|
|
||||||
deser_id = world:entity(id)
|
|
||||||
else
|
|
||||||
if world:contains(ser_id) then
|
|
||||||
if world:has(ser_id, jecs.Name) then
|
|
||||||
deser_id = ser_id
|
|
||||||
else
|
|
||||||
deser_id = world:entity(ser_id)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if world:exists(ser_id) then
|
|
||||||
deser_id = world:entity()
|
|
||||||
else
|
|
||||||
deser_id = world:entity(ser_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ctx[ser_id] = deser_id
|
|
||||||
end
|
|
||||||
|
|
||||||
e = deser_id
|
|
||||||
|
|
||||||
return e
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ecs_deser_pairs(world: jecs.World, rel, tgt, ctx)
|
|
||||||
rel = ecs_ensure_entity(world, rel, ctx)
|
|
||||||
tgt = ecs_ensure_entity(world, tgt, ctx)
|
|
||||||
|
|
||||||
return jecs.pair(rel, tgt)
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
ecs_ensure_entity = ecs_ensure_entity,
|
|
||||||
ecs_deser_pairs = ecs_deser_pairs,
|
|
||||||
}
|
|
||||||
0
modules/PerfGraph/perfgraph.py → modules/perfgraph.py
Normal file → Executable file
0
modules/PerfGraph/perfgraph.py → modules/perfgraph.py
Normal file → Executable file
39
modules/remotes.luau
Executable file
39
modules/remotes.luau
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
-- A simple way to safely type remote events without hassle
|
||||||
|
|
||||||
|
local ReplicatedStorage = require("@game/ReplicatedStorage")
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local ty = require("./")
|
||||||
|
|
||||||
|
type Remote<T...> = {
|
||||||
|
FireClient: (Remote<T...>, Player, T...) -> (),
|
||||||
|
FireAllClients: (Remote<T...>, T...) -> (),
|
||||||
|
FireServer: (Remote<T...>, T...) -> (),
|
||||||
|
OnServerEvent: RBXScriptSignal<(Player, T...)>,
|
||||||
|
OnClientEvent: RBXScriptSignal<T...>
|
||||||
|
}
|
||||||
|
|
||||||
|
local function stream_ensure(name)
|
||||||
|
local remote = ReplicatedStorage:FindFirstChild(name)
|
||||||
|
if not remote then
|
||||||
|
remote = Instance.new("RemoteEvent")
|
||||||
|
remote.Name = name
|
||||||
|
remote.Parent = ReplicatedStorage
|
||||||
|
end
|
||||||
|
return remote
|
||||||
|
end
|
||||||
|
|
||||||
|
local function datagram_ensure(name)
|
||||||
|
local remote = ReplicatedStorage:FindFirstChild(name)
|
||||||
|
if not remote then
|
||||||
|
remote = Instance.new("UnreliableRemoteEvent")
|
||||||
|
remote.Name = name
|
||||||
|
remote.Parent = ReplicatedStorage
|
||||||
|
end
|
||||||
|
return remote
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
input = datagram_ensure("input") :: Remote<string>,
|
||||||
|
replication = stream_ensure("replication") :: Remote<snapshot>
|
||||||
|
}
|
||||||
0
modules/PerfGraph/svg.py → modules/svg.py
Normal file → Executable file
0
modules/PerfGraph/svg.py → modules/svg.py
Normal file → Executable file
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.11.0",
|
"version": "0.10.2",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "src/jecs.luau",
|
"main": "src/jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Ukendio/jecs"
|
"url": "git+https://github.com/ukendio/jecs.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Ukendio",
|
"author": "Ukendio",
|
||||||
|
|
|
||||||
342
src/jecs.luau
342
src/jecs.luau
|
|
@ -156,7 +156,6 @@ type componentrecord = {
|
||||||
counts: { [i53]: number },
|
counts: { [i53]: number },
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
cache: { number },
|
|
||||||
|
|
||||||
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
|
|
@ -568,21 +567,17 @@ end
|
||||||
|
|
||||||
local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range"
|
local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range"
|
||||||
|
|
||||||
local function ENTITY_INDEX_NEW_ID(world: world): i53
|
local function ENTITY_INDEX_NEW_ID(entity_index: entityindex): i53
|
||||||
local entity_index = world.entity_index
|
|
||||||
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
|
||||||
local dense_array = entity_index.dense_array
|
local dense_array = entity_index.dense_array
|
||||||
local alive_count = entity_index.alive_count
|
local alive_count = entity_index.alive_count
|
||||||
local sparse_array = entity_index.sparse_array
|
local sparse_array = entity_index.sparse_array
|
||||||
local max_id = entity_index.max_id
|
local max_id = entity_index.max_id
|
||||||
local next_count = alive_count + 1
|
|
||||||
|
|
||||||
if alive_count < max_id then
|
if alive_count < max_id then
|
||||||
local id = dense_array[next_count]
|
alive_count += 1
|
||||||
if id then
|
entity_index.alive_count = alive_count
|
||||||
entity_index.alive_count = next_count
|
local id = dense_array[alive_count]
|
||||||
return id
|
return id
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local id = max_id + 1
|
local id = max_id + 1
|
||||||
|
|
@ -590,9 +585,10 @@ local function ENTITY_INDEX_NEW_ID(world: world): i53
|
||||||
ecs_assert(range_end == nil or id < range_end, ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY)
|
ecs_assert(range_end == nil or id < range_end, ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY)
|
||||||
|
|
||||||
entity_index.max_id = id
|
entity_index.max_id = id
|
||||||
entity_index.alive_count = next_count
|
alive_count += 1
|
||||||
dense_array[next_count] = id
|
entity_index.alive_count = alive_count
|
||||||
sparse_array[id] = { dense = next_count, row = 0, archetype = ROOT_ARCHETYPE }
|
dense_array[alive_count] = id
|
||||||
|
sparse_array[id] = { dense = alive_count } :: record
|
||||||
|
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
@ -919,7 +915,6 @@ local function id_record_create(
|
||||||
records = {},
|
records = {},
|
||||||
counts = {},
|
counts = {},
|
||||||
flags = flags,
|
flags = flags,
|
||||||
cache = {},
|
|
||||||
|
|
||||||
on_add = on_add,
|
on_add = on_add,
|
||||||
on_change = on_change,
|
on_change = on_change,
|
||||||
|
|
@ -957,7 +952,6 @@ local function archetype_append_to_records(
|
||||||
idr_records[archetype_id] = index
|
idr_records[archetype_id] = index
|
||||||
idr_counts[archetype_id] = 1
|
idr_counts[archetype_id] = 1
|
||||||
columns_map[id] = column
|
columns_map[id] = column
|
||||||
table.insert(idr.cache, archetype_id)
|
|
||||||
else
|
else
|
||||||
local max_count = idr_counts[archetype_id] + 1
|
local max_count = idr_counts[archetype_id] + 1
|
||||||
idr_counts[archetype_id] = max_count
|
idr_counts[archetype_id] = max_count
|
||||||
|
|
@ -1049,10 +1043,8 @@ local function world_range(world: world, range_begin: number, range_end: number?
|
||||||
for i = max_id + 1, range_begin do
|
for i = max_id + 1, range_begin do
|
||||||
dense_array[i] = i
|
dense_array[i] = i
|
||||||
sparse_array[i] = {
|
sparse_array[i] = {
|
||||||
dense = 0,
|
dense = 0
|
||||||
row = 0,
|
} :: record
|
||||||
archetype = world.ROOT_ARCHETYPE
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
entity_index.max_id = range_begin
|
entity_index.max_id = range_begin
|
||||||
entity_index.alive_count = range_begin
|
entity_index.alive_count = range_begin
|
||||||
|
|
@ -1092,11 +1084,10 @@ local function find_archetype_without(
|
||||||
): archetype
|
): archetype
|
||||||
local id_types = node.types
|
local id_types = node.types
|
||||||
local at = table.find(id_types, id)
|
local at = table.find(id_types, id)
|
||||||
if at == nil then
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
local dst = table.clone(id_types)
|
local dst = table.clone(id_types)
|
||||||
table.remove(dst, at)
|
table.remove(dst, at)
|
||||||
|
|
||||||
return archetype_ensure(world, dst)
|
return archetype_ensure(world, dst)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1219,13 +1210,6 @@ local function archetype_destroy(world: world, archetype: archetype)
|
||||||
if archetype == world.ROOT_ARCHETYPE then
|
if archetype == world.ROOT_ARCHETYPE then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
-- RAII / idempotent: already destroyed or still has entities → no-op
|
|
||||||
if world.archetypes[archetype.id] ~= archetype then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if #archetype.entities > 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
local archetype_edges = world.archetype_edges
|
local archetype_edges = world.archetype_edges
|
||||||
|
|
@ -2577,7 +2561,7 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
end
|
end
|
||||||
local from = r.archetype
|
local from = r.archetype
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
if from == world.ROOT_ARCHETYPE then
|
if not from then
|
||||||
local dst_types = table.clone(ids)
|
local dst_types = table.clone(ids)
|
||||||
table.sort(dst_types)
|
table.sort(dst_types)
|
||||||
|
|
||||||
|
|
@ -2665,8 +2649,6 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local ON_REMOVE_STRUCTURAL_WARN = "jecs: on_remove must not perform structural changes (world:add/world:remove); this will be removed as a lint in future versions and can cause silent failures"
|
|
||||||
|
|
||||||
local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 })
|
local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 })
|
||||||
local entity_index = world.entity_index
|
local entity_index = world.entity_index
|
||||||
local r = entity_index_try_get(entity_index, entity)
|
local r = entity_index_try_get(entity_index, entity)
|
||||||
|
|
@ -2675,13 +2657,14 @@ local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 })
|
||||||
end
|
end
|
||||||
local from = r.archetype
|
local from = r.archetype
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
|
if not from then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local remove: { [i53]: boolean } = {}
|
local remove: { [i53]: boolean } = {}
|
||||||
|
|
||||||
local columns_map = from.columns_map
|
local columns_map = from.columns_map
|
||||||
|
|
||||||
local dst_types = table.clone(from.types) :: { i53 }
|
|
||||||
|
|
||||||
for i, id in ids do
|
for i, id in ids do
|
||||||
if not columns_map[id] then
|
if not columns_map[id] then
|
||||||
continue
|
continue
|
||||||
|
|
@ -2693,15 +2676,22 @@ local function ecs_bulk_remove(world: world, entity: i53, ids: { i53 })
|
||||||
local on_remove = idr.on_remove
|
local on_remove = idr.on_remove
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id)
|
on_remove(entity, id)
|
||||||
if from ~= r.archetype then
|
|
||||||
error(ON_REMOVE_STRUCTURAL_WARN)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local to = r.archetype
|
||||||
|
if from ~= to then
|
||||||
|
from = to
|
||||||
|
end
|
||||||
|
|
||||||
|
local dst_types = table.clone(from.types) :: { i53 }
|
||||||
|
|
||||||
|
for id in remove do
|
||||||
local at = table.find(dst_types, id)
|
local at = table.find(dst_types, id)
|
||||||
table.remove(dst_types, at)
|
table.remove(dst_types, at)
|
||||||
end
|
end
|
||||||
|
|
||||||
local to = archetype_ensure(world, dst_types)
|
to = archetype_ensure(world, dst_types)
|
||||||
|
|
||||||
if from ~= to then
|
if from ~= to then
|
||||||
entity_move(entity_index, entity, r, to)
|
entity_move(entity_index, entity, r, to)
|
||||||
|
|
@ -2712,12 +2702,12 @@ local function world_new(DEBUG: boolean?)
|
||||||
local eindex_dense_array = {} :: { i53 }
|
local eindex_dense_array = {} :: { i53 }
|
||||||
local eindex_sparse_array = {} :: { record }
|
local eindex_sparse_array = {} :: { record }
|
||||||
|
|
||||||
local entity_index: entityindex = {
|
local entity_index = {
|
||||||
dense_array = eindex_dense_array,
|
dense_array = eindex_dense_array,
|
||||||
sparse_array = eindex_sparse_array,
|
sparse_array = eindex_sparse_array,
|
||||||
alive_count = 0,
|
alive_count = 0,
|
||||||
max_id = 0,
|
max_id = 0,
|
||||||
}
|
} :: entityindex
|
||||||
|
|
||||||
-- NOTE(marcus): with the way the component index is accessed, we want to
|
-- NOTE(marcus): with the way the component index is accessed, we want to
|
||||||
-- ensure that components range has fast access.
|
-- ensure that components range has fast access.
|
||||||
|
|
@ -2757,6 +2747,31 @@ local function world_new(DEBUG: boolean?)
|
||||||
signals = signals,
|
signals = signals,
|
||||||
} :: world
|
} :: world
|
||||||
|
|
||||||
|
|
||||||
|
local function entity_index_new_id(entity_index: entityindex): i53
|
||||||
|
local alive_count = entity_index.alive_count
|
||||||
|
local max_id = entity_index.max_id
|
||||||
|
|
||||||
|
if alive_count < max_id then
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
local id = eindex_dense_array[alive_count]
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
local id = max_id + 1
|
||||||
|
local range_end = entity_index.range_end
|
||||||
|
ecs_assert(range_end == nil or id < range_end, ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY)
|
||||||
|
|
||||||
|
entity_index.max_id = id
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
eindex_dense_array[alive_count] = id
|
||||||
|
eindex_sparse_array[id] = { dense = alive_count } :: record
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
local ROOT_ARCHETYPE = archetype_create(world, {}, "")
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||||
|
|
||||||
|
|
@ -2886,7 +2901,9 @@ local function world_new(DEBUG: boolean?)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local src = record.archetype
|
local from: archetype = record.archetype
|
||||||
|
local ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||||
|
local src = from or ROOT_ARCHETYPE
|
||||||
local column = src.columns_map[id]
|
local column = src.columns_map[id]
|
||||||
if column then
|
if column then
|
||||||
local idr = component_index[id]
|
local idr = component_index[id]
|
||||||
|
|
@ -2916,9 +2933,9 @@ local function world_new(DEBUG: boolean?)
|
||||||
local id_types = src.types
|
local id_types = src.types
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
if src ~= record.archetype then
|
src = record.archetype
|
||||||
error(ON_REMOVE_STRUCTURAL_WARN)
|
id_types = src.types
|
||||||
end
|
cr = idr.records[src.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
to = exclusive_traverse_add(src, cr, id)
|
to = exclusive_traverse_add(src, cr, id)
|
||||||
|
|
@ -2941,8 +2958,11 @@ local function world_new(DEBUG: boolean?)
|
||||||
if cr then
|
if cr then
|
||||||
local id_types = src.types
|
local id_types = src.types
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
if src ~= record.archetype then
|
local arche = record.archetype
|
||||||
error(ON_REMOVE_STRUCTURAL_WARN)
|
if src ~= arche then
|
||||||
|
id_types = arche.types
|
||||||
|
cr = idr.records[arche.id]
|
||||||
|
to = exclusive_traverse_add(arche, cr, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -2961,9 +2981,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
idr = component_index[id]
|
idr = component_index[id]
|
||||||
end
|
end
|
||||||
|
|
||||||
local ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
if from then
|
||||||
local src_is_root_archetype = src == ROOT_ARCHETYPE
|
|
||||||
if not src_is_root_archetype then
|
|
||||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||||
inner_entity_move(entity, record, to)
|
inner_entity_move(entity, record, to)
|
||||||
else
|
else
|
||||||
|
|
@ -2990,7 +3008,9 @@ local function world_new(DEBUG: boolean?)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local src = record.archetype
|
local from = record.archetype
|
||||||
|
local ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
||||||
|
local src = from or ROOT_ARCHETYPE
|
||||||
if src.columns_map[id] then
|
if src.columns_map[id] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -3012,9 +3032,10 @@ local function world_new(DEBUG: boolean?)
|
||||||
local id_types = src.types
|
local id_types = src.types
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id_types[cr])
|
on_remove(entity, id_types[cr])
|
||||||
if src ~= record.archetype then
|
|
||||||
error(ON_REMOVE_STRUCTURAL_WARN)
|
src = record.archetype
|
||||||
end
|
id_types = src.types
|
||||||
|
cr = idr.records[src.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
to = exclusive_traverse_add(src, cr, id)
|
to = exclusive_traverse_add(src, cr, id)
|
||||||
|
|
@ -3060,9 +3081,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
idr = component_index[id]
|
idr = component_index[id]
|
||||||
end
|
end
|
||||||
|
|
||||||
local ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
if from then
|
||||||
local src_is_root_archetype = src == ROOT_ARCHETYPE
|
|
||||||
if not src_is_root_archetype then
|
|
||||||
inner_entity_move(entity, record, to)
|
inner_entity_move(entity, record, to)
|
||||||
else
|
else
|
||||||
if #to.types > 0 then
|
if #to.types > 0 then
|
||||||
|
|
@ -3085,6 +3104,9 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
local columns_map = archetype.columns_map
|
local columns_map = archetype.columns_map
|
||||||
local row = record.row
|
local row = record.row
|
||||||
|
|
@ -3142,8 +3164,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
local n = #listeners
|
local n = #listeners
|
||||||
local i = table.find(listeners, fn::Listener<any>)
|
local i = table.find(listeners, fn)
|
||||||
assert(i, "Listener not found, maybe you tried to disconnect it twice")
|
|
||||||
listeners[i] = listeners[n]
|
listeners[i] = listeners[n]
|
||||||
listeners[n] = nil
|
listeners[n] = nil
|
||||||
end
|
end
|
||||||
|
|
@ -3188,8 +3209,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
table.insert(listeners, fn)
|
table.insert(listeners, fn)
|
||||||
return function()
|
return function()
|
||||||
local n = #listeners
|
local n = #listeners
|
||||||
local i = table.find(listeners, fn::Listener<any>)
|
local i = table.find(listeners, fn)
|
||||||
assert(i, "Listener not found, maybe you tried to disconnect it twice")
|
|
||||||
listeners[i] = listeners[n]
|
listeners[i] = listeners[n]
|
||||||
listeners[n] = nil
|
listeners[n] = nil
|
||||||
end
|
end
|
||||||
|
|
@ -3234,7 +3254,6 @@ local function world_new(DEBUG: boolean?)
|
||||||
return function()
|
return function()
|
||||||
local n = #listeners
|
local n = #listeners
|
||||||
local i = table.find(listeners, fn::Listener<any>)
|
local i = table.find(listeners, fn::Listener<any>)
|
||||||
assert(i, "Listener not found, maybe you tried to disconnect it twice")
|
|
||||||
listeners[i] = listeners[n]
|
listeners[i] = listeners[n]
|
||||||
listeners[n] = nil
|
listeners[n] = nil
|
||||||
end
|
end
|
||||||
|
|
@ -3307,13 +3326,10 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_entity(world: world, entity: i53?): i53
|
local function world_entity(world: world, entity: i53?): i53
|
||||||
local sparse_array = eindex_sparse_array
|
|
||||||
local dense_array = eindex_dense_array
|
|
||||||
|
|
||||||
if entity then
|
if entity then
|
||||||
local index = ECS_ID(entity)
|
local index = ECS_ID(entity)
|
||||||
local alive_count = entity_index.alive_count
|
local alive_count = entity_index.alive_count
|
||||||
local r = sparse_array[index]
|
local r = eindex_sparse_array[index]
|
||||||
if r then
|
if r then
|
||||||
local dense = r.dense
|
local dense = r.dense
|
||||||
|
|
||||||
|
|
@ -3323,17 +3339,17 @@ local function world_new(DEBUG: boolean?)
|
||||||
alive_count += 1
|
alive_count += 1
|
||||||
entity_index.alive_count = alive_count
|
entity_index.alive_count = alive_count
|
||||||
r.dense = alive_count
|
r.dense = alive_count
|
||||||
dense_array[alive_count] = entity
|
eindex_dense_array[alive_count] = entity
|
||||||
return entity
|
return entity
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If dense > 0, check if there's an existing entity at that position
|
-- If dense > 0, check if there's an existing entity at that position
|
||||||
local existing_entity = dense_array[dense]
|
local existing_entity = eindex_dense_array[dense]
|
||||||
if existing_entity and existing_entity ~= entity then
|
if existing_entity and existing_entity ~= entity then
|
||||||
alive_count += 1
|
alive_count += 1
|
||||||
entity_index.alive_count = alive_count
|
entity_index.alive_count = alive_count
|
||||||
r.dense = alive_count
|
r.dense = alive_count
|
||||||
dense_array[alive_count] = entity
|
eindex_dense_array[alive_count] = entity
|
||||||
return entity
|
return entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3342,41 +3358,30 @@ local function world_new(DEBUG: boolean?)
|
||||||
local max_id = entity_index.max_id
|
local max_id = entity_index.max_id
|
||||||
|
|
||||||
if index > max_id then
|
if index > max_id then
|
||||||
|
-- Pre-populate all intermediate IDs to keep sparse_array as an array
|
||||||
for i = max_id + 1, index - 1 do
|
for i = max_id + 1, index - 1 do
|
||||||
sparse_array[i] = { dense = 0, row = 0, archetype = ROOT_ARCHETYPE }
|
if not eindex_sparse_array[i] then
|
||||||
|
-- NOTE(marcus): We have to do this check to see if
|
||||||
|
-- they exist first because world:range() may have
|
||||||
|
-- pre-populated some slots already.
|
||||||
|
end
|
||||||
|
|
||||||
|
eindex_sparse_array[i] = { dense = 0 } :: record
|
||||||
end
|
end
|
||||||
entity_index.max_id = index
|
entity_index.max_id = index
|
||||||
end
|
end
|
||||||
|
|
||||||
alive_count += 1
|
alive_count += 1
|
||||||
entity_index.alive_count = alive_count
|
entity_index.alive_count = alive_count
|
||||||
dense_array[alive_count] = entity
|
eindex_dense_array[alive_count] = entity
|
||||||
|
|
||||||
r = { dense = alive_count, row = 0, archetype = ROOT_ARCHETYPE }
|
r = { dense = alive_count } :: record
|
||||||
sparse_array[index] = r
|
eindex_sparse_array[index] = r
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return entity_index_new_id(entity_index)
|
||||||
local alive_count = entity_index.alive_count
|
|
||||||
local max_id = entity_index.max_id
|
|
||||||
local next_count = alive_count + 1
|
|
||||||
if alive_count < max_id then
|
|
||||||
entity = dense_array[next_count]
|
|
||||||
if entity then
|
|
||||||
entity_index.alive_count = next_count
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local id = max_id + 1
|
|
||||||
local range_end = entity_index.range_end
|
|
||||||
ecs_assert(range_end == nil or id < range_end, ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY)
|
|
||||||
entity_index.max_id = id
|
|
||||||
entity_index.alive_count = next_count
|
|
||||||
dense_array[next_count] = id
|
|
||||||
sparse_array[id] = { dense = next_count, row = 0, archetype = ROOT_ARCHETYPE }
|
|
||||||
return id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_remove(world: world, entity: i53, id: i53)
|
local function world_remove(world: world, entity: i53, id: i53)
|
||||||
|
|
@ -3386,6 +3391,10 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
local from = record.archetype
|
local from = record.archetype
|
||||||
|
|
||||||
|
if not from then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if from.columns_map[id] then
|
if from.columns_map[id] then
|
||||||
local idr = world.component_index[id]
|
local idr = world.component_index[id]
|
||||||
local on_remove = idr.on_remove
|
local on_remove = idr.on_remove
|
||||||
|
|
@ -3463,6 +3472,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
for i = n, 1, -1 do
|
for i = n, 1, -1 do
|
||||||
world_delete(world, entities[i])
|
world_delete(world, entities[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -3479,10 +3489,13 @@ local function world_new(DEBUG: boolean?)
|
||||||
local r = eindex_sparse_array[ECS_ID(e :: number)]
|
local r = eindex_sparse_array[ECS_ID(e :: number)]
|
||||||
local from = r.archetype
|
local from = r.archetype
|
||||||
if from ~= idr_archetype then
|
if from ~= idr_archetype then
|
||||||
|
-- unfortunately the on_remove hook allows a window where `e` can have changed archetype
|
||||||
|
-- this is hypothetically not that expensive of an operation anyways
|
||||||
to = archetype_traverse_remove(world, entity, from)
|
to = archetype_traverse_remove(world, entity, from)
|
||||||
end
|
end
|
||||||
inner_entity_move(e, r, to)
|
inner_entity_move(e, r, to)
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -3495,6 +3508,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
local e = entities[i]
|
local e = entities[i]
|
||||||
entity_move(entity_index, e, eindex_sparse_array[ECS_ID(e :: number)], to)
|
entity_move(entity_index, e, eindex_sparse_array[ECS_ID(e :: number)], to)
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -3502,10 +3516,11 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr_t then
|
if idr_t then
|
||||||
local to_remove = {} :: { [i53]: componentrecord }
|
local archetype_ids = idr_t.records
|
||||||
local cache = idr_t.cache
|
local to_remove = {}:: { [i53]: componentrecord}
|
||||||
for i = #cache, 1, -1 do
|
local did_cascade_delete = false
|
||||||
local archetype_id = cache[i]
|
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
if not idr_t_archetype then
|
if not idr_t_archetype then
|
||||||
continue
|
continue
|
||||||
|
|
@ -3519,14 +3534,20 @@ local function world_new(DEBUG: boolean?)
|
||||||
if not ECS_IS_PAIR(id) then
|
if not ECS_IS_PAIR(id) then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
local object = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id))
|
local object = entity_index_get_alive(
|
||||||
|
entity_index, ECS_PAIR_SECOND(id))
|
||||||
if object ~= entity then
|
if object ~= entity then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
local id_record = component_index[id]
|
local id_record = component_index[id]
|
||||||
local has_delete = bit32.btest(id_record.flags, ECS_ID_DELETE)
|
local flags = id_record.flags
|
||||||
if has_delete then
|
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
||||||
deleted_any = true
|
if flags_delete_mask then
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
local child = entities[i]
|
||||||
|
world_delete(world, child)
|
||||||
|
end
|
||||||
|
deleted_any = true
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
to_remove[id] = id_record
|
to_remove[id] = id_record
|
||||||
|
|
@ -3534,50 +3555,72 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if deleted_any then
|
if deleted_any then
|
||||||
for row = #entities, 1, -1 do
|
did_cascade_delete = true
|
||||||
world_delete(world, entities[row])
|
continue
|
||||||
end
|
end
|
||||||
archetype_destroy(world, idr_t_archetype)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
if remove_count == 1 then
|
|
||||||
local id, id_record = next(to_remove)
|
|
||||||
local to = archetype_traverse_remove(world, id::i53, idr_t_archetype)
|
|
||||||
local on_remove = id_record.on_remove
|
|
||||||
for row = #entities, 1, -1 do
|
|
||||||
local child = entities[row]
|
|
||||||
local r = entity_index_try_get_unsafe(child)::record
|
|
||||||
local dst = to
|
|
||||||
if on_remove then
|
|
||||||
on_remove(child, id :: i53)
|
|
||||||
end
|
|
||||||
inner_entity_move(child, r, dst)
|
|
||||||
end
|
|
||||||
archetype_destroy(world, idr_t_archetype)
|
|
||||||
else
|
|
||||||
local dst_types = table.clone(idr_t_archetype.types)
|
|
||||||
for id in to_remove do
|
|
||||||
table.remove(dst_types, table.find(dst_types, id))
|
|
||||||
end
|
|
||||||
|
|
||||||
local dst = archetype_ensure(world, dst_types)
|
if remove_count == 1 then
|
||||||
|
local id, id_record = next(to_remove)
|
||||||
|
local to_u = archetype_traverse_remove(world, id :: i53, idr_t_archetype)
|
||||||
|
local on_remove = id_record.on_remove
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
local child = entities[i]
|
||||||
|
local r = entity_index_try_get_unsafe(child) :: record
|
||||||
|
local to = to_u
|
||||||
|
if on_remove then
|
||||||
|
on_remove(child, id :: i53)
|
||||||
|
local src = r.archetype
|
||||||
|
if src ~= idr_t_archetype then
|
||||||
|
to = archetype_traverse_remove(world, id::i53, src)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for row = #entities, 1, -1 do
|
inner_entity_move(child, r, to)
|
||||||
local child = entities[row]
|
end
|
||||||
local r = entity_index_try_get_unsafe(child) :: record
|
elseif remove_count > 1 then
|
||||||
for id, component_record in to_remove do
|
local dst_types = table.clone(idr_t_types)
|
||||||
local on_remove = component_record.on_remove
|
for id, component_record in to_remove do
|
||||||
if on_remove then
|
table.remove(dst_types, table.find(dst_types, id))
|
||||||
on_remove(child, id)
|
end
|
||||||
end
|
|
||||||
end
|
local to_u = archetype_ensure(world, dst_types)
|
||||||
inner_entity_move(child, r, dst)
|
for i = #entities, 1, -1 do
|
||||||
|
local child = entities[i]
|
||||||
|
local r = entity_index_try_get_unsafe(child) :: record
|
||||||
|
|
||||||
|
local to = to_u
|
||||||
|
for id, component_record in to_remove do
|
||||||
|
local on_remove = component_record.on_remove
|
||||||
|
if on_remove then
|
||||||
|
on_remove(child, id)
|
||||||
|
local src = r.archetype
|
||||||
|
if src ~= idr_t_archetype then
|
||||||
|
to = archetype_traverse_remove(world, id, src)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
inner_entity_move(child, r, to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.clear(to_remove)
|
||||||
|
archetype_destroy(world, idr_t_archetype)
|
||||||
|
end
|
||||||
|
|
||||||
|
if did_cascade_delete then
|
||||||
|
for archetype_id in archetype_ids do
|
||||||
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
if not idr_t_archetype then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local entities = idr_t_archetype.entities
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
world_delete(world, entities[i])
|
||||||
end
|
end
|
||||||
archetype_destroy(world, idr_t_archetype)
|
archetype_destroy(world, idr_t_archetype)
|
||||||
end
|
end
|
||||||
|
|
||||||
table.clear(to_remove)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3632,11 +3675,14 @@ local function world_new(DEBUG: boolean?)
|
||||||
local r = entity_index_try_get_unsafe(e) :: record
|
local r = entity_index_try_get_unsafe(e) :: record
|
||||||
inner_entity_move(e, r, node)
|
inner_entity_move(e, r, node)
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_r_archetype)
|
archetype_destroy(world, idr_r_archetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local dense = record.dense
|
local dense = record.dense
|
||||||
local i_swap = entity_index.alive_count
|
local i_swap = entity_index.alive_count
|
||||||
entity_index.alive_count = i_swap - 1
|
entity_index.alive_count = i_swap - 1
|
||||||
|
|
@ -3644,8 +3690,8 @@ local function world_new(DEBUG: boolean?)
|
||||||
local e_swap = eindex_dense_array[i_swap]
|
local e_swap = eindex_dense_array[i_swap]
|
||||||
local r_swap = entity_index_try_get_any(e_swap) :: record
|
local r_swap = entity_index_try_get_any(e_swap) :: record
|
||||||
r_swap.dense = dense
|
r_swap.dense = dense
|
||||||
record.archetype = ROOT_ARCHETYPE
|
record.archetype = nil :: any
|
||||||
record.row = 0
|
record.row = nil :: any
|
||||||
record.dense = i_swap
|
record.dense = i_swap
|
||||||
|
|
||||||
eindex_dense_array[dense] = e_swap
|
eindex_dense_array[dense] = e_swap
|
||||||
|
|
@ -3667,8 +3713,8 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
archetype_delete(world, record.archetype, record.row)
|
archetype_delete(world, record.archetype, record.row)
|
||||||
record.archetype = world.ROOT_ARCHETYPE
|
record.archetype = nil :: any
|
||||||
record.row = 0
|
record.row = nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_exists(world: world, entity: i53): boolean
|
local function world_exists(world: world, entity: i53): boolean
|
||||||
|
|
@ -3827,7 +3873,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, EcsRest do
|
for i = 1, EcsRest do
|
||||||
ENTITY_INDEX_NEW_ID(world)
|
entity_index_new_id(entity_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, max_component_id do
|
for i = 1, max_component_id do
|
||||||
|
|
@ -3863,7 +3909,7 @@ local function world_new(DEBUG: boolean?)
|
||||||
world_add(world, EcsOnDeleteTarget, EcsExclusive)
|
world_add(world, EcsOnDeleteTarget, EcsExclusive)
|
||||||
|
|
||||||
for i = EcsRest + 1, ecs_max_tag_id do
|
for i = EcsRest + 1, ecs_max_tag_id do
|
||||||
ENTITY_INDEX_NEW_ID(world)
|
entity_index_new_id(entity_index)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i, bundle in ecs_metadata do
|
for i, bundle in ecs_metadata do
|
||||||
|
|
@ -3881,7 +3927,7 @@ end
|
||||||
|
|
||||||
local function ecs_is_tag(world: world, entity: i53): boolean
|
local function ecs_is_tag(world: world, entity: i53): boolean
|
||||||
if ECS_IS_PAIR(entity) then
|
if ECS_IS_PAIR(entity) then
|
||||||
return ecs_is_tag(world, ecs_pair_first(world, entity)) and ecs_is_tag(world, ecs_pair_second(world, entity))
|
return ecs_is_tag(world, ecs_pair_first(world, entity)) or ecs_is_tag(world, ecs_pair_second(world, entity))
|
||||||
end
|
end
|
||||||
local idr = world.component_index[entity]
|
local idr = world.component_index[entity]
|
||||||
if idr then
|
if idr then
|
||||||
|
|
@ -3950,7 +3996,7 @@ local function entity_index_ensure(entity_index: entityindex, e: i53)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function new(world: world)
|
local function new(world: world)
|
||||||
local e = ENTITY_INDEX_NEW_ID(world)
|
local e = ENTITY_INDEX_NEW_ID(world.entity_index)
|
||||||
return e
|
return e
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3968,7 +4014,7 @@ local function new_low_id(world: world)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if e == 0 or e >= HI_COMPONENT_ID then
|
if e == 0 or e >= HI_COMPONENT_ID then
|
||||||
e = ENTITY_INDEX_NEW_ID(world)
|
e = ENTITY_INDEX_NEW_ID(entity_index)
|
||||||
else
|
else
|
||||||
entity_index_ensure(entity_index, e)
|
entity_index_ensure(entity_index, e)
|
||||||
end
|
end
|
||||||
|
|
@ -3976,7 +4022,7 @@ local function new_low_id(world: world)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function new_w_id(world: world, id: i53)
|
local function new_w_id(world: world, id: i53)
|
||||||
local e = ENTITY_INDEX_NEW_ID(world)
|
local e = ENTITY_INDEX_NEW_ID(world.entity_index)
|
||||||
world.add(world, e, id)
|
world.add(world, e, id)
|
||||||
return e
|
return e
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
|
ecs:range(1000, 20000)
|
||||||
local mcs = mirror.World.new()
|
local mcs = mirror.World.new()
|
||||||
return ecs, mcs
|
return ecs, mcs
|
||||||
end,
|
end,
|
||||||
|
|
@ -18,14 +19,14 @@ return {
|
||||||
Mirror = function(_, ecs, mcs)
|
Mirror = function(_, ecs, mcs)
|
||||||
for i = 1, 100 do
|
for i = 1, 100 do
|
||||||
|
|
||||||
local _e = mcs:entity()
|
mcs:entity()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function(_, ecs, mcs)
|
Jecs = function(_, ecs, mcs)
|
||||||
for i = 1, 100 do
|
for i = 1, 100 do
|
||||||
|
|
||||||
local _e = ecs:entity()
|
ecs:entity()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
272
test/tests.luau
272
test/tests.luau
|
|
@ -24,174 +24,6 @@ type Id<T=unknown> = jecs.Id<T>
|
||||||
local entity_visualiser = require("@modules/entity_visualiser")
|
local entity_visualiser = require("@modules/entity_visualiser")
|
||||||
local dwi = entity_visualiser.stringify
|
local dwi = entity_visualiser.stringify
|
||||||
|
|
||||||
-- FOCUS()
|
|
||||||
TEST("Stale to_remove", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local a = world:component()
|
|
||||||
local b = world:component()
|
|
||||||
local c = world:component()
|
|
||||||
local d = world:component()
|
|
||||||
local marker = world:component()
|
|
||||||
|
|
||||||
local target = world:entity()
|
|
||||||
|
|
||||||
local first = world:entity()
|
|
||||||
world:add(first, jecs.pair(a, target))
|
|
||||||
world:add(first, jecs.pair(b, target))
|
|
||||||
world:add(first, jecs.pair(jecs.ChildOf, target))
|
|
||||||
|
|
||||||
local second = world:entity()
|
|
||||||
world:add(second, jecs.pair(c, target))
|
|
||||||
world:add(second, jecs.pair(d, target))
|
|
||||||
world:add(second, marker)
|
|
||||||
|
|
||||||
print("-------")
|
|
||||||
world:delete(target)
|
|
||||||
print("-------")
|
|
||||||
|
|
||||||
CHECK(world:contains(second))
|
|
||||||
CHECK(world:has(second, marker))
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- FOCUS()
|
|
||||||
-- Exercises idr_t multi-remove path: entity has multiple pairs with same target (no cascade); delete target → both pairs removed, on_remove each called.
|
|
||||||
TEST("Target delete: multi-remove path (removing_count > 1)", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
local rel1 = world:entity()
|
|
||||||
local rel2 = world:entity()
|
|
||||||
local tag = world:component()
|
|
||||||
local target = world:entity()
|
|
||||||
local e = world:entity()
|
|
||||||
world:add(e, jecs.pair(rel1, target))
|
|
||||||
world:add(e, jecs.pair(rel2, target))
|
|
||||||
world:add(e, tag)
|
|
||||||
local removed_ids = {}
|
|
||||||
world:removed(rel1, function(_e, id) table.insert(removed_ids, id) end)
|
|
||||||
world:removed(rel2, function(_e, id) table.insert(removed_ids, id) end)
|
|
||||||
world:delete(target)
|
|
||||||
CHECK(world:contains(e))
|
|
||||||
CHECK(world:has(e, tag))
|
|
||||||
CHECK(not world:has(e, jecs.pair(rel1, target)))
|
|
||||||
CHECK(not world:has(e, jecs.pair(rel2, target)))
|
|
||||||
CHECK(#removed_ids == 2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- FOCUS()
|
|
||||||
TEST("repro 2", function()
|
|
||||||
local sessionDeletedCount = 0
|
|
||||||
local slotDeletedCount = 0
|
|
||||||
|
|
||||||
for i = 1, 100 do
|
|
||||||
local world = jecs.world(true);
|
|
||||||
|
|
||||||
-- randomness
|
|
||||||
for _ = 1, i % 40 do
|
|
||||||
world:entity()
|
|
||||||
end
|
|
||||||
|
|
||||||
local ofMatch = world:component()
|
|
||||||
local ofTeam = world:component()
|
|
||||||
local ofRound = world:component()
|
|
||||||
local ownedBy = world:component()
|
|
||||||
local nextTeam = world:component()
|
|
||||||
local activeRound = world:component()
|
|
||||||
local team = world:component()
|
|
||||||
local session = world:component()
|
|
||||||
local round = world:component()
|
|
||||||
|
|
||||||
world:add(ofTeam, jecs.Exclusive)
|
|
||||||
world:add(ownedBy, jecs.Exclusive)
|
|
||||||
world:add(nextTeam, jecs.Exclusive)
|
|
||||||
|
|
||||||
world:add(activeRound, jecs.Exclusive)
|
|
||||||
|
|
||||||
local slotEntity = world:entity()
|
|
||||||
local matchEntity = world:entity()
|
|
||||||
world:add(matchEntity, jecs.pair(jecs.ChildOf, slotEntity))
|
|
||||||
|
|
||||||
local teams = {}
|
|
||||||
for t = 1, 2 do
|
|
||||||
local teamEntity = world:entity()
|
|
||||||
world:add(teamEntity, team)
|
|
||||||
world:add(teamEntity, jecs.pair(jecs.ChildOf, matchEntity))
|
|
||||||
world:add(teamEntity, jecs.pair(ofMatch, matchEntity))
|
|
||||||
table.insert(teams, teamEntity)
|
|
||||||
end
|
|
||||||
world:add(matchEntity, jecs.pair(nextTeam, teams[1]))
|
|
||||||
|
|
||||||
local roundEntity = world:entity()
|
|
||||||
world:add(roundEntity, round)
|
|
||||||
world:add(roundEntity, jecs.pair(jecs.ChildOf, matchEntity))
|
|
||||||
|
|
||||||
-- doing something as simple as adding this pair causes error rate to change. When this isn't here, sessionDeletedCount drops from 100% to 80%.
|
|
||||||
world:add(matchEntity, jecs.pair(activeRound, roundEntity))
|
|
||||||
|
|
||||||
local sessions = {}
|
|
||||||
for j = 1, #teams do
|
|
||||||
-- random number of players on team
|
|
||||||
for _ = 1, 1 + (i % 5) do
|
|
||||||
local player = world:entity()
|
|
||||||
|
|
||||||
local sessionEntity = world:entity()
|
|
||||||
world:add(sessionEntity, session)
|
|
||||||
world:add(sessionEntity, jecs.pair(ofMatch, matchEntity))
|
|
||||||
world:add(sessionEntity, jecs.pair(ofTeam, teams[j]))
|
|
||||||
|
|
||||||
-- not adding this next pair makes sessionDeletedCount to drop to 0%??
|
|
||||||
world:add(sessionEntity, jecs.pair(ownedBy, player))
|
|
||||||
world:add(sessionEntity, jecs.pair(ofRound, roundEntity))
|
|
||||||
table.insert(sessions, sessionEntity)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
world:delete(matchEntity)
|
|
||||||
|
|
||||||
-- session should stay alive after match deletion
|
|
||||||
for _, entity in sessions do
|
|
||||||
if not world:contains(entity) then
|
|
||||||
sessionDeletedCount += 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not world:contains(slotEntity) then
|
|
||||||
slotDeletedCount += 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(sessionDeletedCount == 0)
|
|
||||||
CHECK(slotDeletedCount == 0)
|
|
||||||
|
|
||||||
if (sessionDeletedCount + slotDeletedCount > 0)then
|
|
||||||
print(`repro 2 incorrect session deletion count: {sessionDeletedCount}`)
|
|
||||||
|
|
||||||
-- this has never been above 0, but it's the issue i'm having
|
|
||||||
print(`repro 2 incorrect slot deletion count: {slotDeletedCount}`)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
TEST("migrating to real records", function()
|
|
||||||
local world = jecs.world(true)
|
|
||||||
local e1 = world:entity()
|
|
||||||
local e2 = world:entity()
|
|
||||||
print(jecs.record(world, e1).row)
|
|
||||||
world:add(e1, jecs.pair(jecs.ChildOf, e2))
|
|
||||||
world:set(e1, jecs.Name, "hello")
|
|
||||||
CHECK(jecs.record(world, e1).row~=0)
|
|
||||||
CHECK(world:get(e1, jecs.Name)=="hello")
|
|
||||||
CHECK(world:has(e1, jecs.pair(jecs.ChildOf, jecs.Wildcard)))
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("e2 is nil", function()
|
|
||||||
local world = jecs.world(true)
|
|
||||||
local e1 = world:entity(1000)
|
|
||||||
local e2 = world:entity()
|
|
||||||
|
|
||||||
CHECK(e1 and world:contains(e1))
|
|
||||||
CHECK(e2 and world:contains(e2))
|
|
||||||
end)
|
|
||||||
-- FOCUS()
|
|
||||||
TEST("reproduce idr_t nil archetype bug", function()
|
TEST("reproduce idr_t nil archetype bug", function()
|
||||||
local world = jecs.world(true)
|
local world = jecs.world(true)
|
||||||
|
|
||||||
|
|
@ -706,6 +538,44 @@ TEST("world:add()", function()
|
||||||
CHECK(world:has(e, pair(A, C)) == true)
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "exclusive relations invoke on_remove hooks that should allow side effects"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
local call_count = 0
|
||||||
|
world:set(A, jecs.OnRemove, function(e, id)
|
||||||
|
call_count += 1
|
||||||
|
if call_count == 1 then
|
||||||
|
world:add(e, C)
|
||||||
|
else
|
||||||
|
world:add(e, D)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, C))
|
||||||
|
|
||||||
|
|
||||||
|
-- We have to ensure that it actually invokes hooks everytime it
|
||||||
|
-- traverses the archetype
|
||||||
|
e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, D))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "idempotent"
|
do CASE "idempotent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local d = dwi(world)
|
local d = dwi(world)
|
||||||
|
|
@ -728,8 +598,8 @@ TEST("world:add()", function()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
-- An entity starts without an archetype or row
|
-- An entity starts without an archetype or row
|
||||||
-- should therefore not need to copy over data
|
-- should therefore not need to copy over data
|
||||||
CHECK(d.tbl(e) == world.ROOT_ARCHETYPE)
|
CHECK(d.tbl(e) == nil)
|
||||||
CHECK(d.row(e) == 0)
|
CHECK(d.row(e) == nil)
|
||||||
|
|
||||||
local archetypes = #world.archetypes
|
local archetypes = #world.archetypes
|
||||||
-- This should create a new archetype
|
-- This should create a new archetype
|
||||||
|
|
@ -1163,6 +1033,24 @@ TEST("world:delete()", function()
|
||||||
-- CHECK(B_OnRemove_called)
|
-- CHECK(B_OnRemove_called)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "idr_t//remove//on_remove//changed_archetype@3123..3126"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
world:set(A, jecs.OnRemove, function(entity, id)
|
||||||
|
world:set(entity, B, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:add(e2, pair(A, e2))
|
||||||
|
world:set(e2, pair(A, e1), true)
|
||||||
|
|
||||||
|
world:delete(e1)
|
||||||
|
|
||||||
|
CHECK(not world:has(e2, pair(A, e1)))
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "pair(OnDelete, Delete)"
|
do CASE "pair(OnDelete, Delete)"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local ct = world:component()
|
local ct = world:component()
|
||||||
|
|
@ -1705,7 +1593,6 @@ TEST("world:added", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- FOCUS()
|
|
||||||
TEST("world:range()", function()
|
TEST("world:range()", function()
|
||||||
|
|
||||||
do CASE "spawn entity under min range"
|
do CASE "spawn entity under min range"
|
||||||
|
|
@ -3013,6 +2900,43 @@ TEST("world:set()", function()
|
||||||
CHECK(world:has(e, pair(A, C)) == true)
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "exclusive relations invoke on_remove hooks that should allow side effects"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
local call_count = 0
|
||||||
|
world:set(A, jecs.OnRemove, function(e, id)
|
||||||
|
call_count += 1
|
||||||
|
if call_count == 1 then
|
||||||
|
world:set(e, C, true)
|
||||||
|
else
|
||||||
|
world:set(e, D, true)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, pair(A, B), true)
|
||||||
|
world:set(e, pair(A, C), true)
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, C))
|
||||||
|
|
||||||
|
|
||||||
|
-- We have to ensure that it actually invokes hooks everytime it
|
||||||
|
-- traverses the archetype
|
||||||
|
e = world:entity()
|
||||||
|
world:set(e, pair(A, B), true)
|
||||||
|
world:set(e, pair(A, C), true)
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
CHECK(world:has(e, D))
|
||||||
|
end
|
||||||
do CASE "archetype move"
|
do CASE "archetype move"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
|
|
@ -3023,8 +2947,8 @@ TEST("world:set()", function()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
-- An entity starts without an archetype or row
|
-- An entity starts without an archetype or row
|
||||||
-- should therefore not need to copy over data
|
-- should therefore not need to copy over data
|
||||||
CHECK(d.tbl(e) == world.ROOT_ARCHETYPE)
|
CHECK(d.tbl(e) == nil)
|
||||||
CHECK(d.row(e) == 0)
|
CHECK(d.row(e) == nil)
|
||||||
|
|
||||||
local archetypes = #world.archetypes
|
local archetypes = #world.archetypes
|
||||||
-- This should create a new archetype since it is the first
|
-- This should create a new archetype since it is the first
|
||||||
|
|
@ -3360,7 +3284,7 @@ TEST("Hooks", function()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
|
|
||||||
world:set(A, jecs.OnRemove, function(entity: jecs.Entity)
|
world:set(A, jecs.OnRemove, function(entity)
|
||||||
world:set(entity, B, true)
|
world:set(entity, B, true)
|
||||||
CHECK(world:get(entity, A))
|
CHECK(world:get(entity, A))
|
||||||
CHECK(world:get(entity, B))
|
CHECK(world:get(entity, B))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.11.0"
|
version = "0.10.2"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue