mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-05 03:39:17 +00:00
Compare commits
1 commit
8194a03304
...
825a8248a7
Author | SHA1 | Date | |
---|---|---|---|
|
825a8248a7 |
5 changed files with 15 additions and 251 deletions
|
@ -2,12 +2,9 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
## 0.7.0
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `jecs.component_record` for retrieving the component_record of a component.
|
- `jecs.component_record` for retrieving the component_record of a component.
|
||||||
- `Column<T>` and `ColumnsMap<T>` types for typescript.
|
- `Column<T>` and `ColumnsMap<T>` types for typescript.
|
||||||
- `bulk_insert` and `bulk_remove` respectively for moving an entity to an archetype without intermediate steps.
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- The fields `archetype.records[id]` and `archetype.counts[id` have been removed from the archetype struct and been moved to the component record `component_index[id].records[archetype.id]` and `component_index[id].counts[archetype.id]` respectively.
|
- The fields `archetype.records[id]` and `archetype.counts[id` have been removed from the archetype struct and been moved to the component record `component_index[id].records[archetype.id]` and `component_index[id].counts[archetype.id]` respectively.
|
||||||
|
|
162
jecs.luau
162
jecs.luau
|
@ -48,7 +48,7 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
__iter: Iter<T...>,
|
__iter: Iter<T...>
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -126,28 +126,8 @@ export type World = {
|
||||||
& (<A, B, C, D>(World, Id<A>, Id<B>, Id<C>, Id<D>) -> Query<A, B, C, D>)
|
& (<A, B, C, D>(World, Id<A>, Id<B>, Id<C>, Id<D>) -> Query<A, B, C, D>)
|
||||||
& (<A, B, C, D, E>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>) -> Query<A, B, C, D, E>)
|
& (<A, B, C, D, E>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>) -> Query<A, B, C, D, E>)
|
||||||
& (<A, B, C, D, E, F>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>) -> Query<A, B, C, D, E, F>)
|
& (<A, B, C, D, E, F>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>) -> Query<A, B, C, D, E, F>)
|
||||||
& (<A, B, C, D, E, F, G>(
|
& (<A, B, C, D, E, F, G>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>, Id<G>) -> Query<A, B, C, D, E, F, G>)
|
||||||
World,
|
& (<A, B, C, D, E, F, G, H>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>, Id<G>, Id<H>, ...Id<any>) -> Query<A, B, C, D, E, F, G, H>)
|
||||||
Id<A>,
|
|
||||||
Id<B>,
|
|
||||||
Id<C>,
|
|
||||||
Id<D>,
|
|
||||||
Id<E>,
|
|
||||||
Id<F>,
|
|
||||||
Id<G>
|
|
||||||
) -> Query<A, B, C, D, E, F, G>)
|
|
||||||
& (<A, B, C, D, E, F, G, H>(
|
|
||||||
World,
|
|
||||||
Id<A>,
|
|
||||||
Id<B>,
|
|
||||||
Id<C>,
|
|
||||||
Id<D>,
|
|
||||||
Id<E>,
|
|
||||||
Id<F>,
|
|
||||||
Id<G>,
|
|
||||||
Id<H>,
|
|
||||||
...Id<any>
|
|
||||||
) -> Query<A, B, C, D, E, F, G, H>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Record = {
|
export type Record = {
|
||||||
|
@ -175,7 +155,7 @@ export type EntityIndex = {
|
||||||
alive_count: number,
|
alive_count: number,
|
||||||
max_id: number,
|
max_id: number,
|
||||||
range_begin: number?,
|
range_begin: number?,
|
||||||
range_end: number?,
|
range_end: number?
|
||||||
}
|
}
|
||||||
|
|
||||||
-- stylua: ignore start
|
-- stylua: ignore start
|
||||||
|
@ -842,6 +822,7 @@ end
|
||||||
local function find_insert(id_types: { i53 }, toAdd: i53): number
|
local function find_insert(id_types: { i53 }, toAdd: i53): number
|
||||||
for i, id in id_types do
|
for i, id in id_types do
|
||||||
if id == toAdd then
|
if id == toAdd then
|
||||||
|
error("Duplicate component id")
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
if id > toAdd then
|
if id > toAdd then
|
||||||
|
@ -1962,134 +1943,6 @@ local function world_children<a>(world: World, parent: Id<a>)
|
||||||
return world_each(world, ECS_PAIR(EcsChildOf, parent::number))
|
return world_each(world, ECS_PAIR(EcsChildOf, parent::number))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, values: { any })
|
|
||||||
local entity_index = world.entity_index
|
|
||||||
local r = entity_index_try_get(entity_index, entity)
|
|
||||||
if not r then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local from = r.archetype
|
|
||||||
local component_index = world.component_index
|
|
||||||
if not from then
|
|
||||||
local dst_types = ids
|
|
||||||
local to = archetype_ensure(world, dst_types)
|
|
||||||
new_entity(entity, r, to)
|
|
||||||
local row = r.row
|
|
||||||
local columns_map = to.columns_map
|
|
||||||
for i, id in ids do
|
|
||||||
local value = values[i]
|
|
||||||
local cdr = component_index[id]
|
|
||||||
|
|
||||||
local on_add = cdr.hooks.on_add
|
|
||||||
if value then
|
|
||||||
columns_map[id][row] = value
|
|
||||||
if on_add then
|
|
||||||
on_add(entity, id, value :: any)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if on_add then
|
|
||||||
on_add(entity, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local dst_types = table.clone(from.types)
|
|
||||||
|
|
||||||
local emplaced: { [number]: boolean } = {}
|
|
||||||
|
|
||||||
for i, id in ids do
|
|
||||||
local at = find_insert(dst_types :: { number }, id :: number)
|
|
||||||
if at == -1 then
|
|
||||||
emplaced[i] = true
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
emplaced[i] = false
|
|
||||||
|
|
||||||
table.insert(dst_types, at, id)
|
|
||||||
end
|
|
||||||
|
|
||||||
local to = archetype_ensure(world, dst_types)
|
|
||||||
local columns_map = to.columns_map
|
|
||||||
|
|
||||||
if from ~= to then
|
|
||||||
entity_move(entity_index, entity, r, to)
|
|
||||||
end
|
|
||||||
local row = r.row
|
|
||||||
|
|
||||||
for i, set in emplaced do
|
|
||||||
local id = ids[i]
|
|
||||||
local idr = component_index[id]
|
|
||||||
|
|
||||||
local value = values[i] :: any
|
|
||||||
|
|
||||||
local on_add = idr.hooks.on_add
|
|
||||||
local on_change = idr.hooks.on_change
|
|
||||||
|
|
||||||
if value then
|
|
||||||
columns_map[id][row] = value
|
|
||||||
local hook = if set then on_change else on_add
|
|
||||||
if hook then
|
|
||||||
hook(entity, id, value :: any)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if on_add then
|
|
||||||
on_add(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity })
|
|
||||||
local entity_index = world.entity_index
|
|
||||||
local r = entity_index_try_get(entity_index, entity)
|
|
||||||
if not r then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local from = r.archetype
|
|
||||||
local component_index = world.component_index
|
|
||||||
if not from then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local remove: { [Entity]: boolean } = {}
|
|
||||||
|
|
||||||
local columns_map = from.columns_map
|
|
||||||
|
|
||||||
for i, id in ids do
|
|
||||||
if not columns_map[id] then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
remove[id] = true
|
|
||||||
local idr = component_index[id]
|
|
||||||
|
|
||||||
local on_remove = idr.hooks.on_remove
|
|
||||||
if on_remove then
|
|
||||||
on_remove(entity, id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local to = r.archetype
|
|
||||||
if from ~= to then
|
|
||||||
from = to
|
|
||||||
end
|
|
||||||
|
|
||||||
local dst_types = table.clone(from.types) :: { Entity }
|
|
||||||
|
|
||||||
for id in remove do
|
|
||||||
local at = table.find(dst_types, id)
|
|
||||||
table.remove(dst_types, at)
|
|
||||||
end
|
|
||||||
|
|
||||||
to = archetype_ensure(world, dst_types)
|
|
||||||
if from ~= to then
|
|
||||||
entity_move(entity_index, entity, r, to)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_new()
|
local function world_new()
|
||||||
local eindex_dense_array = {} :: { Entity }
|
local eindex_dense_array = {} :: { Entity }
|
||||||
local eindex_sparse_array = {} :: { Record }
|
local eindex_sparse_array = {} :: { Record }
|
||||||
|
@ -2340,8 +2193,11 @@ local function world_new()
|
||||||
-- 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
|
||||||
entity_move(entity_index, entity, record, to)
|
entity_move(entity_index, entity, record, to)
|
||||||
else
|
else
|
||||||
|
if #to.types > 0 then
|
||||||
|
-- When there is no previous archetype it should create the archetype
|
||||||
new_entity(entity, record, to)
|
new_entity(entity, record, to)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
local column = to.columns_map[id]
|
local column = to.columns_map[id]
|
||||||
column[record.row] = data
|
column[record.row] = data
|
||||||
|
|
||||||
|
@ -2886,8 +2742,6 @@ return {
|
||||||
create_edge_for_remove = create_edge_for_remove,
|
create_edge_for_remove = create_edge_for_remove,
|
||||||
archetype_traverse_add = archetype_traverse_add,
|
archetype_traverse_add = archetype_traverse_add,
|
||||||
archetype_traverse_remove = archetype_traverse_remove,
|
archetype_traverse_remove = archetype_traverse_remove,
|
||||||
bulk_insert = ecs_bulk_insert,
|
|
||||||
bulk_remove = ecs_bulk_remove,
|
|
||||||
|
|
||||||
entity_move = entity_move,
|
entity_move = entity_move,
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.7.0",
|
"version": "0.6.1",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -25,93 +25,6 @@ local entity_visualiser = require("@tools/entity_visualiser")
|
||||||
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
local lifetime_tracker_add = require("@tools/lifetime_tracker")
|
||||||
local dwi = entity_visualiser.stringify
|
local dwi = entity_visualiser.stringify
|
||||||
|
|
||||||
TEST("bulk", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
local C = world:component()
|
|
||||||
|
|
||||||
local D = world:component()
|
|
||||||
local E = world:entity()
|
|
||||||
local F = world:component()
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
local r = jecs.entity_index_try_get(world.entity_index, e)
|
|
||||||
jecs.bulk_insert(world, e, { A, B, C }, { 1, 2, 3 })
|
|
||||||
CHECK(world:get(e, A) == 1)
|
|
||||||
CHECK(world:get(e, B) == 2)
|
|
||||||
CHECK(world:get(e, C) == 3)
|
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, { D, E, F }, { 4, nil, 5 })
|
|
||||||
CHECK(world:get(e, A) == 1)
|
|
||||||
CHECK(world:get(e, B) == 2)
|
|
||||||
CHECK(world:get(e, C) == 3)
|
|
||||||
|
|
||||||
CHECK(world:get(e, D) == 4)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 5)
|
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, { A, D, E, F, C }, { 10, 40, nil, 50, 30 })
|
|
||||||
|
|
||||||
CHECK(world:get(e, A) == 10)
|
|
||||||
CHECK(world:get(e, B) == 2)
|
|
||||||
CHECK(world:get(e, C) == 30)
|
|
||||||
CHECK(world:get(e, D) == 40)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 50)
|
|
||||||
|
|
||||||
local G = world:component()
|
|
||||||
world:set(e, G, 100)
|
|
||||||
|
|
||||||
CHECK(world:get(e, A) == 10)
|
|
||||||
CHECK(world:get(e, B) == 2)
|
|
||||||
CHECK(world:get(e, C) == 30)
|
|
||||||
CHECK(world:get(e, D) == 40)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 50)
|
|
||||||
CHECK(world:get(e, G) == 100)
|
|
||||||
|
|
||||||
world:remove(e, B)
|
|
||||||
|
|
||||||
CHECK(world:get(e, A) == 10)
|
|
||||||
CHECK(world:has(e, B) == false)
|
|
||||||
CHECK(world:get(e, C) == 30)
|
|
||||||
CHECK(world:get(e, D) == 40)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 50)
|
|
||||||
CHECK(world:get(e, G) == 100)
|
|
||||||
|
|
||||||
jecs.bulk_remove(world, e, { A, B, C, D })
|
|
||||||
|
|
||||||
CHECK(world:has(e, A) == false)
|
|
||||||
CHECK(world:has(e, B) == false)
|
|
||||||
CHECK(world:has(e, C) == false)
|
|
||||||
CHECK(world:has(e, D) == false)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 50)
|
|
||||||
CHECK(world:get(e, G) == 100)
|
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, { D, G }, { 999, 1 })
|
|
||||||
|
|
||||||
CHECK(world:has(e, A) == false)
|
|
||||||
CHECK(world:has(e, B) == false)
|
|
||||||
CHECK(world:has(e, C) == false)
|
|
||||||
CHECK(world:get(e, D) == 999)
|
|
||||||
CHECK(world:get(e, E) == nil and world:has(e, E))
|
|
||||||
CHECK(world:get(e, F) == 50)
|
|
||||||
CHECK(world:get(e, G) == 1)
|
|
||||||
|
|
||||||
jecs.bulk_remove(world, e, { A, B, C, D, E, F, G })
|
|
||||||
|
|
||||||
CHECK(world:has(e, A) == false)
|
|
||||||
CHECK(world:has(e, B) == false)
|
|
||||||
CHECK(world:has(e, C) == false)
|
|
||||||
CHECK(world:has(e, D) == false)
|
|
||||||
CHECK(world:has(e, E) == false)
|
|
||||||
CHECK(world:has(e, F) == false)
|
|
||||||
CHECK(world:has(e, G) == false)
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("repro", function()
|
TEST("repro", function()
|
||||||
local Model = jecs.component()
|
local Model = jecs.component()
|
||||||
local Relation = jecs.component()
|
local Relation = jecs.component()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.7.0"
|
version = "0.6.1"
|
||||||
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