mirror of
https://github.com/Ukendio/jecs.git
synced 2025-08-04 11:19:17 +00:00
Compare commits
8 commits
v0.9.0-rc.
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
0b6bfea5c8 | ||
|
3cfce10a4a | ||
|
add9ad3939 | ||
|
4153a7cdfe | ||
|
4230a0a797 | ||
|
499afc20cd | ||
|
f284de6ec1 | ||
|
abc0b1ec22 |
11 changed files with 105 additions and 32 deletions
|
@ -69,6 +69,9 @@ local function observers_new<T...>(
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
world:added(term, emplaced)
|
world:added(term, emplaced)
|
||||||
world:changed(term, emplaced)
|
world:changed(term, emplaced)
|
||||||
end
|
end
|
||||||
|
@ -151,7 +154,7 @@ local function monitors_new<T...>(
|
||||||
i += 1
|
i += 1
|
||||||
entities[i] = entity
|
entities[i] = entity
|
||||||
if callback ~= nil then
|
if callback ~= nil then
|
||||||
callback(entity, id, value)
|
callback(entity, jecs.OnAdd)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -164,6 +167,9 @@ local function monitors_new<T...>(
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
end
|
||||||
world:added(term, emplaced)
|
world:added(term, emplaced)
|
||||||
world:removed(term, removed)
|
world:removed(term, removed)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local jecs = require(ReplicatedStorage.ecs)
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local schedule = require(ReplicatedStorage.schedule)
|
local schedule = require(ReplicatedStorage.schedule)
|
||||||
local observers_add = require(ReplicatedStorage.observers_add)
|
|
||||||
|
|
||||||
local SYSTEM = schedule.SYSTEM
|
local SYSTEM = schedule.SYSTEM
|
||||||
local RUN = schedule.RUN
|
local RUN = schedule.RUN
|
||||||
require(ReplicatedStorage.components)
|
require(ReplicatedStorage.components)
|
||||||
local world = observers_add(jecs.world())
|
local world = jecs.world()
|
||||||
|
|
||||||
local systems = ReplicatedStorage.systems
|
local systems = ReplicatedStorage.systems
|
||||||
SYSTEM(world, systems.receive_replication)
|
SYSTEM(world, systems.receive_replication)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
local observers_add = require("../ReplicatedStorage/observers_add")
|
|
||||||
|
|
||||||
export type World = typeof(observers_add(jecs.world()))
|
export type World = typeof(jecs.world())
|
||||||
export type Entity = jecs.Entity
|
export type Entity = jecs.Entity
|
||||||
export type Id<T> = jecs.Id<T>
|
export type Id<T> = jecs.Id<T>
|
||||||
export type Snapshot = {
|
export type Snapshot = {
|
||||||
|
|
|
@ -97,7 +97,7 @@ Builtin component type. This ID is for naming components, but realistically you
|
||||||
jecs.Rest: Id
|
jecs.Rest: Id
|
||||||
```
|
```
|
||||||
|
|
||||||
Builtin component type. This ID is for setting up a callback that is invoked when an instance of a component is changed.
|
Builtin component type. This ID is simply for denoting the end of the range for builtin component IDs.
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ Creates a new component. Do note components are entities as well, meaning jecs a
|
||||||
These are meant to be added onto other entities through `add` and `set`
|
These are meant to be added onto other entities through `add` and `set`
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function World:component<T>(): Entity<T> -- The new componen.
|
function World:component<T>(): Entity<T> -- The new component.
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
4
jecs.d.ts
vendored
4
jecs.d.ts
vendored
|
@ -247,6 +247,10 @@ export class World {
|
||||||
* @returns A Query object to iterate over results.
|
* @returns A Query object to iterate over results.
|
||||||
*/
|
*/
|
||||||
query<T extends Id[]>(...components: T): Query<InferComponents<T>>;
|
query<T extends Id[]>(...components: T): Query<InferComponents<T>>;
|
||||||
|
|
||||||
|
added<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
|
||||||
|
changed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
|
||||||
|
removed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>) => void): () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function world(): World;
|
export function world(): World;
|
||||||
|
|
39
jecs.luau
39
jecs.luau
|
@ -24,10 +24,10 @@ export type Archetype = {
|
||||||
|
|
||||||
export type QueryInner = {
|
export type QueryInner = {
|
||||||
compatible_archetypes: { Archetype },
|
compatible_archetypes: { Archetype },
|
||||||
ids: { i53 },
|
ids: { Id },
|
||||||
filter_with: { i53 },
|
filter_with: { Id },
|
||||||
filter_without: { i53 },
|
filter_without: { Id },
|
||||||
next: () -> (number, ...any),
|
next: () -> (Entity, ...any),
|
||||||
world: World,
|
world: World,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
|
patch: (self: Query<T...>, fn: (T...) -> (T...)) -> (),
|
||||||
|
view: (self: Query<T...>) -> View<T...>,
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
|
@ -68,6 +70,12 @@ export type Observer = {
|
||||||
query: QueryInner,
|
query: QueryInner,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type observer = {
|
||||||
|
callback: (archetype: archetype) -> (),
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
|
||||||
type archetype = {
|
type archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
types: { i53 },
|
types: { i53 },
|
||||||
|
@ -150,9 +158,9 @@ export type World = {
|
||||||
|
|
||||||
observable: Map<Id, Map<Id, { Observer }>>,
|
observable: Map<Id, Map<Id, { Observer }>>,
|
||||||
|
|
||||||
added: <T>(World, Id<T>, <e>(e: Entity<e>, id: Id<T>, value: T?) -> ()) -> () -> (),
|
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T?) -> ()) -> () -> (),
|
||||||
removed: <T>(World, Id<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
||||||
changed: <T>(World, Id<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||||
|
|
||||||
--- Enforce a check on entities to be created within desired range
|
--- Enforce a check on entities to be created within desired range
|
||||||
range: (self: World, range_begin: number, range_end: number?) -> (),
|
range: (self: World, range_begin: number, range_end: number?) -> (),
|
||||||
|
@ -504,7 +512,7 @@ local function ecs_pair_second(world: world, e: i53)
|
||||||
return ecs_get_alive(world, obj)
|
return ecs_get_alive(world, obj)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function query_match(query: QueryInner, archetype: archetype)
|
local function query_match(query: query, archetype: archetype)
|
||||||
local columns_map = archetype.columns_map
|
local columns_map = archetype.columns_map
|
||||||
local with = query.filter_with
|
local with = query.filter_with
|
||||||
|
|
||||||
|
@ -526,7 +534,7 @@ local function query_match(query: QueryInner, archetype: archetype)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_observers(world: world, event: i53, component: i53): { Observer }?
|
local function find_observers(world: world, event: i53, component: i53): { observer }?
|
||||||
local cache = world.observable[event]
|
local cache = world.observable[event]
|
||||||
if not cache then
|
if not cache then
|
||||||
return nil
|
return nil
|
||||||
|
@ -725,7 +733,7 @@ local function world_target(world: world, entity: i53, relation: i53, index: num
|
||||||
local nth = index or 0
|
local nth = index or 0
|
||||||
|
|
||||||
if nth >= count then
|
if nth >= count then
|
||||||
nth = nth + count + 1
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
nth = archetype.types[nth + idr.records[archetype_id]]
|
nth = archetype.types[nth + idr.records[archetype_id]]
|
||||||
|
@ -871,6 +879,7 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
||||||
|
|
||||||
for i, component_id in archetype.types do
|
for i, component_id in archetype.types do
|
||||||
local idr = id_record_ensure(world, component_id)
|
local idr = id_record_ensure(world, component_id)
|
||||||
|
idr.size += 1
|
||||||
local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
||||||
local column = if is_tag then NULL_ARRAY else {}
|
local column = if is_tag then NULL_ARRAY else {}
|
||||||
columns[i] = column
|
columns[i] = column
|
||||||
|
@ -882,11 +891,13 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
||||||
local object = ECS_PAIR_SECOND(component_id)
|
local object = ECS_PAIR_SECOND(component_id)
|
||||||
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)
|
||||||
|
idr_r.size += 1
|
||||||
|
|
||||||
archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
|
archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
|
||||||
|
|
||||||
local t = ECS_PAIR(EcsWildcard, object)
|
local t = ECS_PAIR(EcsWildcard, object)
|
||||||
local idr_t = id_record_ensure(world, t)
|
local idr_t = id_record_ensure(world, t)
|
||||||
|
idr_t.size += 1
|
||||||
|
|
||||||
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
||||||
end
|
end
|
||||||
|
@ -2615,7 +2626,7 @@ local function world_new()
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
||||||
if idr then
|
if idr then
|
||||||
idr.on_add = on_add
|
idr.on_add = on_add
|
||||||
else
|
else
|
||||||
|
@ -2650,7 +2661,7 @@ local function world_new()
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
||||||
if idr then
|
if idr then
|
||||||
idr.on_change = on_change
|
idr.on_change = on_change
|
||||||
else
|
else
|
||||||
|
@ -2681,7 +2692,7 @@ local function world_new()
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = world.component_index[component]
|
local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
|
||||||
if idr then
|
if idr then
|
||||||
idr.on_remove = on_remove
|
idr.on_remove = on_remove
|
||||||
else
|
else
|
||||||
|
@ -2748,7 +2759,7 @@ local function world_new()
|
||||||
local nth = index or 0
|
local nth = index or 0
|
||||||
|
|
||||||
if nth >= count then
|
if nth >= count then
|
||||||
nth = nth + count + 1
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
nth = archetype.types[nth + idr.records[archetype_id]]
|
nth = archetype.types[nth + idr.records[archetype_id]]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.9.0-rc.6",
|
"version": "0.9.0-rc.8",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -2,11 +2,28 @@ local jecs = require("@jecs")
|
||||||
local testkit = require("@testkit")
|
local testkit = require("@testkit")
|
||||||
local test = testkit.test()
|
local test = testkit.test()
|
||||||
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||||
|
local FOCUS = test.FOCUS
|
||||||
local ob = require("@addons/ob")
|
local ob = require("@addons/ob")
|
||||||
|
|
||||||
TEST("addons/observers", function()
|
TEST("addons/observers", function()
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
|
local world = jecs.world()
|
||||||
|
do CASE "monitors should accept pairs"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.monitor(world:query(jecs.pair(A, B)), function (_, event)
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 2)
|
||||||
|
|
||||||
|
world:remove(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 3)
|
||||||
|
end
|
||||||
do CASE "Ensure ordering between signals and observers"
|
do CASE "Ensure ordering between signals and observers"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
|
|
|
@ -1064,40 +1064,77 @@ end)
|
||||||
|
|
||||||
TEST("world:added", function()
|
TEST("world:added", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
do CASE "Should work even if set after the component has been used"
|
do CASE "Should work even if set after the component has been used"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
|
||||||
world:set(world:entity(), A, 2)
|
world:set(world:entity(), A, 2)
|
||||||
local ran = false
|
local ran = false
|
||||||
world:added(A, function()
|
world:added(A, function()
|
||||||
ran = true
|
ran = true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
world:set(entity, A, 3)
|
world:set(entity, A, 3)
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
do CASE "Should work even if set after the pair has been used"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:set(world:entity(), A, 2)
|
||||||
|
world:set(world:entity(), pair(A, B), 2)
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local entity = world:entity()
|
||||||
|
world:set(entity, pair(A, B), 3)
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should allow setting signal after Relation has been used as a component"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:add(world:entity(), A)
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:add(world:entity(), pair(A, B))
|
||||||
|
CHECK(ran)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should invoke signal for the Relation being set as a key despite a pair with Relation having been cached"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
world:add(world:entity(), pair(A, B))
|
||||||
|
|
||||||
|
world:added(A, function()
|
||||||
|
ran = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:add(world:entity(), A)
|
||||||
|
|
||||||
CHECK(ran)
|
CHECK(ran)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Should not override hook"
|
do CASE "Should not override hook"
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
|
||||||
local count = 1
|
local count = 1
|
||||||
local function counter()
|
local function counter()
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
world:set(A, jecs.OnAdd, counter)
|
world:set(A, jecs.OnAdd, counter)
|
||||||
world:added(A, counter)
|
world:added(A, counter)
|
||||||
world:set(world:entity(), A, false)
|
world:set(world:entity(), A, false)
|
||||||
CHECK(count == (1 + 2))
|
CHECK(count == (1 + 2))
|
||||||
world:set(world:entity(), A, false)
|
world:set(world:entity(), A, false)
|
||||||
|
|
||||||
CHECK(count == (1 + (2 * 2)))
|
CHECK(count == (1 + (2 * 2)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:range()", function()
|
TEST("world:range()", function()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.9.0-rc.6"
|
version = "0.9.0-rc.8"
|
||||||
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