mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Add nth parameter to world:target (#116)
* Add nth parameter to world:target * Put archetype record creation into function * Fix docs and comments
This commit is contained in:
parent
8e0a9409f5
commit
244d799cec
4 changed files with 217 additions and 97 deletions
4
demo/src/ReplicatedStorage/ecs_init.luau
Normal file
4
demo/src/ReplicatedStorage/ecs_init.luau
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
_G.JECS_DEBUG = true
|
||||||
|
_G.JECS_HI_COMPONENT_ID = 32
|
||||||
|
require(game:GetService("ReplicatedStorage").ecs)
|
||||||
|
return
|
|
@ -147,15 +147,17 @@ Queries are uncached by default, this is generally very cheap unless you have hi
|
||||||
```luau
|
```luau
|
||||||
function World:target(
|
function World:target(
|
||||||
entity: Entity, -- The entity
|
entity: Entity, -- The entity
|
||||||
relation: Entity -- The relationship between the entity and the target
|
relation: Entity, -- The relationship between the entity and the target
|
||||||
): Entity? -- Returns the parent of the child
|
nth: number, -- The index
|
||||||
|
): Entity? -- The target for the relationship at the specified index.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Get the target of a relationship.
|
Get the target of a relationship.
|
||||||
|
|
||||||
This will return a target (second element of a pair) of the entity for the specified 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 there is no pair with specified relationship, it will return nil.
|
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.
|
||||||
|
|
||||||
## parent()
|
## parent()
|
||||||
```luau
|
```luau
|
||||||
|
@ -169,5 +171,5 @@ Get parent (target of ChildOf relationship) for entity. If there is no ChildOf r
|
||||||
This operation is the same as calling:
|
This operation is the same as calling:
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
world:target(entity, jecs.ChildOf)
|
world:target(entity, jecs.ChildOf, 0)
|
||||||
```
|
```
|
||||||
|
|
110
src/init.luau
110
src/init.luau
|
@ -382,11 +382,8 @@ local function world_has(world: World, entity: number, ...: i53): boolean
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO:
|
|
||||||
-- should have an additional `nth` parameter which selects the nth target
|
|
||||||
-- this is important when an entity can have multiple relationships with the same target
|
|
||||||
local function world_target(world: World, entity: i53,
|
local function world_target(world: World, entity: i53,
|
||||||
relation: i24--[[, nth: number]]): i24?
|
relation: i24, index: number): i24?
|
||||||
|
|
||||||
local record = world.entityIndex.sparse[entity]
|
local record = world.entityIndex.sparse[entity]
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
|
@ -404,7 +401,24 @@ local function world_target(world: World, entity: i53,
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
return ecs_pair_second(world, archetype.types[tr.column])
|
local count = tr.count
|
||||||
|
if index >= count then
|
||||||
|
index = index + count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local nth = archetype.types[index + tr.column]
|
||||||
|
|
||||||
|
if not nth then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return ecs_pair_second(world, nth)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ECS_ID_IS_WILDCARD(e: i53): boolean
|
||||||
|
local first = ECS_ENTITY_T_HI(e)
|
||||||
|
local second = ECS_ENTITY_T_LO(e)
|
||||||
|
return first == EcsWildcard or second == EcsWildcard
|
||||||
end
|
end
|
||||||
|
|
||||||
local function id_record_ensure(world: World, id: number): ArchetypeMap
|
local function id_record_ensure(world: World, id: number): ArchetypeMap
|
||||||
|
@ -415,8 +429,8 @@ local function id_record_ensure(world: World, id: number): ArchetypeMap
|
||||||
local flags = ECS_ID_MASK
|
local flags = ECS_ID_MASK
|
||||||
local relation = ECS_ENTITY_T_HI(id)
|
local relation = ECS_ENTITY_T_HI(id)
|
||||||
|
|
||||||
local cleanup_policy = world_target(world, relation, EcsOnDelete)
|
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
|
||||||
local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget)
|
local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0)
|
||||||
|
|
||||||
local has_delete = false
|
local has_delete = false
|
||||||
|
|
||||||
|
@ -426,14 +440,14 @@ local function id_record_ensure(world: World, id: number): ArchetypeMap
|
||||||
|
|
||||||
local on_add, on_set, on_remove = world_get(world, relation, EcsOnAdd, EcsOnSet, EcsOnRemove)
|
local on_add, on_set, on_remove = world_get(world, relation, EcsOnAdd, EcsOnSet, EcsOnRemove)
|
||||||
|
|
||||||
local has_tag = world_has_one_inline(world, id, EcsTag)
|
local is_tag = not world_has_one_inline(world, relation, EcsComponent)
|
||||||
|
|
||||||
flags = bit32.bor(flags,
|
flags = bit32.bor(flags,
|
||||||
if on_add then ECS_ID_HAS_ON_ADD else 0,
|
if on_add then ECS_ID_HAS_ON_ADD else 0,
|
||||||
if on_remove then ECS_ID_HAS_ON_REMOVE else 0,
|
if on_remove then ECS_ID_HAS_ON_REMOVE else 0,
|
||||||
if on_set then ECS_ID_HAS_ON_SET else 0,
|
if on_set then ECS_ID_HAS_ON_SET else 0,
|
||||||
if has_delete then ECS_ID_DELETE else 0,
|
if has_delete then ECS_ID_DELETE else 0,
|
||||||
if has_tag then ECS_ID_IS_TAG else 0
|
if is_tag then ECS_ID_IS_TAG else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
idr = {
|
idr = {
|
||||||
|
@ -447,47 +461,43 @@ local function id_record_ensure(world: World, id: number): ArchetypeMap
|
||||||
return idr
|
return idr
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ECS_ID_IS_WILDCARD(e: i53): boolean
|
local function archetype_append_to_records(idr: ArchetypeMap, archetype_id, records, id, index)
|
||||||
assert(ECS_IS_PAIR(e))
|
local tr = idr.cache[archetype_id]
|
||||||
local first = ECS_ENTITY_T_HI(e)
|
if not tr then
|
||||||
local second = ECS_ENTITY_T_LO(e)
|
tr = { column = index, count = 1}
|
||||||
return first == EcsWildcard or second == EcsWildcard
|
idr.cache[archetype_id] = tr
|
||||||
|
idr.size += 1
|
||||||
|
records[id] = tr
|
||||||
|
else
|
||||||
|
tr.count += 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetype_create(world: World, types: { i24 }, prev: Archetype?): Archetype
|
local function archetype_create(world: World, types: { i24 }, prev: Archetype?): Archetype
|
||||||
local ty = hash(types)
|
local ty = hash(types)
|
||||||
|
|
||||||
local id = (world.nextArchetypeId :: number) + 1
|
local archetype_id = (world.nextArchetypeId :: number) + 1
|
||||||
world.nextArchetypeId = id
|
world.nextArchetypeId = archetype_id
|
||||||
|
|
||||||
local length = #types
|
local length = #types
|
||||||
local columns = (table.create(length) :: any) :: { Column }
|
local columns = (table.create(length) :: any) :: { Column }
|
||||||
|
|
||||||
local records: { ArchetypeRecord } = {}
|
local records: { ArchetypeRecord } = {}
|
||||||
for i, componentId in types do
|
for i, componentId in types do
|
||||||
local tr = { column = i, count = 1 }
|
|
||||||
local idr = id_record_ensure(world, componentId)
|
local idr = id_record_ensure(world, componentId)
|
||||||
idr.cache[id] = tr
|
archetype_append_to_records(idr, archetype_id, records, componentId, i)
|
||||||
idr.size += 1
|
|
||||||
records[componentId] = tr
|
|
||||||
if ECS_IS_PAIR(componentId) then
|
if ECS_IS_PAIR(componentId) then
|
||||||
local relation = ecs_pair_first(world, componentId)
|
local relation = ecs_pair_first(world, componentId)
|
||||||
local object = ecs_pair_second(world, componentId)
|
local object = ecs_pair_second(world, componentId)
|
||||||
|
|
||||||
local r = ECS_PAIR(relation, EcsWildcard)
|
local r = ECS_PAIR(relation, EcsWildcard)
|
||||||
local idr_r = id_record_ensure(world, r)
|
local idr_r = id_record_ensure(world, r)
|
||||||
|
archetype_append_to_records(idr_r, archetype_id, records, r, i)
|
||||||
|
|
||||||
local o = ECS_PAIR(EcsWildcard, object)
|
local t = ECS_PAIR(EcsWildcard, object)
|
||||||
local idr_o = id_record_ensure(world, o)
|
local idr_t = id_record_ensure(world, t)
|
||||||
|
archetype_append_to_records(idr_t, archetype_id, records, t, i)
|
||||||
records[r] = tr
|
|
||||||
records[o] = tr
|
|
||||||
|
|
||||||
idr_r.cache[id] = tr
|
|
||||||
idr_o.cache[id] = tr
|
|
||||||
|
|
||||||
idr_r.size += 1
|
|
||||||
idr_o.size += 1
|
|
||||||
end
|
end
|
||||||
if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then
|
if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then
|
||||||
columns[i] = {}
|
columns[i] = {}
|
||||||
|
@ -500,14 +510,14 @@ local function archetype_create(world: World, types: { i24 }, prev: Archetype?):
|
||||||
columns = columns,
|
columns = columns,
|
||||||
edges = {},
|
edges = {},
|
||||||
entities = {},
|
entities = {},
|
||||||
id = id,
|
id = archetype_id,
|
||||||
records = records,
|
records = records,
|
||||||
type = ty,
|
type = ty,
|
||||||
types = types,
|
types = types,
|
||||||
}
|
}
|
||||||
|
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[id] = archetype
|
world.archetypes[archetype_id] = archetype
|
||||||
|
|
||||||
return archetype
|
return archetype
|
||||||
end
|
end
|
||||||
|
@ -519,7 +529,7 @@ local function world_entity(world: World): i53
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_parent(world: World, entity: i53)
|
local function world_parent(world: World, entity: i53)
|
||||||
return world_target(world, entity, EcsChildOf)
|
return world_target(world, entity, EcsChildOf, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetype_ensure(world: World, types, prev): Archetype
|
local function archetype_ensure(world: World, types, prev): Archetype
|
||||||
|
@ -747,7 +757,9 @@ do
|
||||||
column_count: number, types: { i53 }, entity: i53)
|
column_count: number, types: { i53 }, entity: i53)
|
||||||
|
|
||||||
for i, column in columns do
|
for i, column in columns do
|
||||||
column[column_count] = nil
|
if column ~= NULL_ARRAY then
|
||||||
|
column[column_count] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -755,8 +767,10 @@ do
|
||||||
column_count: number, row, types, entity)
|
column_count: number, row, types, entity)
|
||||||
|
|
||||||
for i, column in columns do
|
for i, column in columns do
|
||||||
column[row] = column[column_count]
|
if column ~= NULL_ARRAY then
|
||||||
column[column_count] = nil
|
column[row] = column[column_count]
|
||||||
|
column[column_count] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local function archetype_delete(world: World,
|
local function archetype_delete(world: World,
|
||||||
|
@ -1449,9 +1463,7 @@ if _G.__JECS_DEBUG == true then
|
||||||
return world_query(world, ...)
|
return world_query(world, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
World.set = function(world: World, entity: i53,
|
World.set = function(world: World, entity: i53, id: i53, value: any): ()
|
||||||
id: i53, value: any): ()
|
|
||||||
|
|
||||||
local idr = world.componentIndex[id]
|
local idr = world.componentIndex[id]
|
||||||
local flags = idr.flags
|
local flags = idr.flags
|
||||||
local id_is_tag = bit32.band(flags, ECS_ID_IS_TAG) ~= 0
|
local id_is_tag = bit32.band(flags, ECS_ID_IS_TAG) ~= 0
|
||||||
|
@ -1475,7 +1487,7 @@ if _G.__JECS_DEBUG == true then
|
||||||
world_add(world, entity, id)
|
world_add(world, entity, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
World.get = function(world: World, entity: i53, id: i53, ...: i53)
|
World.get = function(world: World, entity: i53, ...)
|
||||||
local length = select("#", ...)
|
local length = select("#", ...)
|
||||||
ASSERT(length > 4, "world:get does not support more than 4 components")
|
ASSERT(length > 4, "world:get does not support more than 4 components")
|
||||||
for i = 1, length do
|
for i = 1, length do
|
||||||
|
@ -1484,15 +1496,12 @@ if _G.__JECS_DEBUG == true then
|
||||||
local flags = idr.flags
|
local flags = idr.flags
|
||||||
local id_is_tag = bit32.band(flags, ECS_ID_IS_TAG) ~= 0
|
local id_is_tag = bit32.band(flags, ECS_ID_IS_TAG) ~= 0
|
||||||
if id_is_tag then
|
if id_is_tag then
|
||||||
|
local name = world_get_one_inline(world, id, EcsName) or `${id}`
|
||||||
throw(`cannot get component ({name}) value because it is a tag. If this was intentional, use "world:has(entity, {name})"`)
|
throw(`cannot get component ({name}) value because it is a tag. If this was intentional, use "world:has(entity, {name})"`)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if value ~= nil then
|
|
||||||
local name = world_get_one_inline(world, id, EcsName) or `${id}`
|
|
||||||
throw(`You provided a value when none was expected. Did you mean to use "world:add(entity, {name})"`)
|
|
||||||
end
|
|
||||||
|
|
||||||
return world_get(world, entity, id)
|
return world_get(world, entity, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1518,6 +1527,11 @@ function World.new()
|
||||||
entity_index_new_id(self.entityIndex, i)
|
entity_index_new_id(self.entityIndex, i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
world_add(self, EcsOnSet, EcsComponent)
|
||||||
|
world_add(self, EcsOnAdd, EcsComponent)
|
||||||
|
world_add(self, EcsOnRemove, EcsComponent)
|
||||||
|
world_add(self, EcsRest, EcsComponent)
|
||||||
|
world_add(self, EcsName, EcsComponent)
|
||||||
world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -1562,8 +1576,8 @@ export type World = {
|
||||||
--- These should be used for static components for fast access.
|
--- These should be used for static components for fast access.
|
||||||
component: <T>(self: World) -> Entity<T>,
|
component: <T>(self: World) -> Entity<T>,
|
||||||
--- Gets the target of an relationship. For example, when a user calls
|
--- Gets the target of an relationship. For example, when a user calls
|
||||||
--- `world:target(id, ChildOf(parent))`, you will obtain the parent entity.
|
--- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity.
|
||||||
target: (self: World, id: Entity, relation: Entity) -> Entity?,
|
target: (self: World, id: Entity, relation: Entity, nth: number) -> Entity?,
|
||||||
--- Deletes an entity and all it's related components and relationships.
|
--- Deletes an entity and all it's related components and relationships.
|
||||||
delete: (self: World, id: Entity) -> (),
|
delete: (self: World, id: Entity) -> (),
|
||||||
|
|
||||||
|
|
188
test/tests.luau
188
test/tests.luau
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local testkit = require("@testkit")
|
local testkit = require("@testkit")
|
||||||
local BENCH, START = testkit.benchmark()
|
local BENCH, START = testkit.benchmark()
|
||||||
|
@ -9,6 +10,7 @@ local pair = jecs.pair
|
||||||
local getAlive = jecs.entity_index_get_alive
|
local getAlive = jecs.entity_index_get_alive
|
||||||
local ecs_pair_first = jecs.pair_first
|
local ecs_pair_first = jecs.pair_first
|
||||||
local ecs_pair_second = jecs.pair_second
|
local ecs_pair_second = jecs.pair_second
|
||||||
|
local world_new = jecs.World.new
|
||||||
|
|
||||||
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
||||||
local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
|
local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
|
||||||
|
@ -138,24 +140,50 @@ TEST("world:set()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "arbitrary order"
|
do CASE "arbitrary order"
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:entity()
|
||||||
local Poison = world:component()
|
local Poison = world:component()
|
||||||
|
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
world:set(id, Poison, 5)
|
world:set(id, Poison, 5)
|
||||||
world:set(id, Health, 50)
|
world:set(id, Health, 50)
|
||||||
|
|
||||||
CHECK(world:get(id, Poison) == 5)
|
CHECK(world:get(id, Poison) == 5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "pairs"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local C1 = world:component()
|
||||||
|
local C2 = world:component()
|
||||||
|
local T1 = world:entity()
|
||||||
|
local T2 = world:entity()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
|
||||||
|
world:set(e, pair(C1, C2), true)
|
||||||
|
world:set(e, pair(C1, T1), true)
|
||||||
|
world:set(e, pair(T1, C1), true)
|
||||||
|
world:set(e, pair(T1, T2), true)
|
||||||
|
|
||||||
|
CHECK(world:get(e, pair(C1, C2)))
|
||||||
|
CHECK(world:get(e, pair(C1, T1)))
|
||||||
|
CHECK(not world:get(e, pair(T1, C1)))
|
||||||
|
CHECK(not world:get(e, pair(T1, T2)))
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
world:set(e2, pair(jecs.ChildOf, e), true)
|
||||||
|
CHECK(not world:get(e2, pair(jecs.ChildOf, e)))
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:remove()", function()
|
TEST("world:remove()", function()
|
||||||
do CASE "should allow remove a component that doesn't exist on entity"
|
do CASE "should allow remove a component that doesn't exist on entity"
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:component()
|
||||||
local Poison = world:component()
|
local Poison = world:component()
|
||||||
|
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
|
@ -213,14 +241,35 @@ end)
|
||||||
TEST("world:query()", function()
|
TEST("world:query()", function()
|
||||||
do CASE "tag"
|
do CASE "tag"
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:entity()
|
||||||
world:add(A, jecs.Tag)
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, "test")
|
world:set(e, A, "test")
|
||||||
for id, a in world:query(A) do
|
for id, a in world:query(A) do
|
||||||
CHECK(a == nil)
|
CHECK(a == nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
do CASE "pairs"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local C1 = world:component()
|
||||||
|
local C2 = world:component()
|
||||||
|
local T1 = world:entity()
|
||||||
|
local T2 = world:entity()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
|
||||||
|
world:set(e, pair(C1, C2), true)
|
||||||
|
world:set(e, pair(C1, T1), true)
|
||||||
|
world:set(e, pair(T1, C1), true)
|
||||||
|
world:set(e, pair(T1, T2), true)
|
||||||
|
|
||||||
|
for id, a, b, c, d in world:query(pair(C1, C2), pair(C1, T1), pair(T1, C1), pair(T1, T2)) do
|
||||||
|
CHECK(a == true)
|
||||||
|
CHECK(b == true)
|
||||||
|
CHECK(c == nil)
|
||||||
|
CHECK(d == nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
do CASE "query single component"
|
do CASE "query single component"
|
||||||
do
|
do
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
@ -405,8 +454,8 @@ TEST("world:query()", function()
|
||||||
|
|
||||||
do CASE("should allow querying for relations")
|
do CASE("should allow querying for relations")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:component()
|
||||||
local Apples = world:entity()
|
local Apples = world:component()
|
||||||
local bob = world:entity()
|
local bob = world:entity()
|
||||||
|
|
||||||
world:set(bob, pair(Eats, Apples), true)
|
world:set(bob, pair(Eats, Apples), true)
|
||||||
|
@ -418,7 +467,7 @@ TEST("world:query()", function()
|
||||||
|
|
||||||
do CASE("should allow wildcards in queries")
|
do CASE("should allow wildcards in queries")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:component()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
local bob = world:entity()
|
local bob = world:entity()
|
||||||
|
|
||||||
|
@ -437,7 +486,7 @@ TEST("world:query()", function()
|
||||||
|
|
||||||
do CASE("should match against multiple pairs")
|
do CASE("should match against multiple pairs")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Eats = world:entity()
|
local Eats = world:component()
|
||||||
local Apples = world:entity()
|
local Apples = world:entity()
|
||||||
local Oranges = world:entity()
|
local Oranges = world:entity()
|
||||||
local bob = world:entity()
|
local bob = world:entity()
|
||||||
|
@ -697,32 +746,32 @@ TEST("world:clear()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:has()", function()
|
TEST("world:has()", function()
|
||||||
do CASE "should find Tag on entity"
|
do CASE "should find Tag on entity"
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Tag = world:component()
|
local Tag = world:entity()
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:add(e, Tag)
|
world:add(e, Tag)
|
||||||
|
|
||||||
CHECK(world:has(e, Tag))
|
CHECK(world:has(e, Tag))
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "should return false when missing one tag"
|
do CASE "should return false when missing one tag"
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local A = world:component()
|
local A = world:entity()
|
||||||
local B = world:component()
|
local B = world:entity()
|
||||||
local C = world:component()
|
local C = world:entity()
|
||||||
local D = world:component()
|
local D = world:entity()
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:add(e, A)
|
world:add(e, A)
|
||||||
world:add(e, C)
|
world:add(e, C)
|
||||||
world:add(e, D)
|
world:add(e, D)
|
||||||
|
|
||||||
CHECK(world:has(e, A, B, C, D) == false)
|
CHECK(world:has(e, A, B, C, D) == false)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:component()", function()
|
TEST("world:component()", function()
|
||||||
|
@ -738,10 +787,8 @@ TEST("world:component()", function()
|
||||||
do CASE "tag"
|
do CASE "tag"
|
||||||
local world = jecs.World.new() :: World
|
local world = jecs.World.new() :: World
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:entity()
|
||||||
local C = world:component()
|
local C = world:entity()
|
||||||
world:add(B, jecs.Tag)
|
|
||||||
world:add(C, jecs.Tag)
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:set(e, A, "test")
|
world:set(e, A, "test")
|
||||||
world:add(e, B, "test")
|
world:add(e, B, "test")
|
||||||
|
@ -758,7 +805,7 @@ TEST("world:delete", function()
|
||||||
do CASE("should allow deleting components")
|
do CASE("should allow deleting components")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local Health = world:entity()
|
local Health = world:component()
|
||||||
local Poison = world:component()
|
local Poison = world:component()
|
||||||
|
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
|
@ -900,13 +947,66 @@ TEST("world:delete", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
TEST("world:target", function()
|
||||||
|
do CASE "nth index"
|
||||||
|
local world = world_new()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
local e = world:entity()
|
||||||
|
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
world:add(e, pair(B, C))
|
||||||
|
world:add(e, pair(B, D))
|
||||||
|
world:add(e, pair(C, D))
|
||||||
|
|
||||||
|
CHECK(pair(A, B) < pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:target(e, A, 0) == B)
|
||||||
|
CHECK(world:target(e, A, 1) == C)
|
||||||
|
CHECK(world:target(e, B, 0) == C)
|
||||||
|
CHECK(world:target(e, B, 1) == D)
|
||||||
|
CHECK(world:target(e, C, 0) == D)
|
||||||
|
CHECK(world:target(e, C, 1) == nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "loop until no target"
|
||||||
|
local world = world_new()
|
||||||
|
|
||||||
|
local ROOT = world:entity()
|
||||||
|
local e1 = world:entity()
|
||||||
|
local targets = {}
|
||||||
|
|
||||||
|
for i = 1, 10 do
|
||||||
|
local target = world:entity()
|
||||||
|
targets[i] = target
|
||||||
|
world:add(e1, pair(ROOT, target))
|
||||||
|
end
|
||||||
|
|
||||||
|
local i = 0
|
||||||
|
local target = world:target(e1, ROOT, 0)
|
||||||
|
while target do
|
||||||
|
i+=1
|
||||||
|
CHECK(targets[i] == target)
|
||||||
|
target = world:target(e1, ROOT, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
CHECK(i == 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
TEST("world:contains", function()
|
TEST("world:contains", function()
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
|
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
CHECK(world:contains(id))
|
CHECK(world:contains(id))
|
||||||
world:delete(id)
|
|
||||||
CHECK(not world:contains(id))
|
do CASE "should not exist after delete"
|
||||||
|
world:delete(id)
|
||||||
|
CHECK(not world:contains(id))
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
type Tracker<T> = { track: (world: World, fn: (changes: {
|
type Tracker<T> = { track: (world: World, fn: (changes: {
|
||||||
added: () -> () -> (number, T),
|
added: () -> () -> (number, T),
|
||||||
|
@ -1434,7 +1534,7 @@ TEST("scheduler", function()
|
||||||
local DependsOn = components.DependsOn
|
local DependsOn = components.DependsOn
|
||||||
|
|
||||||
local Physics = scheduler.phase(Heartbeat)
|
local Physics = scheduler.phase(Heartbeat)
|
||||||
CHECK(world:target(Physics, DependsOn) == Heartbeat)
|
CHECK(world:target(Physics, DependsOn, 0) == Heartbeat)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "user-defined sub phases"
|
do CASE "user-defined sub phases"
|
||||||
|
@ -1447,7 +1547,7 @@ TEST("scheduler", function()
|
||||||
local A = scheduler.phase(phases.Heartbeat)
|
local A = scheduler.phase(phases.Heartbeat)
|
||||||
local B = scheduler.phase(A)
|
local B = scheduler.phase(A)
|
||||||
|
|
||||||
CHECK(world:target(B, DependsOn) == A)
|
CHECK(world:target(B, DependsOn, 0) == A)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "phase order"
|
do CASE "phase order"
|
||||||
|
|
Loading…
Reference in a new issue