From d9308a91e9d7ae2b200efd782f6e6a917e86b1e8 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 10 Jun 2025 14:28:00 +0200 Subject: [PATCH] lua51 --- .luacheckrc | 1 + .luaurc | 2 +- addons/observers.luau | 268 - benches/cached.luau | 160 - benches/general.luau | 244 - benches/query.luau | 246 - benches/visual/despawn.bench.luau | 64 - benches/visual/insertion.bench.luau | 81 - benches/visual/query.bench.luau | 164 - benches/visual/remove.bench.luau | 49 - benches/visual/spawn.bench.luau | 33 - benches/visual/wally.toml | 11 - demo/.gitignore | 6 - demo/README.md | 15 - demo/default.project.json | 55 - demo/src/ReplicatedStorage/collect.luau | 28 - demo/src/ReplicatedStorage/components.luau | 36 - demo/src/ReplicatedStorage/main.client.luau | 13 - demo/src/ReplicatedStorage/observers_add.luau | 190 - demo/src/ReplicatedStorage/remotes.luau | 50 - demo/src/ReplicatedStorage/schedule.luau | 136 - .../systems/receive_replication.luau | 86 - demo/src/ReplicatedStorage/types.luau | 15 - demo/src/ServerScriptService/main.server.luau | 19 - .../systems/life_is_painful.luau | 12 - .../systems/players_added.luau | 20 - .../systems/poison_hurts.luau | 12 - .../systems/replication.luau | 190 - demo/wally.toml | 8 - jecs.d.ts | 310 - jecs.luau => jecs.lua | 5152 ++++++++--------- mirror.luau | 659 --- package-lock.json | 4464 -------------- package.json | 47 - rokit.toml | 6 - test/addons/observers.luau | 136 - test/lol.luau | 158 - test/stress.client.luau | 122 - test/stress.project.json | 19 - test/tests.luau | 1855 ------ test/tools/entity_visualiser.luau | 24 - tsconfig.json | 24 - wally.toml | 15 - 43 files changed, 2437 insertions(+), 12768 deletions(-) create mode 100644 .luacheckrc delete mode 100644 addons/observers.luau delete mode 100644 benches/cached.luau delete mode 100644 benches/general.luau delete mode 100644 benches/query.luau delete mode 100644 benches/visual/despawn.bench.luau delete mode 100644 benches/visual/insertion.bench.luau delete mode 100644 benches/visual/query.bench.luau delete mode 100644 benches/visual/remove.bench.luau delete mode 100644 benches/visual/spawn.bench.luau delete mode 100644 benches/visual/wally.toml delete mode 100644 demo/.gitignore delete mode 100644 demo/README.md delete mode 100644 demo/default.project.json delete mode 100644 demo/src/ReplicatedStorage/collect.luau delete mode 100644 demo/src/ReplicatedStorage/components.luau delete mode 100644 demo/src/ReplicatedStorage/main.client.luau delete mode 100644 demo/src/ReplicatedStorage/observers_add.luau delete mode 100644 demo/src/ReplicatedStorage/remotes.luau delete mode 100644 demo/src/ReplicatedStorage/schedule.luau delete mode 100644 demo/src/ReplicatedStorage/systems/receive_replication.luau delete mode 100644 demo/src/ReplicatedStorage/types.luau delete mode 100644 demo/src/ServerScriptService/main.server.luau delete mode 100644 demo/src/ServerScriptService/systems/life_is_painful.luau delete mode 100644 demo/src/ServerScriptService/systems/players_added.luau delete mode 100644 demo/src/ServerScriptService/systems/poison_hurts.luau delete mode 100644 demo/src/ServerScriptService/systems/replication.luau delete mode 100644 demo/wally.toml delete mode 100644 jecs.d.ts rename jecs.luau => jecs.lua (58%) delete mode 100644 mirror.luau delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 rokit.toml delete mode 100644 test/addons/observers.luau delete mode 100644 test/lol.luau delete mode 100644 test/stress.client.luau delete mode 100644 test/stress.project.json delete mode 100644 test/tests.luau delete mode 100644 test/tools/entity_visualiser.luau delete mode 100644 tsconfig.json delete mode 100644 wally.toml diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..f2f5a37 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1 @@ +std="love+luajit" diff --git a/.luaurc b/.luaurc index 1d36832..7aa8f19 100644 --- a/.luaurc +++ b/.luaurc @@ -1,6 +1,6 @@ { "aliases": { - "jecs": "jecs", + "jecs": "jecs.luau", "testkit": "tools/testkit", "mirror": "mirror", "tools": "tools", diff --git a/addons/observers.luau b/addons/observers.luau deleted file mode 100644 index e2e18e7..0000000 --- a/addons/observers.luau +++ /dev/null @@ -1,268 +0,0 @@ -local jecs = require("@jecs") - -export type PatchedWorld = jecs.World & { - added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (), - removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (), - changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (), - observer: ( - PatchedWorld, - any, - (jecs.Entity) -> () - ) -> (), - monitor: ( - PatchedWorld, - any, - (jecs.Entity, jecs.Id) -> () - ) -> () -} - -local function observers_new(world, query, callback) - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity, id, value) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:changed(term, emplaced) - end -end - -local function join(world, component) - local sparse_array = {} - local dense_array = {} - local values = {} - local max_id = 0 - - world:added(component, function(entity, id, value) - max_id += 1 - sparse_array[entity] = max_id - dense_array[max_id] = entity - values[max_id] = value - end) - - world:removed(component, function(entity, id) - local e_swap = dense_array[max_id] - local v_swap = values[max_id] - - local dense = sparse_array[entity] - dense_array[dense] = e_swap - values[dense] = v_swap - - sparse_array[entity] = nil - dense_array[max_id] = nil - values[max_id] = nil - max_id -= 1 - end) - - world:changed(component, function(entity, id, value) - values[sparse_array[entity]] = value - end) - - return function() - local i = max_id - return function(): ...any - i -= 1 - if i == 0 then - return nil - end - local e = dense_array[i] - return e, values[i] - end - end -end - -local function monitors_new(world, query, callback) - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity: jecs.Entity) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity, jecs.OnAdd) - end - end - - local function removed(entity: jecs.Entity, component: jecs.Id) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - local EcsOnRemove = jecs.OnRemove :: jecs.Id - callback(entity, EcsOnRemove) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:removed(term, removed) - end -end - -local function observers_add(world: jecs.World): PatchedWorld - type Signal = { [jecs.Entity]: { (...any) -> () } } - - local world_mut = world :: jecs.World & {[string]: any} - - local signals = { - added = {} :: Signal, - emplaced = {} :: Signal, - removed = {} :: Signal - } - - world_mut.added = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () - ) - local listeners = signals.added[component] - if not listeners then - listeners = {} - signals.added[component] = listeners - - local function on_add(entity, id, value) - for _, listener in listeners :: any do - listener(entity, id, value) - end - end - local existing_hook = world:get(component, jecs.OnAdd) - if existing_hook then - table.insert(listeners, existing_hook) - end - - local idr = world.component_index[component] - if idr then - idr.hooks.on_add = on_add - else - world:set(component, jecs.OnAdd, on_add) - end - end - table.insert(listeners, fn) - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.changed = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () - ) - local listeners = signals.emplaced[component] - if not listeners then - listeners = {} - signals.emplaced[component] = listeners - local function on_change(entity, id, value: any) - for _, listener in listeners :: any do - listener(entity, id, value) - end - end - local existing_hook = world:get(component, jecs.OnChange) - if existing_hook then - table.insert(listeners, existing_hook) - end - local idr = world.component_index[component] - if idr then - idr.hooks.on_change = on_change - else - world:set(component, jecs.OnChange, on_change) - end - end - table.insert(listeners, fn) - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.removed = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id) -> () - ) - local listeners = signals.removed[component] - if not listeners then - listeners = {} - signals.removed[component] = listeners - local function on_remove(entity, id) - for _, listener in listeners :: any do - listener(entity, id) - end - end - local existing_hook = world:get(component, jecs.OnRemove) - if existing_hook then - table.insert(listeners, existing_hook) - end - - local idr = world.component_index[component] - if idr then - idr.hooks.on_remove = on_remove - else - world:set(component, jecs.OnRemove, on_remove) - end - end - - table.insert(listeners, fn) - - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.signals = signals - - world_mut.observer = observers_new - - world_mut.monitor = monitors_new - - world_mut.trackers = {} - - return world_mut :: PatchedWorld -end - -return observers_add diff --git a/benches/cached.luau b/benches/cached.luau deleted file mode 100644 index df8ff38..0000000 --- a/benches/cached.luau +++ /dev/null @@ -1,160 +0,0 @@ -local jecs = require("@jecs") -local mirror = require("@mirror") - -type i53 = number - -do - TITLE(testkit.color.white_underline("Jecs query")) - local ecs = jecs.World.new() - do - TITLE("one component in common") - - local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) - BENCH("4 component", function() - for _ in world:query(D, C, B, A) do - end - end) - end - - local D1 = ecs:component() - local D2 = ecs:component() - local D3 = ecs:component() - local D4 = ecs:component() - local D5 = ecs:component() - local D6 = ecs:component() - local D7 = ecs:component() - local D8 = ecs:component() - - local function flip() - return math.random() >= 0.15 - end - - local added = 0 - local archetypes = {} - for i = 1, 2 ^ 16 - 2 do - local entity = ecs:entity() - - local combination = "" - - if flip() then - combination ..= "B" - ecs:set(entity, D2, { value = true }) - end - if flip() then - combination ..= "C" - ecs:set(entity, D3, { value = true }) - end - if flip() then - combination ..= "D" - ecs:set(entity, D4, { value = true }) - end - if flip() then - combination ..= "E" - ecs:set(entity, D5, { value = true }) - end - if flip() then - combination ..= "F" - ecs:set(entity, D6, { value = true }) - end - if flip() then - combination ..= "G" - ecs:set(entity, D7, { value = true }) - end - if flip() then - combination ..= "H" - ecs:set(entity, D8, { value = true }) - end - - if #combination == 7 then - added += 1 - ecs:set(entity, D1, { value = true }) - end - archetypes[combination] = true - end - - local a = 0 - for _ in archetypes do - a += 1 - end - - view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) - end -end - -do - TITLE(testkit.color.white_underline("Mirror query")) - local ecs = mirror.World.new() - do - TITLE("one component in common") - - local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) - BENCH("4 component", function() - for _ in world:query(D, C, B, A) do - end - end) - end - - local D1 = ecs:component() - local D2 = ecs:component() - local D3 = ecs:component() - local D4 = ecs:component() - local D5 = ecs:component() - local D6 = ecs:component() - local D7 = ecs:component() - local D8 = ecs:component() - - local function flip() - return math.random() >= 0.15 - end - - local added = 0 - local archetypes = {} - for i = 1, 2 ^ 16 - 2 do - local entity = ecs:entity() - - local combination = "" - - if flip() then - combination ..= "B" - ecs:set(entity, D2, { value = true }) - end - if flip() then - combination ..= "C" - ecs:set(entity, D3, { value = true }) - end - if flip() then - combination ..= "D" - ecs:set(entity, D4, { value = true }) - end - if flip() then - combination ..= "E" - ecs:set(entity, D5, { value = true }) - end - if flip() then - combination ..= "F" - ecs:set(entity, D6, { value = true }) - end - if flip() then - combination ..= "G" - ecs:set(entity, D7, { value = true }) - end - if flip() then - combination ..= "H" - ecs:set(entity, D8, { value = true }) - end - - if #combination == 7 then - added += 1 - ecs:set(entity, D1, { value = true }) - end - archetypes[combination] = true - end - - local a = 0 - for _ in archetypes do - a += 1 - end - - view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) - end -end diff --git a/benches/general.luau b/benches/general.luau deleted file mode 100644 index 3df5538..0000000 --- a/benches/general.luau +++ /dev/null @@ -1,244 +0,0 @@ -local jecs = require("@jecs") -local testkit = require("@testkit") - -local BENCH, START = testkit.benchmark() - -local function TITLE(s: string) - print() - print(testkit.color.white(s)) -end - -local N = 2 ^ 17 - -local pair = jecs.pair - -do - TITLE("create") - local world = jecs.World.new() - - BENCH("entity", function() - for i = 1, START(N) do - world:entity() - end - end) - - local A = world:component() - local B = world:component() - - BENCH("pair", function() - for i = 1, START(N) do - jecs.pair(A, B) - end - end) -end - -do - TITLE("set") - - local world = jecs.World.new() - local A = world:component() - - local entities = table.create(N) - - for i = 1, N do - entities[i] = world:entity() - end - - BENCH("add", function() - for i = 1, START(N) do - world:set(entities[i], A, 1) - end - end) - - BENCH("set", function() - for i = 1, START(N) do - world:set(entities[i], A, 2) - end - end) - - BENCH("remove", function() - for i = 1, START(N) do - world:remove(entities[i], A) - end - end) -end - --- we have a separate benchmark for relationships. --- this is due to that relationships have a very high id compared to normal --- components, which cause them to get added into the hashmap portion. -do - TITLE("set relationship") - - local world = jecs.World.new() - local A = world:component() - - local entities = table.create(N) - - for i = 1, N do - entities[i] = world:entity() - world:set(entities[i], A, 1) - end - - local pair = jecs.pair(A, world:entity()) - - BENCH("add", function() - for i = 1, START(N) do - world:set(entities[i], pair, 1) - end - end) - - BENCH("set", function() - for i = 1, START(N) do - world:set(entities[i], pair, 2) - end - end) - - BENCH("remove", function() - for i = 1, START(N) do - world:remove(entities[i], pair) - end - end) -end - -do - TITLE("get") - - local world = jecs.World.new() - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local entities = table.create(N) - - for i = 1, N do - entities[i] = world:entity() - world:set(entities[i], A, 1) - world:set(entities[i], B, 1) - world:set(entities[i], C, 1) - world:set(entities[i], D, 1) - end - - BENCH("1", function() - for i = 1, START(N) do - world:get(entities[i], A) - end - end) - - BENCH("2", function() - for i = 1, START(N) do - world:get(entities[i], A, B) - end - end) - - BENCH("3", function() - for i = 1, START(N) do - world:get(entities[i], A, B, C) - end - end) - - BENCH("4", function() - for i = 1, START(N) do - world:get(entities[i], A, B, C, D) - end - end) -end - -do - TITLE("target") - - BENCH("1st target", function() - local world = jecs.World.new() - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local entities = table.create(N) - - for i = 1, N do - local ent = world:entity() - entities[i] = ent - - world:set(ent, pair(A, A)) - world:set(ent, pair(A, B)) - world:set(ent, pair(A, C)) - world:set(ent, pair(A, D)) - end - - for i = 1, START(N) do - world:target(entities[i], A, 0) - end - end) -end - ---- this benchmark is used to view how fragmentation affects query performance ---- we use this by determining how many entities should fit per arcehtype, instead ---- of creating x amount of archetypes. this would scale better with any amount of ---- entities. -do - TITLE(`query {N} entities`) - - local function view_bench(n: number) - BENCH(`{n} entities per archetype`, function() - local world = jecs.World.new() - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - - for i = 1, N, n do - local ct = world:entity() - for j = 1, n do - local id = world:entity() - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - world:set(id, D, true) - world:add(id, ct) - end - end - - local q = world:query(A, B, C, D) - START() - for id in q do - end - end) - - BENCH(`inlined query`, function() - local world = jecs.World.new() - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - - for i = 1, N, n do - local ct = world:entity() - for j = 1, n do - local id = world:entity() - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - world:set(id, D, true) - world:add(id, ct) - end - end - - local archetypes = world:query(A, B, C, D):archetypes() - START() - for _, archetype in archetypes do - local columns, records = archetype.columns, archetype.records - local a = columns[records[A]] - local b = columns[records[B]] - local c = columns[records[C]] - local d = columns[records[D]] - for row in archetype.entities do - local _1, _2, _3, _4 = a[row], b[row], c[row], d[row] - end - end - end) - end - - for i = 13, 0, -1 do - view_bench(2 ^ i) - end -end diff --git a/benches/query.luau b/benches/query.luau deleted file mode 100644 index ecd7fd5..0000000 --- a/benches/query.luau +++ /dev/null @@ -1,246 +0,0 @@ ---!optimize 2 ---!native - -local testkit = require("@testkit") -local BENCH, START = testkit.benchmark() -local function TITLE(title: string) - print() - print(testkit.color.white(title)) -end - -local jecs = require("@jecs") -local mirror = require("@mirror") - -type i53 = number - -do - TITLE(testkit.color.white_underline("Jecs query")) - local ecs = jecs.World.new() - do - TITLE("one component in common") - - local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) - BENCH("1 component", function() - for _ in world:query(A) do - end - end) - - BENCH("2 component", function() - for _ in world:query(B, A) do - end - end) - - BENCH("4 component", function() - for _ in world:query(D, C, B, A) do - end - end) - - BENCH("8 component", function() - for _ in world:query(H, G, F, E, D, C, B, A) do - end - end) - - local e = world:entity() - world:set(e, A, true) - world:set(e, B, true) - world:set(e, C, true) - world:set(e, D, true) - world:set(e, E, true) - world:set(e, F, true) - world:set(e, G, true) - world:set(e, H, true) - - BENCH("Update Data", function() - for _ = 1, 100 do - world:set(e, A, false) - world:set(e, B, false) - world:set(e, C, false) - world:set(e, D, false) - world:set(e, E, false) - world:set(e, F, false) - world:set(e, G, false) - world:set(e, H, false) - end - end) - end - - local D1 = ecs:component() - local D2 = ecs:component() - local D3 = ecs:component() - local D4 = ecs:component() - local D5 = ecs:component() - local D6 = ecs:component() - local D7 = ecs:component() - local D8 = ecs:component() - - local function flip() - return math.random() >= 0.15 - end - - local added = 0 - local archetypes = {} - for i = 1, 2 ^ 16 - 2 do - local entity = ecs:entity() - - local combination = "" - - if flip() then - combination ..= "B" - ecs:set(entity, D2, { value = true }) - end - if flip() then - combination ..= "C" - ecs:set(entity, D3, { value = true }) - end - if flip() then - combination ..= "D" - ecs:set(entity, D4, { value = true }) - end - if flip() then - combination ..= "E" - ecs:set(entity, D5, { value = true }) - end - if flip() then - combination ..= "F" - ecs:set(entity, D6, { value = true }) - end - if flip() then - combination ..= "G" - ecs:set(entity, D7, { value = true }) - end - if flip() then - combination ..= "H" - ecs:set(entity, D8, { value = true }) - end - - if #combination == 7 then - added += 1 - ecs:set(entity, D1, { value = true }) - end - archetypes[combination] = true - end - - local a = 0 - for _ in archetypes do - a += 1 - end - - view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) - end -end - -do - TITLE(testkit.color.white_underline("Mirror query")) - local ecs = mirror.World.new() - do - TITLE("one component in common") - - local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) - BENCH("1 component", function() - for _ in world:query(A) do - end - end) - - BENCH("2 component", function() - for _ in world:query(B, A) do - end - end) - - BENCH("4 component", function() - for _ in world:query(D, C, B, A) do - end - end) - - BENCH("8 component", function() - for _ in world:query(H, G, F, E, D, C, B, A) do - end - end) - - local e = world:entity() - world:set(e, A, true) - world:set(e, B, true) - world:set(e, C, true) - world:set(e, D, true) - world:set(e, E, true) - world:set(e, F, true) - world:set(e, G, true) - world:set(e, H, true) - - BENCH("Update Data", function() - for _ = 1, 100 do - world:set(e, A, false) - world:set(e, B, false) - world:set(e, C, false) - world:set(e, D, false) - world:set(e, E, false) - world:set(e, F, false) - world:set(e, G, false) - world:set(e, H, false) - end - end) - end - - local D1 = ecs:component() - local D2 = ecs:component() - local D3 = ecs:component() - local D4 = ecs:component() - local D5 = ecs:component() - local D6 = ecs:component() - local D7 = ecs:component() - local D8 = ecs:component() - - local function flip() - return math.random() >= 0.15 - end - - local added = 0 - local archetypes = {} - for i = 1, 2 ^ 16 - 2 do - local entity = ecs:entity() - - local combination = "" - - if flip() then - combination ..= "B" - ecs:set(entity, D2, { value = true }) - end - if flip() then - combination ..= "C" - ecs:set(entity, D3, { value = true }) - end - if flip() then - combination ..= "D" - ecs:set(entity, D4, { value = true }) - end - if flip() then - combination ..= "E" - ecs:set(entity, D5, { value = true }) - end - if flip() then - combination ..= "F" - ecs:set(entity, D6, { value = true }) - end - if flip() then - combination ..= "G" - ecs:set(entity, D7, { value = true }) - end - if flip() then - combination ..= "H" - ecs:set(entity, D8, { value = true }) - end - - if #combination == 7 then - added += 1 - ecs:set(entity, D1, { value = true }) - end - archetypes[combination] = true - end - - local a = 0 - for _ in archetypes do - a += 1 - end - - view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) - end -end diff --git a/benches/visual/despawn.bench.luau b/benches/visual/despawn.bench.luau deleted file mode 100644 index 5c424d9..0000000 --- a/benches/visual/despawn.bench.luau +++ /dev/null @@ -1,64 +0,0 @@ ---!optimize 2 ---!native - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Matter = require(ReplicatedStorage.DevPackages.Matter) -local ecr = require(ReplicatedStorage.DevPackages.ecr) -local jecs = require(ReplicatedStorage.Lib) -local pair = jecs.pair -local newWorld = Matter.World.new() -local ecs = jecs.World.new() -local mirror = require(ReplicatedStorage.mirror) -local mcs = mirror.World.new() - -local C1 = ecs:component() -local C2 = ecs:entity() -ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete)) -local C3 = ecs:entity() -ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete)) -local C4 = ecs:entity() -ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E1 = mcs:component() -local E2 = mcs:entity() -mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E3 = mcs:entity() -mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E4 = mcs:entity() -mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete)) - -local registry2 = ecr.registry() - -return { - ParameterGenerator = function() - local j = ecs:entity() - ecs:set(j, C1, true) - local m = mcs:entity() - mcs:set(m, E1, true) - for i = 1, 1000 do - local friend1 = ecs:entity() - local friend2 = mcs:entity() - - ecs:add(friend1, pair(C2, j)) - ecs:add(friend1, pair(C3, j)) - ecs:add(friend1, pair(C4, j)) - - mcs:add(friend2, pair(E2, m)) - mcs:add(friend2, pair(E3, m)) - mcs:add(friend2, pair(E4, m)) - end - return { - m = m, - j = j, - } - end, - - Functions = { - Mirror = function(_, a) - mcs:delete(a.m) - end, - - Jecs = function(_, a) - ecs:delete(a.j) - end, - }, -} diff --git a/benches/visual/insertion.bench.luau b/benches/visual/insertion.bench.luau deleted file mode 100644 index 802b406..0000000 --- a/benches/visual/insertion.bench.luau +++ /dev/null @@ -1,81 +0,0 @@ ---!optimize 2 ---!native - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Matter = require(ReplicatedStorage.DevPackages.Matter) -local ecr = require(ReplicatedStorage.DevPackages.ecr) -local jecs = require(ReplicatedStorage.Lib:Clone()) -local ecs = jecs.World.new() -local mirror = require(ReplicatedStorage.mirror:Clone()) -local mcs = mirror.World.new() - -local A1 = Matter.component() -local A2 = Matter.component() -local A3 = Matter.component() -local A4 = Matter.component() -local A5 = Matter.component() -local A6 = Matter.component() -local A7 = Matter.component() -local A8 = Matter.component() - -local B1 = ecr.component() -local B2 = ecr.component() -local B3 = ecr.component() -local B4 = ecr.component() -local B5 = ecr.component() -local B6 = ecr.component() -local B7 = ecr.component() -local B8 = ecr.component() - -local C1 = ecs:component() -local C2 = ecs:component() -local C3 = ecs:component() -local C4 = ecs:component() -local C5 = ecs:component() -local C6 = ecs:component() -local C7 = ecs:component() -local C8 = ecs:component() -local E1 = mcs:component() -local E2 = mcs:component() -local E3 = mcs:component() -local E4 = mcs:component() -local E5 = mcs:component() -local E6 = mcs:component() -local E7 = mcs:component() -local E8 = mcs:component() - -local registry2 = ecr.registry() -return { - ParameterGenerator = function() - return - end, - - Functions = { - Mirror = function() - local e = mcs:entity() - for i = 1, 5000 do - mcs:set(e, E1, false) - mcs:set(e, E2, false) - mcs:set(e, E3, false) - mcs:set(e, E4, false) - mcs:set(e, E5, false) - mcs:set(e, E6, false) - mcs:set(e, E7, false) - mcs:set(e, E8, false) - end - end, - Jecs = function() - local e = ecs:entity() - for i = 1, 5000 do - ecs:set(e, C1, false) - ecs:set(e, C2, false) - ecs:set(e, C3, false) - ecs:set(e, C4, false) - ecs:set(e, C5, false) - ecs:set(e, C6, false) - ecs:set(e, C7, false) - ecs:set(e, C8, false) - end - end, - }, -} diff --git a/benches/visual/query.bench.luau b/benches/visual/query.bench.luau deleted file mode 100644 index 16602f1..0000000 --- a/benches/visual/query.bench.luau +++ /dev/null @@ -1,164 +0,0 @@ ---!optimize 2 ---!native - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Matter = require(ReplicatedStorage.DevPackages["_Index"]["matter-ecs_matter@0.8.1"].matter) -local ecr = require(ReplicatedStorage.DevPackages["_Index"]["centau_ecr@0.8.0"].ecr) -local newWorld = Matter.World.new() - -local jecs = require(ReplicatedStorage.Lib) -local mirror = require(ReplicatedStorage.mirror) -local mcs = mirror.World.new() -local ecs = jecs.World.new() - -local A1 = Matter.component() -local A2 = Matter.component() -local A3 = Matter.component() -local A4 = Matter.component() -local A5 = Matter.component() -local A6 = Matter.component() -local A7 = Matter.component() -local A8 = Matter.component() - -local B1 = ecr.component() -local B2 = ecr.component() -local B3 = ecr.component() -local B4 = ecr.component() -local B5 = ecr.component() -local B6 = ecr.component() -local B7 = ecr.component() -local B8 = ecr.component() - -local D1 = ecs:component() -local D2 = ecs:component() -local D3 = ecs:component() -local D4 = ecs:component() -local D5 = ecs:component() -local D6 = ecs:component() -local D7 = ecs:component() -local D8 = ecs:component() - -local E1 = mcs:entity() -local E2 = mcs:entity() -local E3 = mcs:entity() -local E4 = mcs:entity() -local E5 = mcs:entity() -local E6 = mcs:entity() -local E7 = mcs:entity() -local E8 = mcs:entity() - -local registry2 = ecr.registry() - -local function flip() - return math.random() >= 0.25 -end - -local N = 2 ^ 16 - 2 -local archetypes = {} - -local hm = 0 -for i = 1, N do - local id = registry2.create() - local combination = "" - local n = newWorld:spawn() - local entity = ecs:entity() - local m = mcs:entity() - - if flip() then - registry2:set(id, B1, { value = true }) - ecs:set(entity, D1, { value = true }) - newWorld:insert(n, A1({ value = true })) - mcs:set(m, E1, { value = 2 }) - end - if flip() then - combination ..= "B" - registry2:set(id, B2, { value = true }) - ecs:set(entity, D2, { value = true }) - mcs:set(m, E2, { value = 2 }) - newWorld:insert(n, A2({ value = true })) - end - if flip() then - combination ..= "C" - registry2:set(id, B3, { value = true }) - ecs:set(entity, D3, { value = true }) - mcs:set(m, E3, { value = 2 }) - newWorld:insert(n, A3({ value = true })) - end - if flip() then - combination ..= "D" - registry2:set(id, B4, { value = true }) - ecs:set(entity, D4, { value = true }) - mcs:set(m, E4, { value = 2 }) - - newWorld:insert(n, A4({ value = true })) - end - if flip() then - combination ..= "E" - registry2:set(id, B5, { value = true }) - ecs:set(entity, D5, { value = true }) - mcs:set(m, E5, { value = 2 }) - - newWorld:insert(n, A5({ value = true })) - end - if flip() then - combination ..= "F" - registry2:set(id, B6, { value = true }) - ecs:set(entity, D6, { value = true }) - mcs:set(m, E6, { value = 2 }) - newWorld:insert(n, A6({ value = true })) - end - if flip() then - combination ..= "G" - registry2:set(id, B7, { value = true }) - ecs:set(entity, D7, { value = true }) - mcs:set(m, E7, { value = 2 }) - newWorld:insert(n, A7({ value = true })) - end - if flip() then - combination ..= "H" - registry2:set(id, B8, { value = true }) - newWorld:insert(n, A8({ value = true })) - ecs:set(entity, D8, { value = true }) - mcs:set(m, E8, { value = 2 }) - end - - if combination:find("BCDF") then - if not archetypes[combination] then - print(combination) - end - hm += 1 - end - archetypes[combination] = true -end -print("TEST", hm) - -local count = 0 - -for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do - count += #archetype.entities -end - -print(count) - -return { - ParameterGenerator = function() - return - end, - - Functions = { - Matter = function() - for entityId, firstComponent in newWorld:query(A2, A4, A6, A8) do - end - end, - - ECR = function() - for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do - end - end, - - Jecs = function() - for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do - end - end, - }, -} diff --git a/benches/visual/remove.bench.luau b/benches/visual/remove.bench.luau deleted file mode 100644 index 5af2a17..0000000 --- a/benches/visual/remove.bench.luau +++ /dev/null @@ -1,49 +0,0 @@ ---!optimize 2 ---!native - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Matter = require(ReplicatedStorage.DevPackages.Matter) -local ecr = require(ReplicatedStorage.DevPackages.ecr) -local jecs = require(ReplicatedStorage.Lib) -local pair = jecs.pair -local ecs = jecs.World.new() -local mirror = require(ReplicatedStorage.mirror) -local mcs = mirror.World.new() - -local C1 = ecs:component() -local C2 = ecs:entity() -ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete)) -local C3 = ecs:entity() -ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete)) -local C4 = ecs:entity() -ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E1 = mcs:component() -local E2 = mcs:entity() -mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E3 = mcs:entity() -mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete)) -local E4 = mcs:entity() -mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete)) - -return { - ParameterGenerator = function() - end, - - Functions = { - Mirror = function() - local m = mcs:entity() - for i = 1, 100 do - mcs:add(m, E3) - mcs:remove(m, E3) - end - end, - - Jecs = function() - local j = ecs:entity() - for i = 1, 100 do - ecs:add(j, C3) - ecs:remove(j, C3) - end - end, - }, -} diff --git a/benches/visual/spawn.bench.luau b/benches/visual/spawn.bench.luau deleted file mode 100644 index 698ff8d..0000000 --- a/benches/visual/spawn.bench.luau +++ /dev/null @@ -1,33 +0,0 @@ ---!optimize 2 ---!native - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.Lib:Clone()) -local mirror = require(ReplicatedStorage.mirror:Clone()) - - - -return { - ParameterGenerator = function() - local ecs = jecs.world() - ecs:range(1000, 20000) - local mcs = mirror.World.new() - return ecs, mcs - end, - - Functions = { - Mirror = function(_, ecs, mcs) - for i = 1, 100 do - - mcs:entity() - end - end, - - Jecs = function(_, ecs, mcs) - for i = 1, 100 do - - ecs:entity() - end - end, -}, -} diff --git a/benches/visual/wally.toml b/benches/visual/wally.toml deleted file mode 100644 index cb0f731..0000000 --- a/benches/visual/wally.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "private/private" -version = "0.1.0-rc.6" -registry = "https://github.com/UpliftGames/wally-index" -realm = "shared" -include = ["default.project.json", "lib/**", "lib", "wally.toml", "README.md"] -exclude = ["**"] - -[dev-dependencies] -Matter = "matter-ecs/matter@0.8.0" -ecr = "centau/ecr@0.8.0" \ No newline at end of file diff --git a/demo/.gitignore b/demo/.gitignore deleted file mode 100644 index cf9d94d..0000000 --- a/demo/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Project place file -/example.rbxlx - -# Roblox Studio lock files -/*.rbxlx.lock -/*.rbxl.lock \ No newline at end of file diff --git a/demo/README.md b/demo/README.md deleted file mode 100644 index 241cc25..0000000 --- a/demo/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Demo - -## Build with Rojo -To build the place, run the following commands from the root of the repository: - -```bash -cd demo -rojo build -o "demo.rbxl" -``` - -Next, open `demo.rbxl` in Roblox Studio and start the Rojo server: - -```bash -rojo serve -``` diff --git a/demo/default.project.json b/demo/default.project.json deleted file mode 100644 index e95cf06..0000000 --- a/demo/default.project.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "demo", - "emitLegacyScripts": false, - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "$className": "ReplicatedStorage", - "$path": "src/ReplicatedStorage", - "ecs": { - "$path": "../jecs.luau" - }, - "Packages": { - "$path": "Packages" - } - }, - "ServerScriptService": { - "$className": "ServerScriptService", - "$path": "src/ServerScriptService" - }, - "Workspace": { - "$properties": { - "FilteringEnabled": true - }, - "Baseplate": { - "$className": "Part", - "$properties": { - "Anchored": true, - "Color": [0.38823, 0.37254, 0.38823], - "Locked": true, - "Position": [0, -10, 0], - "Size": [512, 20, 512] - } - } - }, - "Lighting": { - "$properties": { - "Ambient": [0, 0, 0], - "Brightness": 2, - "GlobalShadows": true, - "Outlines": false, - "Technology": "Voxel" - } - }, - "SoundService": { - "$properties": { - "RespectFilteringEnabled": true - } - }, - "StarterPlayer": { - "StarterPlayerScripts": { - "$path": "src/StarterPlayer/StarterPlayerScripts" - } - } - } -} diff --git a/demo/src/ReplicatedStorage/collect.luau b/demo/src/ReplicatedStorage/collect.luau deleted file mode 100644 index d6df981..0000000 --- a/demo/src/ReplicatedStorage/collect.luau +++ /dev/null @@ -1,28 +0,0 @@ -local function collect( - signal: { - Connect: (RBXScriptSignal, fn: (T...) -> ()) -> RBXScriptConnection - } -): () -> (T...) - local enqueued = {} - - local i = 0 - - local connection = (signal :: any):Connect(function(...) - table.insert(enqueued, { ... }) - i += 1 - end) - - return function(): any - if i == 0 then - return - end - - i -= 1 - - local args: any = table.remove(enqueued, 1) - - return unpack(args) - end, connection -end - -return collect diff --git a/demo/src/ReplicatedStorage/components.luau b/demo/src/ReplicatedStorage/components.luau deleted file mode 100644 index 5bb3225..0000000 --- a/demo/src/ReplicatedStorage/components.luau +++ /dev/null @@ -1,36 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) -local types = require("./types") - -local Networked = jecs.tag() -local NetworkedPair = jecs.tag() - -local Renderable = jecs.component() :: jecs.Id -jecs.meta(Renderable, Networked) - - -local Poison = jecs.component() :: jecs.Id -jecs.meta(Poison, Networked) - -local Health = jecs.component() :: jecs.Id -jecs.meta(Health, Networked) - -local Player = jecs.component() :: jecs.Id -jecs.meta(Player, Networked) - - -local components = { - Renderable = Renderable, - Player = Player, - Poison = Poison, - Health = Health, - - Networked = Networked, - NetworkedPair = NetworkedPair, -} - -for name, component in components do - jecs.meta(component, jecs.Name, name) -end - -return components diff --git a/demo/src/ReplicatedStorage/main.client.luau b/demo/src/ReplicatedStorage/main.client.luau deleted file mode 100644 index 11dffbc..0000000 --- a/demo/src/ReplicatedStorage/main.client.luau +++ /dev/null @@ -1,13 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) -local schedule = require(ReplicatedStorage.schedule) -local observers_add = require(ReplicatedStorage.observers_add) - -local SYSTEM = schedule.SYSTEM -local RUN = schedule.RUN -require(ReplicatedStorage.components) -local world = observers_add(jecs.world()) - -local systems = ReplicatedStorage.systems -SYSTEM(world, systems.receive_replication) -RUN(world) diff --git a/demo/src/ReplicatedStorage/observers_add.luau b/demo/src/ReplicatedStorage/observers_add.luau deleted file mode 100644 index c959b88..0000000 --- a/demo/src/ReplicatedStorage/observers_add.luau +++ /dev/null @@ -1,190 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) - -type Observer = { - callback: (jecs.Entity) -> (), - query: jecs.Query, -} - -export type PatchedWorld = jecs.World & { - added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), - removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (), - changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), - -- deleted: (PatchedWorld, () -> ()) -> () -> (), - observer: (PatchedWorld, Observer) -> (), - monitor: (PatchedWorld, Observer) -> (), -} - -local function observers_new(world, description) - local query = description.query - local callback = description.callback - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity: jecs.Entity) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:changed(term, emplaced) - end -end - -local function monitors_new(world, description) - local query = description.query - local callback = description.callback - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity: jecs.Entity) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity, jecs.OnAdd) - end - end - - local function removed(entity: jecs.Entity, component: jecs.Id) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity, jecs.OnRemove) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:removed(term, removed) - end -end - -local function observers_add(world: jecs.World): PatchedWorld - local signals = { - added = {}, - emplaced = {}, - removed = {}, - deleted = {} - } - - world = world :: jecs.World & {[string]: any} - - world.added = function(_, component, fn) - local listeners = signals.added[component] - if not listeners then - listeners = {} - signals.added[component] = listeners - - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_add(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnAdd, on_add) - idr.hooks.on_add = on_add :: any - idr_r.hooks.on_add = on_add :: any - end - table.insert(listeners, fn) - end - - world.changed = function(_, component, fn) - local listeners = signals.emplaced[component] - if not listeners then - listeners = {} - signals.emplaced[component] = listeners - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_change(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnChange, on_change) - idr.hooks.on_change = on_change :: any - idr_r.hooks.on_change = on_change :: any - end - table.insert(listeners, fn) - end - - world.removed = function(_, component, fn) - local listeners = signals.removed[component] - if not listeners then - listeners = {} - signals.removed[component] = listeners - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_remove(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnRemove, on_remove) - idr.hooks.on_remove = on_remove :: any - idr_r.hooks.on_remove = on_remove :: any - end - table.insert(listeners, fn) - end - - world.signals = signals - - world.observer = observers_new - - world.monitor = monitors_new - - -- local world_delete = world.delete - - -- world.deleted = function(_, fn) - -- local listeners = signals.deleted - -- table.insert(listeners, fn) - -- end - -- world.delete = function(world, entity) - -- world_delete(world, entity) - -- for _, fn in signals.deleted do - -- fn(entity) - -- end - -- end - - return world :: PatchedWorld -end - -return observers_add diff --git a/demo/src/ReplicatedStorage/remotes.luau b/demo/src/ReplicatedStorage/remotes.luau deleted file mode 100644 index 4838770..0000000 --- a/demo/src/ReplicatedStorage/remotes.luau +++ /dev/null @@ -1,50 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local types = require("../ReplicatedStorage/types") - -type Signal = { - Connect: (Signal, fn: (T...) -> ()) -> RBXScriptConnection -} -type Remote = { - FireClient: (Remote, T...) -> (), - FireAllClients: (Remote, T...) -> (), - FireServer: (Remote) -> (), - OnServerEvent: { - Connect: (any, fn: (Player, T...) -> () ) -> () - }, - OnClientEvent: { - Connect: (any, fn: (T...) -> () ) -> () - } - -} - -local function stream_ensure(name): Remote - local remote = ReplicatedStorage:FindFirstChild(name) - if not remote then - remote = Instance.new("RemoteEvent") - remote.Name = name - remote.Parent = ReplicatedStorage - end - return remote :: any -end - -local function datagram_ensure(name): Remote - local remote = ReplicatedStorage:FindFirstChild(name) - if not remote then - remote = Instance.new("UnreliableRemoteEvent") - remote.Name = name - remote.Parent = ReplicatedStorage - end - return remote :: any -end - -return { - input = datagram_ensure("input") :: Remote, - replication = stream_ensure("replication") :: Remote<{ - [string]: { - set: { types.Entity }?, - values: { any }?, - removed: { types.Entity }? - } - }>, - -} diff --git a/demo/src/ReplicatedStorage/schedule.luau b/demo/src/ReplicatedStorage/schedule.luau deleted file mode 100644 index 7030bbe..0000000 --- a/demo/src/ReplicatedStorage/schedule.luau +++ /dev/null @@ -1,136 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jabby = require(ReplicatedStorage.Packages.jabby) -local jecs = require(ReplicatedStorage.ecs) - -jabby.set_check_function(function() return true end) - -local scheduler = jabby.scheduler.create("jabby scheduler") - -jabby.register({ - applet = jabby.applets.scheduler, - name = "Scheduler", - configuration = { - scheduler = scheduler, - }, -}) - -local ContextActionService = game:GetService("ContextActionService") - -local function create_widget(_, state: Enum.UserInputState) - local client = jabby.obtain_client() - if state ~= Enum.UserInputState.Begin then return end - client.spawn_app(client.apps.home, nil) -end - -local RunService = game:GetService("RunService") - -local System = jecs.component() :: jecs.Id<{ - fn: () -> (), - name: string, -}> -local DependsOn = jecs.component() -local Phase = jecs.tag() -local Event = jecs.component() :: jecs.Id - -local pair = jecs.pair - -local types = require(ReplicatedStorage.types) - -local function ECS_PHASE(world, after: types.Entity) - local phase = world:entity() - world:add(phase, Phase) - if after then - local dependency = pair(DependsOn, after) - world:add(phase, dependency) - end - - return phase -end - -local Heartbeat = jecs.tag() -jecs.meta(Heartbeat, Phase) -jecs.meta(Heartbeat, Event, RunService.Heartbeat) - -local PreSimulation = jecs.tag() -jecs.meta(PreSimulation, Phase) -jecs.meta(PreSimulation, Event, RunService.PreSimulation) - -local PreAnimation = jecs.tag() -jecs.meta(PreAnimation, Phase) -jecs.meta(PreAnimation, Event, RunService.PreAnimation) - -local PreRender = jecs.tag() -jecs.meta(PreRender, Phase) -jecs.meta(PreRender, Event, RunService.PreRender) - -local function ECS_SYSTEM(world: types.World, mod: ModuleScript, phase: types.Entity?) - local system = world:entity() - local p = phase or Heartbeat - local fn = require(mod) :: (...any) -> () - world:set(system, System, { - fn = fn(world, 0) or fn, - name = mod.Name, - }) - - local depends_on = DependsOn :: jecs.Entity - world:add(system, pair(depends_on, p)) -end -local function find_systems_w_phase(world: types.World, systems, phase: types.Entity) - local phase_name = world:get(phase, jecs.Name) :: string - for _, s in world:query(System):with(pair(DependsOn, phase)) do - table.insert(systems, { - id = scheduler:register_system({ - phase = phase_name, - name = s.name, - }), - fn = s.fn - }) - end - for after in world:query(Phase, pair(DependsOn, phase)) do - find_systems_w_phase(world, systems, after) - end - return systems -end - -local function ECS_RUN(world: types.World) - - jabby.register({ - applet = jabby.applets.world, - name = "MyWorld", - configuration = { - world = world, - }, - }) - - if RunService:IsClient() then - ContextActionService:BindAction("Open Jabby Home", create_widget, false, Enum.KeyCode.F4) - end - - for phase, event in world:query(Event, Phase) do - local systems = find_systems_w_phase(world, {}, phase) - event:Connect(function(...) - for _, system in systems do - scheduler:run(system.id, system.fn, world, ...) - end - end) - end - -end - -return { - PHASE = ECS_PHASE, - SYSTEM = ECS_SYSTEM, - RUN = ECS_RUN, - phases = { - Heartbeat = Heartbeat, - PreSimulation = PreSimulation, - PreAnimation = PreAnimation, - PreRender = PreRender - }, - components = { - System = System, - DependsOn = DependsOn, - Phase = Phase, - Event = Event, - } -} diff --git a/demo/src/ReplicatedStorage/systems/receive_replication.luau b/demo/src/ReplicatedStorage/systems/receive_replication.luau deleted file mode 100644 index b4be32f..0000000 --- a/demo/src/ReplicatedStorage/systems/receive_replication.luau +++ /dev/null @@ -1,86 +0,0 @@ -local types = require("../types") -local jecs = require(game:GetService("ReplicatedStorage").ecs) -local remotes = require("../remotes") -local collect = require("../collect") -local client_ids = {} - - -local function ecs_map_get(world, id) - local deserialised_id = client_ids[id] - - if not deserialised_id then - if world:has(id, jecs.Name) then - deserialised_id = world:entity(id) - else - deserialised_id = world:entity() - end - - client_ids[id] = deserialised_id - end - - -- local deserialised_id = client_ids[id] - -- if not deserialised_id then - -- if world:has(id, jecs.Name) then - -- deserialised_id = world:entity(id) - -- else - -- if world:exists(id) then - -- deserialised_id = world:entity() - -- else - -- deserialised_id = world:entity(id) - -- end - -- end - -- client_ids[id] = deserialised_id - -- end - - return deserialised_id -end - -local function ecs_make_alive_id(world, id) - local rel = jecs.ECS_PAIR_FIRST(id) - local tgt = jecs.ECS_PAIR_SECOND(id) - - rel = ecs_map_get(world, rel) - tgt = ecs_map_get(world, tgt) - - return jecs.pair(rel, tgt) -end - -local snapshots = collect(remotes.replication.OnClientEvent) - -return function(world: types.World) - for snapshot in snapshots do - for id, map in snapshot do - id = tonumber(id) - if jecs.IS_PAIR(id) then - id = ecs_make_alive_id(world, id) - end - - local set = map.set - if set then - if jecs.is_tag(world, id) then - for _, entity in set do - entity = ecs_map_get(world, entity) - world:add(entity, id) - end - else - local values = map.values - for i, entity in set do - entity = ecs_map_get(world, entity) - world:set(entity, id, values[i]) - end - end - end - - local removed = map.removed - - if removed then - for i, e in removed do - if not world:contains(e) then - continue - end - world:remove(e, id) - end - end - end - end -end diff --git a/demo/src/ReplicatedStorage/types.luau b/demo/src/ReplicatedStorage/types.luau deleted file mode 100644 index 6971254..0000000 --- a/demo/src/ReplicatedStorage/types.luau +++ /dev/null @@ -1,15 +0,0 @@ -local jecs = require(game:GetService("ReplicatedStorage").ecs) -local observers_add = require("../ReplicatedStorage/observers_add") - -export type World = typeof(observers_add(jecs.world())) -export type Entity = jecs.Entity -export type Id = jecs.Id -export type Snapshot = { - [string]: { - set: { jecs.Entity }?, - values: { any }?, - removed: { jecs.Entity }? - } -} - -return {} diff --git a/demo/src/ServerScriptService/main.server.luau b/demo/src/ServerScriptService/main.server.luau deleted file mode 100644 index aa9f0f5..0000000 --- a/demo/src/ServerScriptService/main.server.luau +++ /dev/null @@ -1,19 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local ServerScriptService = game:GetService("ServerScriptService") -local jecs = require(ReplicatedStorage.ecs) -local schedule = require(ReplicatedStorage.schedule) -local observers_add = require(ReplicatedStorage.observers_add) - -local SYSTEM = schedule.SYSTEM -local RUN = schedule.RUN - -require(ReplicatedStorage.components) -local world = observers_add(jecs.world()) - -local systems = ServerScriptService.systems - -SYSTEM(world, systems.replication) -SYSTEM(world, systems.players_added) -SYSTEM(world, systems.poison_hurts) -SYSTEM(world, systems.life_is_painful) -RUN(world, 0) diff --git a/demo/src/ServerScriptService/systems/life_is_painful.luau b/demo/src/ServerScriptService/systems/life_is_painful.luau deleted file mode 100644 index 333e64b..0000000 --- a/demo/src/ServerScriptService/systems/life_is_painful.luau +++ /dev/null @@ -1,12 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local ct = require(ReplicatedStorage.components) -local types = require(ReplicatedStorage.types) - -return function(world: types.World, dt: number) - for e in world:query(ct.Player):without(ct.Health) do - world:set(e, ct.Health, 100) - end - for e in world:query(ct.Player, ct.Health):without(ct.Poison) do - world:set(e, ct.Poison, 10) - end -end diff --git a/demo/src/ServerScriptService/systems/players_added.luau b/demo/src/ServerScriptService/systems/players_added.luau deleted file mode 100644 index 46b1ab1..0000000 --- a/demo/src/ServerScriptService/systems/players_added.luau +++ /dev/null @@ -1,20 +0,0 @@ -local collect = require("../../ReplicatedStorage/collect") -local types = require("../../ReplicatedStorage/types") -local ct = require("../../ReplicatedStorage/components") -local Players = game:GetService("Players") - -local player_added = collect(Players.PlayerAdded) -return function(world: types.World, dt: number) - for player in player_added do - local entity = world:entity() - world:set(entity, ct.Player, player) - end - - for entity, player in world:query(ct.Player):without(ct.Renderable) do - local character = player.Character - if character then - if not character.Parent then - world:set(entity, ct.Renderable, character) - end - end -end diff --git a/demo/src/ServerScriptService/systems/poison_hurts.luau b/demo/src/ServerScriptService/systems/poison_hurts.luau deleted file mode 100644 index a7e1f3e..0000000 --- a/demo/src/ServerScriptService/systems/poison_hurts.luau +++ /dev/null @@ -1,12 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local ct = require(ReplicatedStorage.components) -return function(world, dt) - for e, poison, health in world:query(ct.Poison, ct.Health) do - local health_after_tick = health - poison * dt * 0.05 - if health_after_tick < 0 then - world:remove(e, ct.Health) - continue - end - world:set(e, ct.Health, health_after_tick) - end -end diff --git a/demo/src/ServerScriptService/systems/replication.luau b/demo/src/ServerScriptService/systems/replication.luau deleted file mode 100644 index 5e5004b..0000000 --- a/demo/src/ServerScriptService/systems/replication.luau +++ /dev/null @@ -1,190 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local types = require("../../ReplicatedStorage/types") -local ct = require("../../ReplicatedStorage/components") -local jecs = require(ReplicatedStorage.ecs) -local remotes = require("../../ReplicatedStorage/remotes") -local components = ct :: {[string]: jecs.Entity } - -return function(world: ty.World) - - --- integration test - - -- for _ = 1, 10 do - -- local e = world:entity() - -- world:set(e, ct.TestA, true) - -- end - - local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }} - local networked_components = {} - local networked_pairs = {} - - for component in world:each(ct.Networked) do - local name = world:get(component, jecs.Name) :: string - if components[name] == nil then - continue - end - - storages[component] = {} - - table.insert(networked_components, component) - end - - for relation in world:each(ct.NetworkedPair) do - local name = world:get(relation, jecs.Name) :: string - if not components[name] then - continue - end - table.insert(networked_pairs, relation) - end - - for _, component in networked_components do - local name = world:get(component, jecs.Name) :: string - if not components[name] then - error(`Networked Component (%id{component}%name{name})`) - end - local is_tag = jecs.is_tag(world, component) - local storage = storages[component] - if is_tag then - world:added(component, function(entity) - storage[entity] = true - end) - else - world:added(component, function(entity, _, value) - storage[entity] = value - end) - world:changed(component, function(entity, _, value) - storage[entity] = value - end) - end - - world:removed(component, function(entity) - storage[entity] = "jecs.Remove" - end) - end - - for _, relation in networked_pairs do - world:added(relation, function(entity, id, value) - local is_tag = jecs.is_tag(world, id) - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - if is_tag then - storage[entity] = true - else - storage[entity] = value - end - end) - - world:changed(relation, function(entity, id, value) - local is_tag = jecs.is_tag(world, id) - if is_tag then - return - end - - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - - storage[entity] = value - end) - - world:removed(relation, function(entity, id) - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - - storage[entity] = "jecs.Remove" - end) - end - - local players_added = collect(Players.PlayerAdded) - - return function() - local snapshot_lazy: ty.Snapshot - local set_ids_lazy: { jecs.Entity } - - for player in players_added do - if not snapshot_lazy then - snapshot_lazy, set_ids_lazy = {}, {} - - for component, storage in storages do - local set_values = {} - local set_n = 0 - - local q = world:query(component) - local is_tag = jecs.is_tag(world, component) - for _, archetype in q:archetypes() do - local entities = archetype.entities - local entities_len = #entities - table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy) - if is_tag then - set_values = table.create(entities_len, true) - else - local column = archetype.columns[archetype.records[component]] - table.move(column, 1, entities_len, set_n + 1, set_values) - end - - set_n += entities_len - end - - local set = table.move(set_ids_lazy, 1, set_n, 1, {}) - - snapshot_lazy[tostring(component)] = { - set = if set_n > 0 then set else nil, - values = if set_n > 0 then set_values else nil, - } - end - end - - remotes.replication:FireClient(player, snapshot_lazy) - end - - local snapshot = {} :: ty.Snapshot - - local set_ids = {} - local removed_ids = {} - - for component, storage in storages do - local set_values = {} :: { any } - local set_n = 0 - local removed_n = 0 - for e, v in storage do - if v ~= "jecs.Remove" then - set_n += 1 - set_ids[set_n] = e - set_values[set_n] = v or true - elseif not world:contains(e) then - removed_n += 1 - removed_ids[removed_n] = e - end - end - - table.clear(storage) - - local dirty = false - - if set_n > 0 or removed_n > 0 then - dirty = true - end - - if dirty then - local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity } - local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity } - snapshot[tostring(component)] = { - set = if set_n > 0 then set else nil, - values = if set_n > 0 then set_values else nil, - removed = if removed_n > 0 then removed else nil - } - end - end - if next(snapshot) ~= nil then - remotes.replication:FireAllClients(snapshot) - end - end -end diff --git a/demo/wally.toml b/demo/wally.toml deleted file mode 100644 index a663a77..0000000 --- a/demo/wally.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "marcus/demo" -version = "0.1.0" -registry = "https://github.com/UpliftGames/wally-index" -realm = "shared" - -[dependencies] -jabby = "alicesaidhi/jabby@0.2.2" diff --git a/jecs.d.ts b/jecs.d.ts deleted file mode 100644 index 4ffb93b..0000000 --- a/jecs.d.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * A unique identifier in the world, entity. - * The generic type T defines the data type when this entity is used as a component - */ -export type Entity = number & { - readonly __nominal_Entity: unique symbol; - readonly __type_TData: TData; -}; - -/** - * An entity with no associated data when used as a component - */ -export type Tag = Entity; - -/** - * A pair of entities: - * - `pred` is the type of the "predicate" entity. - * - `obj` is the type of the "object" entity. - */ -export type Pair

= number & { - readonly __nominal_Pair: unique symbol; - readonly __pred: P; - readonly __obj: O; -}; -/** - * An `Id` can be either a single Entity or a Pair of Entities. - * By providing `TData`, you can specifically require an Id that yields that type. - */ -export type Id = Entity | Pair | Pair; - -export type InferComponent = E extends Entity - ? D - : E extends Pair - ? P extends undefined - ? O - : P - : never; - -type FlattenTuple = T extends [infer U] ? U : LuaTuple; -type Nullable = { [K in keyof T]: T[K] | undefined }; -type InferComponents = { [K in keyof A]: InferComponent }; - -type ArchetypeId = number; -type Column = unknown[]; - -export type Archetype = { - id: number; - types: number[]; - type: string; - entities: number[]; - columns: Column[]; - records: number[]; - counts: number[]; -}; - -type Iter = IterableFunction>; - -export type CachedQuery = { - /** - * Returns an iterator that produces a tuple of [Entity, ...queriedComponents]. - */ - iter(): Iter; - - /** - * Returns the matched archetypes of the query - * @returns An array of archetypes of the query - */ - archetypes(): Archetype[]; -} & Iter; - -export type Query = { - /** - * Returns an iterator that produces a tuple of [Entity, ...queriedComponents]. - */ - iter(): Iter; - - /** - * Creates and returns a cached version of this query for efficient reuse. - * Call refinement methods (with/without) on the query before caching. - * @returns A cached query - */ - cached(): CachedQuery; - - /** - * Modifies the query to include specified components. - * @param components The components to include. - * @returns A new Query with the inclusion applied. - */ - with(...components: Id[]): Query; - - /** - * Modifies the Query to exclude specified components. - * @param components The components to exclude. - * @returns A new Query with the exclusion applied. - */ - without(...components: Id[]): Query; - - /** - * Returns the matched archetypes of the query - * @returns An array of archetypes of the query - */ - archetypes(): Archetype[]; -} & Iter; - -export class World { - /** - * Creates a new World. - */ - constructor(); - - /** - * Enforces a check for entities to be created within a desired range. - * @param range_begin The starting point - * @param range_end The end point (optional) - */ - range(range_begin: number, range_end?: number): void; - - /** - * Creates a new entity. - * @returns An entity (Tag) with no data. - */ - entity(): Tag; - entity(id: T): InferComponent extends undefined ? Tag : T; - - /** - * Creates a new entity in the first 256 IDs, typically used for static - * components that need fast access. - * @returns A typed Entity with `TData`. - */ - component(): Entity; - - /** - * Gets the target of a relationship. For example, if we say - * `world.target(entity, ChildOf)`, this returns the parent entity. - * @param entity The entity using a relationship pair. - * @param relation The "relationship" component/tag (e.g., ChildOf). - * @param index If multiple targets exist, specify an index. Defaults to 0. - */ - target(entity: Entity, relation: Entity, index?: number): Entity | undefined; - - /** - * Deletes an entity (and its components/relationships) from the world entirely. - * @param entity The entity to delete. - */ - delete(entity: Entity): void; - - /** - * Adds a component (with no value) to the entity. - * @param entity The target entity. - * @param component The component (or tag) to add. - */ - add(entity: Entity, component: undefined extends InferComponent ? C : Id): void; - - /** - * Installs a hook on the given component. - * @param component The target component. - * @param hook The hook to install. - * @param value The hook callback. - */ - set(component: Entity, hook: StatefulHook, value: (e: Entity, id: Id, data: T) => void): void; - set(component: Entity, hook: StatelessHook, value: (e: Entity, id: Id) => void): void; - - /** - * Assigns a value to a component on the given entity. - * @param entity The target entity. - * @param component The component definition (could be a Pair or Entity). - * @param value The value to store with that component. - */ - set>(entity: Entity, component: E, value: InferComponent): void; - - /** - * Cleans up the world by removing empty archetypes and rebuilding the archetype collections. - * This helps maintain memory efficiency by removing unused archetype definitions. - */ - cleanup(): void; - - /** - * Clears all components and relationships from the given entity, but - * does not delete the entity from the world. - * @param entity The entity to clear. - */ - clear(entity: Entity): void; - - /** - * Removes a component from the given entity. - * @param entity The target entity. - * @param component The component to remove. - */ - remove(entity: Entity, component: Id): void; - - /** - * Retrieves the values of up to 4 components on a given entity. Missing - * components will return `undefined`. - * @param entity The entity to query. - * @param components Up to 4 components/tags to retrieve. - * @returns A tuple of data (or a single value), each possibly undefined. - */ - get( - entity: Entity, - ...components: T - ): FlattenTuple>>; - - /** - * Returns `true` if the given entity has all of the specified components. - * A maximum of 4 components can be checked at once. - * @param entity The entity to check. - * @param components Upto 4 components to check for. - */ - has(entity: Entity, ...components: Id[]): boolean; - - /** - * Gets the parent (the target of a `ChildOf` relationship) for an entity, - * if such a relationship exists. - * @param entity The entity whose parent is queried. - */ - parent(entity: Entity): Entity | undefined; - - /** - * Checks if an entity exists in the world. - * @param entity The entity to verify. - */ - contains(entity: Entity): boolean; - - /** - * Checks if an entity with the given ID is currently alive, ignoring its generation. - * @param entity The entity to verify. - * @returns boolean true if any entity with the given ID exists (ignoring generation), false otherwise - */ - exists(entity: Entity): boolean; - - /** - * Returns an iterator that yields all entities that have the specified component or relationship. - * @param id The component or relationship ID to search for - * @returns An iterator function that yields entities - */ - each(id: Id): IterableFunction; - - /** - * Returns an iterator that yields all child entities of the specified parent entity. - * Uses the ChildOf relationship internally. - * @param parent The parent entity to get children for - * @returns An iterator function that yields child entities - */ - children(parent: Entity): IterableFunction; - - /** - * Searches the world for entities that match specified components. - * @param components The list of components to query. - * @returns A Query object to iterate over results. - */ - query(...components: T): Query>; -} - -export function component(): Entity; - -export function tag(): Tag; - -// note: original types had id: Entity, id: Id, which does not work with TS. -export function meta(e: Entity, id: Id, value?: T): Entity; - -export function is_tag(world: World, id: Id): boolean; - -/** - * Creates a composite key (pair) - * @param pred The first entity (predicate) - * @param obj The second entity (object) - * @returns The composite key (pair) - */ -export function pair(pred: Entity

, obj: Entity): Pair; - -/** - * Checks if the entity is a composite key (pair) - * @param value The entity to check - * @returns If the entity is a pair - */ -export function IS_PAIR(value: Id): value is Pair; - -/** - * Gets the first entity (predicate) of a pair - * @param pair The pair to get the first entity from - * @returns The first entity (predicate) of the pair - */ -export function pair_first(world: World, p: Pair): Entity

; - -/** - * Gets the second entity (object) of a pair - * @param pair The pair to get the second entity from - * @returns The second entity (object) of the pair - */ -export function pair_second(world: World, p: Pair): Entity; - -type StatefulHook = Entity<(e: Entity, id: Id, data: T) => void> & { - readonly __nominal_StatefulHook: unique symbol, -} -type StatelessHook = Entity<(e: Entity, id: Id) => void> & { - readonly __nominal_StatelessHook: unique symbol, -} - -export declare const OnAdd: StatefulHook; -export declare const OnRemove: StatelessHook; -export declare const OnChange: StatefulHook; -export declare const ChildOf: Tag; -export declare const Wildcard: Entity; -export declare const w: Entity; -export declare const OnDelete: Tag; -export declare const OnDeleteTarget: Tag; -export declare const Delete: Tag; -export declare const Remove: Tag; -export declare const Name: Entity; -export declare const Rest: Entity; diff --git a/jecs.luau b/jecs.lua similarity index 58% rename from jecs.luau rename to jecs.lua index 0033e34..388a714 100644 --- a/jecs.luau +++ b/jecs.lua @@ -1,2717 +1,2435 @@ - ---!optimize 2 ---!native ---!strict ---draft 4 - -type i53 = number -type i24 = number - -type Ty = { i53 } -type ArchetypeId = number - -type Column = { any } - -type Map = { [K]: V } - -type ecs_archetype_t = { - id: number, - types: Ty, - type: string, - entities: { number }, - columns: { Column }, - records: { [i53]: number }, - counts: { [i53]: number }, -} - -export type Archetype = { - id: number, - types: Ty, - type: string, - entities: { number }, - columns: { Column }, - records: { [Id]: number }, - counts: { [Id]: number }, -} - -type ecs_record_t = { - archetype: ecs_archetype_t, - row: number, - dense: i24, -} - -type ecs_id_record_t = { - cache: { number }, - counts: { number }, - flags: number, - size: number, - hooks: { - on_add: ((entity: i53, id: i53, data: any?) -> ())?, - on_change: ((entity: i53, id: i53, data: any) -> ())?, - on_remove: ((entity: i53, id: i53) -> ())?, - }, -} - -type ecs_id_index_t = Map - -type ecs_archetypes_map_t = { [string]: ecs_archetype_t } - -type ecs_archetypes_t = { ecs_archetype_t } - -type ecs_entity_index_t = { - dense_array: Map, - sparse_array: Map, - alive_count: number, - max_id: number, - range_begin: number?, - range_end: number? -} - -type ecs_query_data_t = { - compatible_archetypes: { ecs_archetype_t }, - ids: { i53 }, - filter_with: { i53 }, - filter_without: { i53 }, - next: () -> (number, ...any), - world: ecs_world_t, -} - -type ecs_observer_t = { - callback: (archetype: ecs_archetype_t) -> (), - query: ecs_query_data_t, -} - -type ecs_observable_t = Map> - -type ecs_world_t = { - archetype_edges: Map>, - entity_index: ecs_entity_index_t, - component_index: ecs_id_index_t, - archetypes: ecs_archetypes_t, - archetype_index: ecs_archetypes_map_t, - max_archetype_id: number, - max_component_id: number, - ROOT_ARCHETYPE: ecs_archetype_t, - observable: Map>, -} - --- stylua: ignore start - -local ECS_ENTITY_MASK = bit32.lshift(1, 24) -local ECS_GENERATION_MASK = bit32.lshift(1, 16) -local ECS_PAIR_OFFSET = 2^48 - -local ECS_ID_DELETE = 0b01 -local ECS_ID_IS_TAG = 0b10 -local ECS_ID_MASK = 0b00 - -local HI_COMPONENT_ID = 256 -local EcsOnAdd = HI_COMPONENT_ID + 1 -local EcsOnRemove = HI_COMPONENT_ID + 2 -local EcsOnChange = HI_COMPONENT_ID + 3 -local EcsWildcard = HI_COMPONENT_ID + 4 -local EcsChildOf = HI_COMPONENT_ID + 5 -local EcsComponent = HI_COMPONENT_ID + 6 -local EcsOnDelete = HI_COMPONENT_ID + 7 -local EcsOnDeleteTarget = HI_COMPONENT_ID + 8 -local EcsDelete = HI_COMPONENT_ID + 9 -local EcsRemove = HI_COMPONENT_ID + 10 -local EcsName = HI_COMPONENT_ID + 11 -local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12 -local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13 -local EcsRest = HI_COMPONENT_ID + 14 - -local NULL_ARRAY = table.freeze({}) :: Column -local NULL = newproxy(false) - -local ECS_INTERNAL_ERROR = [[ - This is an internal error, please file a bug report via the following link: - - https://github.com/Ukendio/jecs/issues/new?template=BUG-REPORT.md -]] - -local function ecs_assert(condition, msg: string?) - if not condition then - error(msg) - end -end - -local ecs_metadata: Map> = {} -local ecs_max_component_id = 0 -local ecs_max_tag_id = EcsRest - -local function ECS_COMPONENT() - ecs_max_component_id += 1 - if ecs_max_component_id > HI_COMPONENT_ID then - error("Too many components") - end - return ecs_max_component_id -end - -local function ECS_TAG() - ecs_max_tag_id += 1 - return ecs_max_tag_id -end - -local function ECS_META(id: i53, ty: i53, value: any?) - local bundle = ecs_metadata[id] - if bundle == nil then - bundle = {} - ecs_metadata[id] = bundle - end - bundle[ty] = if value == nil then NULL else value -end - -local function ECS_META_RESET() - ecs_metadata = {} - ecs_max_component_id = 0 - ecs_max_tag_id = EcsRest -end - -local function ECS_COMBINE(id: number, generation: number): i53 - return id + (generation * ECS_ENTITY_MASK) -end - -local function ECS_IS_PAIR(e: number): boolean - return e > ECS_PAIR_OFFSET -end - -local function ECS_GENERATION_INC(e: i53): i53 - if e > ECS_ENTITY_MASK then - local id = e % ECS_ENTITY_MASK - local generation = e // ECS_ENTITY_MASK - - local next_gen = generation + 1 - if next_gen >= ECS_GENERATION_MASK then - return id - end - - return ECS_COMBINE(id, next_gen) - end - return ECS_COMBINE(e, 1) -end - -local function ECS_ENTITY_T_LO(e: i53): i24 - return e % ECS_ENTITY_MASK -end - -local function ECS_ID(e: i53) - return e % ECS_ENTITY_MASK -end - -local function ECS_GENERATION(e: i53) - return e // ECS_ENTITY_MASK -end - -local function ECS_ENTITY_T_HI(e: i53): i24 - return e // ECS_ENTITY_MASK -end - -local function ECS_PAIR(pred: i53, obj: i53): i53 - pred %= ECS_ENTITY_MASK - obj %= ECS_ENTITY_MASK - - return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET -end - -local function ECS_PAIR_FIRST(e: i53): i24 - return (e - ECS_PAIR_OFFSET) // ECS_ENTITY_MASK -end - -local function ECS_PAIR_SECOND(e: i53): i24 - return (e - ECS_PAIR_OFFSET) % ECS_ENTITY_MASK -end - -local function entity_index_try_get_any( - entity_index: ecs_entity_index_t, - entity: number -): ecs_record_t? - local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] - - if not r or r.dense == 0 then - return nil - end - - return r -end - -local function entity_index_try_get(entity_index: ecs_entity_index_t, entity: number): ecs_record_t? - local r = entity_index_try_get_any(entity_index, entity) - if r then - local r_dense = r.dense - if r_dense > entity_index.alive_count then - return nil - end - if entity_index.dense_array[r_dense] ~= entity then - return nil - end - end - return r -end - -local function entity_index_try_get_fast(entity_index: ecs_entity_index_t, entity: number): ecs_record_t? - local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] - if r then - if entity_index.dense_array[r.dense] ~= entity then - return nil - end - end - return r -end - -local function entity_index_is_alive(entity_index: ecs_entity_index_t, entity: i53) - return entity_index_try_get(entity_index, entity) ~= nil -end - -local function entity_index_get_alive(entity_index: ecs_entity_index_t, entity: i53): i53? - local r = entity_index_try_get_any(entity_index, entity) - if r then - return entity_index.dense_array[r.dense] - end - return nil -end - -local function ecs_get_alive(world, entity) - if entity == 0 then - return 0 - end - - local eindex = world.entity_index - - if entity_index_is_alive(eindex, entity) then - return entity - end - - if entity > ECS_ENTITY_MASK then - return 0 - end - - local current = entity_index_get_alive(eindex, entity) - if not current or not entity_index_is_alive(eindex, current) then - return 0 - end - - return current -end - -local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range" - -local function entity_index_new_id(entity_index: ecs_entity_index_t): i53 - local dense_array = entity_index.dense_array - local alive_count = entity_index.alive_count - local sparse_array = entity_index.sparse_array - local max_id = entity_index.max_id - - if alive_count < max_id then - alive_count += 1 - entity_index.alive_count = alive_count - local id = 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 - dense_array[alive_count] = id - sparse_array[id] = { dense = alive_count } :: ecs_record_t - - return id -end - -local function ecs_pair_first(world: ecs_world_t, e: i53) - local pred = ECS_PAIR_FIRST(e) - return ecs_get_alive(world, pred) -end - -local function ecs_pair_second(world: ecs_world_t, e: i53) - local obj = ECS_PAIR_SECOND(e) - return ecs_get_alive(world, obj) -end - -local function query_match(query: ecs_query_data_t, - archetype: ecs_archetype_t) - local records = archetype.records - local with = query.filter_with - - for _, id in with do - if not records[id] then - return false - end - end - - local without = query.filter_without - if without then - for _, id in without do - if records[id] then - return false - end - end - end - - return true -end - -local function find_observers(world: ecs_world_t, event: i53, - component: i53): { ecs_observer_t }? - local cache = world.observable[event] - if not cache then - return nil - end - return cache[component] :: any -end - -local function archetype_move( - entity_index: ecs_entity_index_t, - to: ecs_archetype_t, - dst_row: i24, - from: ecs_archetype_t, - src_row: i24 -) - local src_columns = from.columns - local dst_columns = to.columns - local dst_entities = to.entities - local src_entities = from.entities - - local last = #src_entities - local id_types = from.types - local records = to.records - - for i, column in src_columns do - if column == NULL_ARRAY then - continue - end - -- Retrieves the new column index from the source archetype's record from each component - -- We have to do this because the columns are tightly packed and indexes may not correspond to each other. - local tr = records[id_types[i]] - - -- Sometimes target column may not exist, e.g. when you remove a component. - if tr then - dst_columns[tr][dst_row] = column[src_row] - end - - -- If the entity is the last row in the archetype then swapping it would be meaningless. - if src_row ~= last then - -- Swap rempves columns to ensure there are no holes in the archetype. - column[src_row] = column[last] - end - column[last] = nil - end - - local moved = #src_entities - - -- Move the entity from the source to the destination archetype. - -- Because we have swapped columns we now have to update the records - -- corresponding to the entities' rows that were swapped. - local e1 = src_entities[src_row] - local e2 = src_entities[moved] - - if src_row ~= moved then - src_entities[src_row] = e2 - end - - src_entities[moved] = nil :: any - dst_entities[dst_row] = e1 - - local sparse_array = entity_index.sparse_array - - local record1 = sparse_array[ECS_ENTITY_T_LO(e1)] - local record2 = sparse_array[ECS_ENTITY_T_LO(e2)] - record1.row = dst_row - record2.row = src_row -end - -local function archetype_append( - entity: i53, - archetype: ecs_archetype_t -): number - local entities = archetype.entities - local length = #entities + 1 - entities[length] = entity - return length -end - -local function new_entity( - entity: i53, - record: ecs_record_t, - archetype: ecs_archetype_t -): ecs_record_t - local row = archetype_append(entity, archetype) - record.archetype = archetype - record.row = row - return record -end - -local function entity_move( - entity_index: ecs_entity_index_t, - entity: i53, - record: ecs_record_t, - to: ecs_archetype_t -) - local sourceRow = record.row - local from = record.archetype - local dst_row = archetype_append(entity, to) - archetype_move(entity_index, to, dst_row, from, sourceRow) - record.archetype = to - record.row = dst_row -end - -local function hash(arr: { number }): string - return table.concat(arr, "_") -end - -local function fetch(id: i53, records: { number }, - columns: { Column }, row: number): any - local tr = records[id] - - if not tr then - return nil - end - - return columns[tr][row] -end - -local function world_get(world: ecs_world_t, entity: i53, - a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any - local record = entity_index_try_get_fast(world.entity_index, entity) - if not record then - return nil - end - - local archetype = record.archetype - if not archetype then - return nil - end - - local records = archetype.records - local columns = archetype.columns - local row = record.row - - local va = fetch(a, records, columns, row) - - if not b then - return va - elseif not c then - return va, fetch(b, records, columns, row) - elseif not d then - return va, fetch(b, records, columns, row), fetch(c, records, columns, row) - elseif not e then - return va, fetch(b, records, columns, row), fetch(c, records, columns, row), fetch(d, records, columns, row) - else - error("args exceeded") - end -end - -local function world_has_one_inline(world: ecs_world_t, entity: i53, id: i53): boolean - local record = entity_index_try_get_fast(world.entity_index, entity) - if not record then - return false - end - - local archetype = record.archetype - if not archetype then - return false - end - - local records = archetype.records - - return records[id] ~= nil -end - -local function ecs_is_tag(world: ecs_world_t, entity: i53): boolean - local idr = world.component_index[entity] - if idr then - return bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0 - end - return not world_has_one_inline(world, entity, EcsComponent) -end - -local function world_has(world: ecs_world_t, entity: i53, - a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean - - local record = entity_index_try_get_fast(world.entity_index, entity) - if not record then - return false - end - - local archetype = record.archetype - if not archetype then - return false - end - - local records = archetype.records - - return records[a] ~= nil and - (b == nil or records[b] ~= nil) and - (c == nil or records[c] ~= nil) and - (d == nil or records[d] ~= nil) and - (e == nil or error("args exceeded")) -end - -local function world_target(world: ecs_world_t, entity: i53, relation: i24, index: number?): i24? - local nth = index or 0 - local record = entity_index_try_get_fast(world.entity_index, entity) - if not record then - return nil - end - - local archetype = record.archetype - if not archetype then - return nil - end - - local r = ECS_PAIR(relation, EcsWildcard) - - local count = archetype.counts[r] - if not count then - return nil - end - - if nth >= count then - nth = nth + count + 1 - end - - nth = archetype.types[nth + archetype.records[r]] - if not nth then - return nil - end - - return entity_index_get_alive(world.entity_index, - ECS_PAIR_SECOND(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 - -local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t - local component_index = world.component_index - local entity_index = world.entity_index - local idr: ecs_id_record_t? = component_index[id] - - if idr then - return idr - end - - local flags = ECS_ID_MASK - local relation = id - local target = 0 - local is_pair = ECS_IS_PAIR(id) - if is_pair then - relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) :: i53 - ecs_assert(relation and entity_index_is_alive( - entity_index, relation), ECS_INTERNAL_ERROR) - target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) :: i53 - ecs_assert(target and entity_index_is_alive( - entity_index, target), ECS_INTERNAL_ERROR) - end - - local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) - local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0) - - local has_delete = false - - if cleanup_policy == EcsDelete or cleanup_policy_target == EcsDelete then - has_delete = true - end - - local on_add, on_change, on_remove = world_get(world, - relation, EcsOnAdd, EcsOnChange, EcsOnRemove) - - local is_tag = not world_has_one_inline(world, - relation, EcsComponent) - - if is_tag and is_pair then - is_tag = not world_has_one_inline(world, target, EcsComponent) - end - - flags = bit32.bor( - flags, - if has_delete then ECS_ID_DELETE else 0, - if is_tag then ECS_ID_IS_TAG else 0 - ) - - idr = { - size = 0, - cache = {}, - counts = {}, - flags = flags, - hooks = { - on_add = on_add, - on_change = on_change, - on_remove = on_remove, - }, - } :: ecs_id_record_t - - component_index[id] = idr - - return idr -end - -local function archetype_append_to_records( - idr: ecs_id_record_t, - archetype: ecs_archetype_t, - id: i53, - index: number -) - local archetype_id = archetype.id - local archetype_records = archetype.records - local archetype_counts = archetype.counts - local idr_columns = idr.cache - local idr_counts = idr.counts - local tr = idr_columns[archetype_id] - if not tr then - idr_columns[archetype_id] = index - idr_counts[archetype_id] = 1 - - archetype_records[id] = index - archetype_counts[id] = 1 - else - local max_count = idr_counts[archetype_id] + 1 - idr_counts[archetype_id] = max_count - archetype_counts[id] = max_count - end -end - -local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev: i53?): ecs_archetype_t - local archetype_id = (world.max_archetype_id :: number) + 1 - world.max_archetype_id = archetype_id - - local length = #id_types - local columns = (table.create(length) :: any) :: { Column } - - local records: { number } = {} - local counts: { number } = {} - - local archetype: ecs_archetype_t = { - columns = columns, - entities = {}, - id = archetype_id, - records = records, - counts = counts, - type = ty, - types = id_types, - } - - for i, component_id in id_types do - local idr = id_record_ensure(world, component_id) - archetype_append_to_records(idr, archetype, component_id, i) - - if ECS_IS_PAIR(component_id) then - local relation = ECS_PAIR_FIRST(component_id) - local object = ECS_PAIR_SECOND(component_id) - local r = ECS_PAIR(relation, EcsWildcard) - local idr_r = id_record_ensure(world, r) - archetype_append_to_records(idr_r, archetype, r, i) - - local t = ECS_PAIR(EcsWildcard, object) - local idr_t = id_record_ensure(world, t) - archetype_append_to_records(idr_t, archetype, t, i) - end - - if bit32.band(idr.flags, ECS_ID_IS_TAG) == 0 then - columns[i] = {} - else - columns[i] = NULL_ARRAY - end - end - - for id in records do - local observer_list = find_observers(world, EcsOnArchetypeCreate, id) - if not observer_list then - continue - end - for _, observer in observer_list do - if query_match(observer.query, archetype) then - observer.callback(archetype) - end - end - end - - world.archetype_index[ty] = archetype - world.archetypes[archetype_id] = archetype - world.archetype_edges[archetype.id] = {} :: Map - - return archetype -end - -local function world_range(world: ecs_world_t, range_begin: number, range_end: number?) - local entity_index = world.entity_index - - entity_index.range_begin = range_begin - entity_index.range_end = range_end - - local max_id = entity_index.max_id - - if range_begin > max_id then - local dense_array = entity_index.dense_array - local sparse_array = entity_index.sparse_array - - for i = max_id + 1, range_begin do - dense_array[i] = i - sparse_array[i] = { - dense = 0 - } :: ecs_record_t - end - entity_index.max_id = range_begin - 1 - entity_index.alive_count = range_begin - 1 - end -end - -local function world_entity(world: ecs_world_t, entity: i53?): i53 - local entity_index = world.entity_index - if entity then - local index = ECS_ID(entity) - local max_id = entity_index.max_id - local sparse_array = entity_index.sparse_array - local dense_array = entity_index.dense_array - local alive_count = entity_index.alive_count - local r = sparse_array[index] - if r then - local dense = r.dense - - if not dense or r.dense == 0 then - r.dense = index - dense = index - end - - local any = dense_array[dense] - if dense <= alive_count then - if any ~= entity then - error("Entity ID is already in use with a different generation") - else - return entity - end - end - - local e_swap = dense_array[dense] - local r_swap = entity_index_try_get_any(entity_index, e_swap) :: ecs_record_t - alive_count += 1 - entity_index.alive_count = alive_count - r_swap.dense = dense - r.dense = alive_count - dense_array[dense] = e_swap - dense_array[alive_count] = entity - - return entity - else - for i = max_id + 1, index do - sparse_array[i] = { dense = i } :: ecs_record_t - dense_array[i] = i - end - entity_index.max_id = index - - local e_swap = dense_array[alive_count] - local r_swap = sparse_array[alive_count] - r_swap.dense = index - - alive_count += 1 - entity_index.alive_count = alive_count - - r = sparse_array[index] - - r.dense = alive_count - - sparse_array[index] = r - - dense_array[index] = e_swap - dense_array[alive_count] = entity - - - return entity - end - end - return entity_index_new_id(entity_index) -end - -local function world_parent(world: ecs_world_t, entity: i53) - return world_target(world, entity, EcsChildOf, 0) -end - -local function archetype_ensure(world: ecs_world_t, id_types): ecs_archetype_t - if #id_types < 1 then - return world.ROOT_ARCHETYPE - end - - local ty = hash(id_types) - local archetype = world.archetype_index[ty] - if archetype then - return archetype - end - - return archetype_create(world, id_types, ty) -end - -local function find_insert(id_types: { i53 }, toAdd: i53): number - for i, id in id_types do - if id == toAdd then - error("Duplicate component id") - return -1 - end - if id > toAdd then - return i - end - end - return #id_types + 1 -end - -local function find_archetype_without( - world: ecs_world_t, - node: ecs_archetype_t, - id: i53 -): ecs_archetype_t - local id_types = node.types - local at = table.find(id_types, id) - - local dst = table.clone(id_types) - table.remove(dst, at) - - return archetype_ensure(world, dst) -end - - -local function create_edge_for_remove( - world: ecs_world_t, - node: ecs_archetype_t, - edge: Map, - id: i53 -): ecs_archetype_t - local to = find_archetype_without(world, node, id) - local edges = world.archetype_edges - local archetype_id = node.id - edges[archetype_id][id] = to - edges[to.id][id] = node - return to -end - -local function archetype_traverse_remove( - world: ecs_world_t, - id: i53, - from: ecs_archetype_t -): ecs_archetype_t - local edges = world.archetype_edges - local edge = edges[from.id] - - local to: ecs_archetype_t = edge[id] - if to == nil then - to = find_archetype_without(world, from, id) - edge[id] = to - edges[to.id][id] = from - end - - return to -end - -local function find_archetype_with(world, id, from): ecs_archetype_t - local id_types = from.types - - local at = find_insert(id_types, id) - local dst = table.clone(id_types) :: { i53 } - table.insert(dst, at, id) - - return archetype_ensure(world, dst) -end - -local function archetype_traverse_add(world, id, from: ecs_archetype_t): ecs_archetype_t - from = from or world.ROOT_ARCHETYPE - if from.records[id] then - return from - end - local edges = world.archetype_edges - local edge = edges[from.id] - - local to = edge[id] - if not to then - to = find_archetype_with(world, id, from) - edge[id] = to - edges[to.id][id] = from - end - - return to -end - -local function world_add( - world: ecs_world_t, - entity: i53, - id: i53 -): () - local entity_index = world.entity_index - local record = entity_index_try_get_fast(entity_index, entity) - if not record then - return - end - - local from = record.archetype - local to = archetype_traverse_add(world, id, from) - if from == to then - return - end - if from then - entity_move(entity_index, entity, record, to) - else - if #to.types > 0 then - new_entity(entity, record, to) - end - end - - local idr = world.component_index[id] - local on_add = idr.hooks.on_add - - if on_add then - on_add(entity, id) - end -end - -local function world_set(world: ecs_world_t, entity: i53, id: i53, data: unknown): () - local entity_index = world.entity_index - local record = entity_index_try_get_fast(entity_index, entity) - if not record then - return - end - - local from: ecs_archetype_t = record.archetype - local to: ecs_archetype_t = archetype_traverse_add(world, id, from) - local idr = world.component_index[id] - local idr_hooks = idr.hooks - - if from == to then - local tr = (to :: ecs_archetype_t).records[id] - local column = from.columns[tr] - column[record.row] = data - - -- If the archetypes are the same it can avoid moving the entity - -- and just set the data directly. - local on_change = idr_hooks.on_change - if on_change then - on_change(entity, id, data) - end - - return - end - - if from then - -- If there was a previous archetype, then the entity needs to move the archetype - entity_move(entity_index, entity, record, to) - else - if #to.types > 0 then - -- When there is no previous archetype it should create the archetype - new_entity(entity, record, to) - end - end - - local tr = to.records[id] - local column = to.columns[tr] - - column[record.row] = data - - local on_add = idr_hooks.on_add - if on_add then - on_add(entity, id, data) - end -end - -local function world_component(world: World): i53 - local id = (world.max_component_id :: number) + 1 - if id > HI_COMPONENT_ID then - -- IDs are partitioned into ranges because component IDs are not nominal, - -- so it needs to error when IDs intersect into the entity range. - error("Too many components, consider using world:entity() instead to create components.") - end - world.max_component_id = id - - return id -end - -local function world_remove(world: ecs_world_t, entity: i53, id: i53) - local entity_index = world.entity_index - local record = entity_index_try_get_fast(entity_index, entity) - if not record then - return - end - local from = record.archetype - - if not from then - return - end - - if from.records[id] then - local idr = world.component_index[id] - local on_remove = idr.hooks.on_remove - if on_remove then - on_remove(entity, id) - end - - local to = archetype_traverse_remove(world, id, record.archetype) - - entity_move(entity_index, entity, record, to) - end -end - -local function archetype_fast_delete_last(columns: { Column }, column_count: number) - for i, column in columns do - if column ~= NULL_ARRAY then - column[column_count] = nil - end - end -end - -local function archetype_fast_delete(columns: { Column }, column_count: number, row: number) - for i, column in columns do - if column ~= NULL_ARRAY then - column[row] = column[column_count] - column[column_count] = nil - end - end -end - -local function archetype_delete(world: ecs_world_t, archetype: ecs_archetype_t, row: number) - local entity_index = world.entity_index - local component_index = world.component_index - local columns = archetype.columns - local id_types = archetype.types - local entities = archetype.entities - local column_count = #entities - local last = #entities - local move = entities[last] - -- We assume first that the entity is the last in the archetype - local delete = move - - if row ~= last then - local record_to_move = entity_index_try_get_any(entity_index, move) - if record_to_move then - record_to_move.row = row - end - - delete = entities[row] - entities[row] = move - end - - for _, id in id_types do - local idr = component_index[id] - local on_remove = idr.hooks.on_remove - if on_remove then - on_remove(delete, id) - end - end - - entities[last] = nil :: any - - if row == last then - archetype_fast_delete_last(columns, column_count) - else - archetype_fast_delete(columns, column_count, row) - end -end - -local function world_clear(world: ecs_world_t, entity: i53) - local entity_index = world.entity_index - local component_index = world.component_index - local archetypes = world.archetypes - local tgt = ECS_PAIR(EcsWildcard, entity) - local idr_t = component_index[tgt] - local idr = component_index[entity] - local rel = ECS_PAIR(entity, EcsWildcard) - local idr_r = component_index[rel] - - if idr then - local count = 0 - local queue = {} - for archetype_id in idr.cache do - local idr_archetype = archetypes[archetype_id] - local entities = idr_archetype.entities - local n = #entities - count += n - table.move(entities, 1, n, #queue + 1, queue) - end - for _, e in queue do - world_remove(world, e, entity) - end - end - - if idr_t then - local queue: { i53 } - local ids: Map - - local count = 0 - local archetype_ids = idr_t.cache - for archetype_id in archetype_ids do - local idr_t_archetype = archetypes[archetype_id] - local idr_t_types = idr_t_archetype.types - local entities = idr_t_archetype.entities - local removal_queued = false - - for _, id in idr_t_types do - if not ECS_IS_PAIR(id) then - continue - end - local object = entity_index_get_alive( - entity_index, ECS_PAIR_SECOND(id)) - if object ~= entity then - continue - end - if not ids then - ids = {} :: { [i53]: boolean } - end - ids[id] = true - removal_queued = true - end - - if not removal_queued then - continue - end - - if not queue then - queue = {} :: { i53 } - end - - local n = #entities - table.move(entities, 1, n, count + 1, queue) - count += n - end - - for id in ids do - for _, child in queue do - world_remove(world, child, id) - end - end - end - - if idr_r then - local count = 0 - local archetype_ids = idr_r.cache - local ids = {} - local queue = {} - for archetype_id in archetype_ids do - local idr_r_archetype = archetypes[archetype_id] - local entities = idr_r_archetype.entities - local tr = idr_r_archetype.records[rel] - local tr_count = idr_r_archetype.counts[rel] - local types = idr_r_archetype.types - for i = tr, tr + tr_count - 1 do - ids[types[i]] = true - end - local n = #entities - table.move(entities, 1, n, count + 1, queue) - count += n - end - - for _, e in queue do - for id in ids do - world_remove(world, e, id) - end - end - end -end - -local function archetype_destroy(world: ecs_world_t, archetype: ecs_archetype_t) - if archetype == world.ROOT_ARCHETYPE then - return - end - - local component_index = world.component_index - local archetype_edges = world.archetype_edges - - for id, edge in archetype_edges[archetype.id] do - archetype_edges[edge.id][id] = nil - end - - local archetype_id = archetype.id - world.archetypes[archetype_id] = nil :: any - world.archetype_index[archetype.type] = nil :: any - local records = archetype.records - - for id in records do - local observer_list = find_observers(world, EcsOnArchetypeDelete, id) - if not observer_list then - continue - end - for _, observer in observer_list do - if query_match(observer.query, archetype) then - observer.callback(archetype) - end - end - end - - for id in records do - local idr = component_index[id] - idr.cache[archetype_id] = nil :: any - idr.counts[archetype_id] = nil - idr.size -= 1 - records[id] = nil :: any - if idr.size == 0 then - component_index[id] = nil :: any - end - end -end - -local function world_cleanup(world: ecs_world_t) - local archetypes = world.archetypes - - for _, archetype in archetypes do - if #archetype.entities == 0 then - archetype_destroy(world, archetype) - end - end - - local new_archetypes = table.create(#archetypes) :: { ecs_archetype_t } - local new_archetype_map = {} - - for index, archetype in archetypes do - new_archetypes[index] = archetype - new_archetype_map[archetype.type] = archetype - end - - world.archetypes = new_archetypes - world.archetype_index = new_archetype_map -end - -local function world_delete(world: ecs_world_t, entity: i53) - local entity_index = world.entity_index - local record = entity_index_try_get(entity_index, entity) - if not record then - return - end - - local archetype = record.archetype - local row = record.row - - if archetype then - -- In the future should have a destruct mode for - -- deleting archetypes themselves. Maybe requires recycling - archetype_delete(world, archetype, row) - end - - local delete = entity - local component_index = world.component_index - local archetypes = world.archetypes - local tgt = ECS_PAIR(EcsWildcard, delete) - local rel = ECS_PAIR(delete, EcsWildcard) - - local idr_t = component_index[tgt] - local idr = component_index[delete] - local idr_r = component_index[rel] - - if idr then - local flags = idr.flags - if bit32.band(flags, ECS_ID_DELETE) ~= 0 then - for archetype_id in idr.cache do - local idr_archetype = archetypes[archetype_id] - - local entities = idr_archetype.entities - local n = #entities - for i = n, 1, -1 do - world_delete(world, entities[i]) - end - - archetype_destroy(world, idr_archetype) - end - else - for archetype_id in idr.cache do - local idr_archetype = archetypes[archetype_id] - local entities = idr_archetype.entities - local n = #entities - for i = n, 1, -1 do - world_remove(world, entities[i], delete) - end - - archetype_destroy(world, idr_archetype) - end - end - end - - if idr_t then - local children: { i53 } - local ids: Map - - local count = 0 - local archetype_ids = idr_t.cache - for archetype_id in archetype_ids do - local idr_t_archetype = archetypes[archetype_id] - local idr_t_types = idr_t_archetype.types - local entities = idr_t_archetype.entities - local removal_queued = false - - for _, id in idr_t_types do - if not ECS_IS_PAIR(id) then - continue - end - local object = entity_index_get_alive( - entity_index, ECS_PAIR_SECOND(id)) - if object ~= delete then - continue - end - local id_record = component_index[id] - local flags = id_record.flags - local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE) - if flags_delete_mask ~= 0 then - for i = #entities, 1, -1 do - local child = entities[i] - world_delete(world, child) - end - break - else - if not ids then - ids = {} :: { [i53]: boolean } - end - ids[id] = true - removal_queued = true - end - end - - if not removal_queued then - continue - end - if not children then - children = {} :: { i53 } - end - local n = #entities - table.move(entities, 1, n, count + 1, children) - count += n - end - - if ids then - for _, child in children do - for id in ids do - world_remove(world, child, id) - end - end - end - - for archetype_id in archetype_ids do - archetype_destroy(world, archetypes[archetype_id]) - end - end - - if idr_r then - local archetype_ids = idr_r.cache - local flags = idr_r.flags - if (bit32.band(flags, ECS_ID_DELETE) :: number) ~= 0 then - for archetype_id in archetype_ids do - local idr_r_archetype = archetypes[archetype_id] - local entities = idr_r_archetype.entities - local n = #entities - for i = n, 1, -1 do - world_delete(world, entities[i]) - end - archetype_destroy(world, idr_r_archetype) - end - else - local children = {} - local count = 0 - local ids = {} - for archetype_id in archetype_ids do - local idr_r_archetype = archetypes[archetype_id] - local entities = idr_r_archetype.entities - local tr = idr_r_archetype.records[rel] - local tr_count = idr_r_archetype.counts[rel] - local types = idr_r_archetype.types - for i = tr, tr + tr_count - 1 do - ids[types[i]] = true - end - local n = #entities - table.move(entities, 1, n, count + 1, children) - count += n - end - - for _, child in children do - for id in ids do - world_remove(world, child, id) - end - end - - for archetype_id in archetype_ids do - archetype_destroy(world, archetypes[archetype_id]) - end - end - end - - local dense_array = entity_index.dense_array - local dense = record.dense - local i_swap = entity_index.alive_count - entity_index.alive_count = i_swap - 1 - - local e_swap = dense_array[i_swap] - local r_swap = entity_index_try_get_any(entity_index, e_swap) :: ecs_record_t - - r_swap.dense = dense - record.archetype = nil :: any - record.row = nil :: any - record.dense = i_swap - - dense_array[dense] = e_swap - dense_array[i_swap] = ECS_GENERATION_INC(entity) -end - -local function world_exists(world: ecs_world_t, entity): boolean - return entity_index_try_get_any(world.entity_index, entity) ~= nil -end - -local function world_contains(world: ecs_world_t, entity): boolean - return entity_index_is_alive(world.entity_index, entity) -end - -local function NOOP() end - -export type QueryInner = { - compatible_archetypes: { Archetype }, - ids: { i53 }, - filter_with: { i53 }, - filter_without: { i53 }, - next: () -> (number, ...any), - world: World, -} - -local function query_iter_init(query: ecs_query_data_t): () -> (number, ...any) - local world_query_iter_next - - local compatible_archetypes = query.compatible_archetypes - local lastArchetype = 1 - local archetype = compatible_archetypes[1] - if not archetype then - return NOOP :: () -> (number, ...any) - end - local columns = archetype.columns - local entities = archetype.entities - local i = #entities - local records = archetype.records - - local ids = query.ids - local A, B, C, D, E, F, G, H, I = unpack(ids) - local a: Column, b: Column, c: Column, d: Column - local e: Column, f: Column, g: Column, h: Column - - if not B then - a = columns[records[A]] - elseif not C then - a = columns[records[A]] - b = columns[records[B]] - elseif not D then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - elseif not E then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - elseif not F then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - elseif not G then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - elseif not H then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - elseif not I then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - h = columns[records[H]] - end - - if not B then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - end - - local row = i - i -= 1 - - return entity, a[row] - end - elseif not C then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row] - end - elseif not D then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row] - end - elseif not E then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row] - end - elseif not F then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row] - end - elseif not G then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row] - end - elseif not H then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] - end - elseif not I then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - h = columns[records[H]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] - end - else - local output = {} - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - end - - local row = i - i -= 1 - - for j, id in ids do - output[j] = columns[records[id]][row] - end - - return entity, unpack(output) - end - end - - query.next = world_query_iter_next - return world_query_iter_next -end - -local function query_iter(query): () -> (number, ...any) - local query_next = query.next - if not query_next then - query_next = query_iter_init(query) - end - return query_next -end - -local function query_without(query: ecs_query_data_t, ...: i53) - local without = { ... } - query.filter_without = without - local compatible_archetypes = query.compatible_archetypes - for i = #compatible_archetypes, 1, -1 do - local archetype = compatible_archetypes[i] - local records = archetype.records - local matches = true - - for _, id in without do - if records[id] then - matches = false - break - end - end - - if matches then - continue - end - - local last = #compatible_archetypes - if last ~= i then - compatible_archetypes[i] = compatible_archetypes[last] - end - compatible_archetypes[last] = nil :: any - end - - return query :: any -end - -local function query_with(query: ecs_query_data_t, ...: i53) - local compatible_archetypes = query.compatible_archetypes - local with = { ... } - query.filter_with = with - - for i = #compatible_archetypes, 1, -1 do - local archetype = compatible_archetypes[i] - local records = archetype.records - local matches = true - - for _, id in with do - if not records[id] then - matches = false - break - end - end - - if matches then - continue - end - - local last = #compatible_archetypes - if last ~= i then - compatible_archetypes[i] = compatible_archetypes[last] - end - compatible_archetypes[last] = nil :: any - end - - return query :: any -end - --- Meant for directly iterating over archetypes to minimize --- function call overhead. Should not be used unless iterating over --- hundreds of thousands of entities in bulk. -local function query_archetypes(query) - return query.compatible_archetypes -end - -local function query_cached(query: ecs_query_data_t) - local with = query.filter_with - local ids = query.ids - if with then - table.move(ids, 1, #ids, #with + 1, with) - else - query.filter_with = ids - end - - local compatible_archetypes = query.compatible_archetypes - local lastArchetype = 1 - - local A, B, C, D, E, F, G, H, I = unpack(ids) - local a: Column, b: Column, c: Column, d: Column - local e: Column, f: Column, g: Column, h: Column - - local world_query_iter_next - local columns: { Column } - local entities: { number } - local i: number - local archetype: ecs_archetype_t - local records: { number } - local archetypes = query.compatible_archetypes - - local world = query.world :: { observable: ecs_observable_t } - -- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively - -- because the event will be emitted for all components of that Archetype. - local observable = world.observable :: ecs_observable_t - local on_create_action = observable[EcsOnArchetypeCreate] - if not on_create_action then - on_create_action = {} :: Map - observable[EcsOnArchetypeCreate] = on_create_action - end - local query_cache_on_create = on_create_action[A] - if not query_cache_on_create then - query_cache_on_create = {} - on_create_action[A] = query_cache_on_create - end - - local on_delete_action = observable[EcsOnArchetypeDelete] - if not on_delete_action then - on_delete_action = {} :: Map - observable[EcsOnArchetypeDelete] = on_delete_action - end - local query_cache_on_delete = on_delete_action[A] - if not query_cache_on_delete then - query_cache_on_delete = {} - on_delete_action[A] = query_cache_on_delete - end - - local function on_create_callback(archetype) - table.insert(archetypes, archetype) - end - - local function on_delete_callback(archetype) - local i = table.find(archetypes, archetype) :: number - if i == nil then - return - end - local n = #archetypes - archetypes[i] = archetypes[n] - archetypes[n] = nil - end - - local observer_for_create = { query = query, callback = on_create_callback } - local observer_for_delete = { query = query, callback = on_delete_callback } - - table.insert(query_cache_on_create, observer_for_create) - table.insert(query_cache_on_delete, observer_for_delete) - - local function cached_query_iter() - lastArchetype = 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return NOOP - end - entities = archetype.entities - i = #entities - records = archetype.records - columns = archetype.columns - if not B then - a = columns[records[A]] - elseif not C then - a = columns[records[A]] - b = columns[records[B]] - elseif not D then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - elseif not E then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - elseif not F then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - elseif not G then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - elseif not H then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - elseif not I then - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - h = columns[records[H]] - end - - return world_query_iter_next - end - - if not B then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - end - - local row = i - i -= 1 - - return entity, a[row] - end - elseif not C then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row] - end - elseif not D then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row] - end - elseif not E then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row] - end - elseif not F then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row] - end - elseif not G then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row] - end - elseif not H then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] - end - elseif not I then - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - a = columns[records[A]] - b = columns[records[B]] - c = columns[records[C]] - d = columns[records[D]] - e = columns[records[E]] - f = columns[records[F]] - g = columns[records[G]] - h = columns[records[H]] - end - - local row = i - i -= 1 - - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] - end - else - local queryOutput = {} - function world_query_iter_next(): any - local entity = entities[i] - while entity == nil do - lastArchetype += 1 - archetype = compatible_archetypes[lastArchetype] - if not archetype then - return nil - end - - entities = archetype.entities - i = #entities - if i == 0 then - continue - end - entity = entities[i] - columns = archetype.columns - records = archetype.records - end - - local row = i - i -= 1 - - if not F then - return entity, a[row], b[row], c[row], d[row], e[row] - elseif not G then - return entity, a[row], b[row], c[row], d[row], e[row], f[row] - elseif not H then - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] - elseif not I then - return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] - end - - for j, id in ids do - queryOutput[j] = columns[records[id]][row] - end - - return entity, unpack(queryOutput) - end - end - - local cached_query = query :: any - cached_query.archetypes = query_archetypes - cached_query.__iter = cached_query_iter - cached_query.iter = cached_query_iter - setmetatable(cached_query, cached_query) - return cached_query -end - -local Query = {} -Query.__index = Query -Query.__iter = query_iter -Query.iter = query_iter_init -Query.without = query_without -Query.with = query_with -Query.archetypes = query_archetypes -Query.cached = query_cached - -local function world_query(world: ecs_world_t, ...) - local compatible_archetypes = {} - local length = 0 - - local ids = { ... } - - local archetypes = world.archetypes - - local idr: ecs_id_record_t? - local component_index = world.component_index - - local q = setmetatable({ - ids = ids, - compatible_archetypes = compatible_archetypes, - world = world, - }, Query) - - for _, id in ids do - local map = component_index[id] - if not map then - return q - end - - if idr == nil or (map.size :: number) < (idr.size :: number) then - idr = map - end - end - - if idr == nil then - return q - end - - for archetype_id in idr.cache do - local compatibleArchetype = archetypes[archetype_id] - if #compatibleArchetype.entities == 0 then - continue - end - local records = compatibleArchetype.records - - local skip = false - - for i, id in ids do - local tr = records[id] - if not tr then - skip = true - break - end - end - - if skip then - continue - end - - length += 1 - compatible_archetypes[length] = compatibleArchetype - end - - return q -end - -local function world_each(world: ecs_world_t, id: i53): () -> () - local idr = world.component_index[id] - if not idr then - return NOOP - end - - local idr_cache = idr.cache - local archetypes = world.archetypes - local archetype_id = next(idr_cache, nil) :: number - local archetype = archetypes[archetype_id] - if not archetype then - return NOOP - end - - local entities = archetype.entities - local row = #entities - - return function(): any - local entity = entities[row] - while not entity do - archetype_id = next(idr_cache, archetype_id) :: number - if not archetype_id then - return - end - archetype = archetypes[archetype_id] - entities = archetype.entities - row = #entities - entity = entities[row] - end - row -= 1 - return entity - end -end - -local function world_children(world: ecs_world_t, parent: i53) - return world_each(world, ECS_PAIR(EcsChildOf, parent)) -end - -export type Record = { - archetype: Archetype, - row: number, - dense: i24, -} -export type ComponentRecord = { - cache: { [Id]: number }, - counts: { [Id]: number }, - flags: number, - size: number, - hooks: { - on_add: ((entity: Entity, id: Entity, value: T) -> ())?, - on_change: ((entity: Entity, id: Entity, value: T) -> ())?, - on_remove: ((entity: Entity, id: Entity) -> ())?, - }, -} -export type ComponentIndex = Map -export type Archetypes = { [Id]: Archetype } - -export type EntityIndex = { - dense_array: Map, - sparse_array: Map, - alive_count: number, - max_id: number, - range_begin: number?, - range_end: number? -} - -local World = {} -World.__index = World - -World.entity = world_entity -World.query = world_query -World.remove = world_remove -World.clear = world_clear -World.delete = world_delete -World.component = world_component -World.add = world_add -World.set = world_set -World.get = world_get -World.has = world_has -World.target = world_target -World.parent = world_parent -World.contains = world_contains -World.exists = world_exists -World.cleanup = world_cleanup -World.each = world_each -World.children = world_children -World.range = world_range - -local function world_new() - local entity_index = { - dense_array = {}, - sparse_array = {}, - alive_count = 0, - max_id = 0, - } :: ecs_entity_index_t - local self = setmetatable({ - archetype_edges = {}, - - archetype_index = {} :: { [string]: Archetype }, - archetypes = {} :: Archetypes, - component_index = {} :: ComponentIndex, - entity_index = entity_index, - ROOT_ARCHETYPE = (nil :: any) :: Archetype, - - max_archetype_id = 0, - max_component_id = ecs_max_component_id, - - observable = {} :: Observable, - }, World) :: any - - self.ROOT_ARCHETYPE = archetype_create(self, {}, "") - - for i = 1, HI_COMPONENT_ID do - local e = entity_index_new_id(entity_index) - world_add(self, e, EcsComponent) - end - - for i = HI_COMPONENT_ID + 1, EcsRest do - -- Initialize built-in components - entity_index_new_id(entity_index) - end - - world_add(self, EcsName, EcsComponent) - world_add(self, EcsOnChange, EcsComponent) - world_add(self, EcsOnAdd, EcsComponent) - world_add(self, EcsOnRemove, EcsComponent) - world_add(self, EcsWildcard, EcsComponent) - world_add(self, EcsRest, EcsComponent) - - world_set(self, EcsOnAdd, EcsName, "jecs.OnAdd") - world_set(self, EcsOnRemove, EcsName, "jecs.OnRemove") - world_set(self, EcsOnChange, EcsName, "jecs.OnChange") - world_set(self, EcsWildcard, EcsName, "jecs.Wildcard") - world_set(self, EcsChildOf, EcsName, "jecs.ChildOf") - world_set(self, EcsComponent, EcsName, "jecs.Component") - world_set(self, EcsOnDelete, EcsName, "jecs.OnDelete") - world_set(self, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget") - world_set(self, EcsDelete, EcsName, "jecs.Delete") - world_set(self, EcsRemove, EcsName, "jecs.Remove") - world_set(self, EcsName, EcsName, "jecs.Name") - world_set(self, EcsRest, EcsRest, "jecs.Rest") - - world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) - - for i = EcsRest + 1, ecs_max_tag_id do - entity_index_new_id(entity_index) - end - - for i, bundle in ecs_metadata do - for ty, value in bundle do - if value == NULL then - world_add(self, i, ty) - else - world_set(self, i, ty, value) - end - end - end - - return self -end - -World.new = world_new - -export type Entity = number | { __T: T } -export type Id = number | { __T: T } -export type Pair = Id

-type ecs_id_t = Id | Pair | Pair<"Tag", T> -export type Item = (self: Query) -> (Entity, T...) -export type Iter = (query: Query) -> () -> (Entity, T...) - -export type Query = typeof(setmetatable( - {} :: { - iter: Iter, - with: (self: Query, ...Id) -> Query, - without: (self: Query, ...Id) -> Query, - archetypes: (self: Query) -> { Archetype }, - cached: (self: Query) -> Query, - }, - {} :: { - __iter: Iter - } -)) - -export type Observer = { - callback: (archetype: Archetype) -> (), - query: QueryInner, -} - -export type Observable = { - [Id]: { - [Id]: { - { Observer } - } - } -} - -export type World = { - archetype_index: { [string]: Archetype }, - archetypes: Archetypes, - component_index: ComponentIndex, - entity_index: EntityIndex, - ROOT_ARCHETYPE: Archetype, - - max_component_id: number, - max_archetype_id: number, - - observable: any, - - --- Enforce a check on entities to be created within desired range - range: (self: World, range_begin: number, range_end: number?) -> (), - - --- Creates a new entity - entity: (self: World, id: Entity?) -> Entity, - --- Creates a new entity located in the first 256 ids. - --- These should be used for static components for fast access. - component: (self: World) -> Entity, - --- Gets the target of an relationship. For example, when a user calls - --- `world:target(id, ChildOf(parent), 0)`, you will obtain the parent entity. - target: (self: World, id: Entity, relation: Id, index: number?) -> Entity?, - --- Deletes an entity and all it's related components and relationships. - delete: (self: World, id: Entity) -> (), - - --- Adds a component to the entity with no value - add: (self: World, id: Entity, component: Id) -> (), - --- Assigns a value to a component on the given entity - set: (self: World, id: Entity, component: Id, data: a) -> (), - - cleanup: (self: World) -> (), - -- Clears an entity from the world - clear: (self: World, id: Id) -> (), - --- Removes a component from the given entity - remove: (self: World, id: Entity, component: Id) -> (), - --- Retrieves the value of up to 4 components. These values may be nil. - get: & ((World, Entity, Id) -> a?) - & ((World, Entity, Id, Id) -> (a?, b?)) - & ((World, Entity, Id, Id, Id) -> (a?, b?, c?)) - & ((World, Entity, Id, Id, Id, Id) -> (a?, b?, c?, d?)), - - --- Returns whether the entity has the ID. - has: ((World, Entity, Id) -> boolean) - & ((World, Entity, Id, Id) -> boolean) - & ((World, Entity, Id, Id, Id) -> boolean) - & (World, Entity, Id, Id, Id, Id) -> boolean, - - --- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil. - parent: (self: World, entity: Entity) -> Entity, - - --- Checks if the world contains the given entity - contains: (self: World, entity: Entity) -> boolean, - - --- Checks if the entity exists - exists: (self: World, entity: Entity) -> boolean, - - each: (self: World, id: Id) -> () -> Entity, - - children: (self: World, id: Id) -> () -> Entity, - - --- Searches the world for entities that match a given query - query: ((World, Id) -> Query) - & ((World, Id, Id) -> Query) - & ((World, Id, Id, Id) -> Query) - & ((World, Id, Id, Id, Id) -> Query) - & ((World, Id, Id, Id, Id, Id) -> Query) - & ((World, Id, Id, Id, Id, Id, Id) -> Query) - & ((World, Id, Id, Id, Id, Id, Id, Id) -> Query) - & ((World, Id, Id, Id, Id, Id, Id, Id, Id, ...Id) -> Query) -} --- type function ecs_id_t(entity) --- local ty = entity:components()[2] --- local __T = ty:readproperty(types.singleton("__T")) --- if not __T then --- return ty:readproperty(types.singleton("__jecs_pair_value")) --- end --- return __T --- end - --- type function ecs_pair_t(first, second) --- if ecs_id_t(first):is("nil") then --- return second --- else --- return first --- end --- end --- - -return { - World = World :: { new: () -> World }, - world = world_new :: () -> World, - component = (ECS_COMPONENT :: any) :: () -> Entity, - tag = (ECS_TAG :: any) :: () -> Entity, - meta = (ECS_META :: any) :: (id: Entity, id: Id, value: T) -> Entity, - is_tag = (ecs_is_tag :: any) :: (World, Id) -> boolean, - - OnAdd = (EcsOnAdd :: any) :: Entity<(entity: Entity, id: Id, data: T) -> ()>, - OnRemove = (EcsOnRemove :: any) :: Entity<(entity: Entity, id: Id) -> ()>, - OnChange = (EcsOnChange :: any) :: Entity<(entity: Entity, id: Id, data: T) -> ()>, - ChildOf = (EcsChildOf :: any) :: Entity, - Component = (EcsComponent :: any) :: Entity, - Wildcard = (EcsWildcard :: any) :: Entity, - w = (EcsWildcard :: any) :: Entity, - OnDelete = (EcsOnDelete :: any) :: Entity, - OnDeleteTarget = (EcsOnDeleteTarget :: any) :: Entity, - Delete = (EcsDelete :: any) :: Entity, - Remove = (EcsRemove :: any) :: Entity, - Name = (EcsName :: any) :: Entity, - Rest = (EcsRest :: any) :: Entity, - - pair = (ECS_PAIR :: any) :: (first: Id

, second: Id) -> Pair, - - -- Inwards facing API for testing - ECS_ID = ECS_ENTITY_T_LO, - ECS_GENERATION_INC = ECS_GENERATION_INC, - ECS_GENERATION = ECS_GENERATION, - ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD, - ECS_ID_DELETE = ECS_ID_DELETE, - ECS_META_RESET = ECS_META_RESET, - - IS_PAIR = (ECS_IS_PAIR :: any) :: (pair: Pair) -> boolean, - ECS_PAIR_FIRST = ECS_PAIR_FIRST :: (pair: Pair) -> Id

, - ECS_PAIR_SECOND = ECS_PAIR_SECOND :: (pair: Pair) -> Id, - pair_first = (ecs_pair_first :: any) :: (world: World, pair: Pair) -> Id

, - pair_second = (ecs_pair_second :: any) :: (world: World, pair: Pair) -> Id, - entity_index_get_alive = entity_index_get_alive, - - archetype_append_to_records = archetype_append_to_records, - id_record_ensure = id_record_ensure, - archetype_create = archetype_create, - archetype_ensure = archetype_ensure, - find_insert = find_insert, - find_archetype_with = find_archetype_with, - find_archetype_without = find_archetype_without, - create_edge_for_remove = create_edge_for_remove, - archetype_traverse_add = archetype_traverse_add, - archetype_traverse_remove = archetype_traverse_remove, - - entity_move = entity_move, - - entity_index_try_get = entity_index_try_get, - entity_index_try_get_any = entity_index_try_get_any, - entity_index_try_get_fast = entity_index_try_get_fast, - entity_index_is_alive = entity_index_is_alive, - entity_index_new_id = entity_index_new_id, - - query_iter = query_iter, - query_iter_init = query_iter_init, - query_with = query_with, - query_without = query_without, - query_archetypes = query_archetypes, - query_match = query_match, - - find_observers = find_observers, -} +local bit = require("bit") +local ECS_ENTITY_MASK = bit.lshift(1, 24) +local ECS_GENERATION_MASK = bit.lshift(1, 16) +local ECS_PAIR_OFFSET = 2 ^ 48 + +local ECS_ID_DELETE = 2 --0b01 +local ECS_ID_IS_TAG = 1 --0b10 +local ECS_ID_MASK = 0 --0b00 + +local function table_create(n, v) + if v == nil then + return {} + end + local t = {} + for i = 1, n do + t[i] = v + end + return t +end + +local function table_find(tbl, val) + for i, v in tbl do + if v == val then + return i + end + end + return nil +end + +local function table_clone(tbl) + local t = {} + for i, v in ipairs(tbl) do + t[i] = v + end + return t +end + +local HI_COMPONENT_ID = 256 +local EcsOnAdd = HI_COMPONENT_ID + 1 +local EcsOnRemove = HI_COMPONENT_ID + 2 +local EcsOnChange = HI_COMPONENT_ID + 3 +local EcsWildcard = HI_COMPONENT_ID + 4 +local EcsChildOf = HI_COMPONENT_ID + 5 +local EcsComponent = HI_COMPONENT_ID + 6 +local EcsOnDelete = HI_COMPONENT_ID + 7 +local EcsOnDeleteTarget = HI_COMPONENT_ID + 8 +local EcsDelete = HI_COMPONENT_ID + 9 +local EcsRemove = HI_COMPONENT_ID + 10 +local EcsName = HI_COMPONENT_ID + 11 +local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12 +local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13 +local EcsRest = HI_COMPONENT_ID + 14 + +local NULL_ARRAY = {} +local NULL = newproxy(false) + +local ECS_INTERNAL_ERROR = [[ + This is an internal error, please file a bug report via the following link: + + https://github.com/Ukendio/jecs/issues/new?template=BUG-REPORT.md +]] + +local function ecs_assert(condition, msg) + if not condition then + error(msg) + end +end + +local ecs_metadata = {} +local ecs_max_component_id = 0 +local ecs_max_tag_id = EcsRest + +local function ECS_COMPONENT() + ecs_max_component_id = ecs_max_component_id + 1 + if ecs_max_component_id > HI_COMPONENT_ID then + error("Too many components") + end + return ecs_max_component_id +end + +local function ECS_TAG() + ecs_max_tag_id = ecs_max_tag_id + 1 + return ecs_max_tag_id +end + +local function ECS_META(id, ty, value) + local bundle = ecs_metadata[id] + if bundle == nil then + bundle = {} + ecs_metadata[id] = bundle + end + if value == nil then + bundle[ty] = NULL + else + bundle[ty] = value + end +end + +local function ECS_META_RESET() + ecs_metadata = {} + ecs_max_component_id = 0 + ecs_max_tag_id = EcsRest +end + +local function ECS_COMBINE(id, generation) + return id + (generation * ECS_ENTITY_MASK) +end + +local function ECS_IS_PAIR(e) + return e > ECS_PAIR_OFFSET +end + +local function ECS_GENERATION_INC(e) + if e > ECS_ENTITY_MASK then + local id = bit.rshift(e, ECS_ENTITY_MASK) + local generation = bit.band(e, ECS_ENTITY_MASK) + + local next_gen = generation + 1 + if next_gen >= ECS_GENERATION_MASK then + return id + end + + return ECS_COMBINE(id, next_gen) + end + return ECS_COMBINE(e, 1) +end + +local function ECS_ENTITY_T_LO(e) + if e > ECS_ENTITY_MASK then + return bit.band(e, ECS_ENTITY_MASK) + end + return e +end + +local function ECS_ID(e) + if e > ECS_ENTITY_MASK then + return bit.band(e, ECS_ENTITY_MASK) + end + return e +end + +local function ECS_GENERATION(e) + if e > ECS_ENTITY_MASK then + return bit.rshift(e, ECS_ENTITY_MASK) + end + return e +end + +local function ECS_ENTITY_T_HI(e) + if e > ECS_ENTITY_MASK then + return bit.rshift(e, ECS_ENTITY_MASK) + end + return e +end + +local function ECS_PAIR(pred, obj) + pred = bit.band(pred, ECS_ENTITY_MASK) + obj = bit.band(obj, ECS_ENTITY_MASK) + + return obj + (pred * ECS_ENTITY_MASK) + ECS_PAIR_OFFSET +end + +local function ECS_PAIR_FIRST(e) + return bit.rshift((e - ECS_PAIR_OFFSET), ECS_ENTITY_MASK) +end + +local function ECS_PAIR_SECOND(e) + return bit.band((e - ECS_PAIR_OFFSET), ECS_ENTITY_MASK) +end + +local function entity_index_try_get_any( + entity_index, + entity +) + local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)] + + if not r or r.dense == 0 then + return nil + end + + return r +end + +local function entity_index_try_get(entity_index, entity) + local r = entity_index_try_get_any(entity_index, entity) + if r then + local r_dense = r.dense + if r_dense > entity_index.alive_count then + return nil + end + if entity_index.dense_array[r_dense] ~= entity then + return nil + end + end + return r +end + +local function entity_index_try_get_fast(entity_index, entity) + local id = ECS_ENTITY_T_LO(entity) + local r = entity_index.sparse_array[id] + if r then + if entity_index.dense_array[r.dense] ~= entity then + return nil + end + end + return r +end + +local function entity_index_is_alive(entity_index, entity) + return entity_index_try_get(entity_index, entity) ~= nil +end + +local function entity_index_get_alive(entity_index, entity) + local r = entity_index_try_get_any(entity_index, entity) + if r then + return entity_index.dense_array[r.dense] + end + return nil +end + +local function ecs_get_alive(world, entity) + if entity == 0 then + return 0 + end + + local eindex = world.entity_index + + if entity_index_is_alive(eindex, entity) then + return entity + end + + if entity > ECS_ENTITY_MASK then + return 0 + end + + local current = entity_index_get_alive(eindex, entity) + if not current or not entity_index_is_alive(eindex, current) then + return 0 + end + + return current +end + +local ECS_INTERNAL_ERROR_INCOMPATIBLE_ENTITY = "Entity is outside range" + +local function entity_index_new_id(entity_index) + local dense_array = entity_index.dense_array + local alive_count = entity_index.alive_count + local sparse_array = entity_index.sparse_array + local max_id = entity_index.max_id + + if alive_count < max_id then + alive_count = alive_count + 1 + entity_index.alive_count = alive_count + local id = 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 = alive_count + 1 + entity_index.alive_count = alive_count + dense_array[alive_count] = id + sparse_array[id] = { dense = alive_count } + + return id +end + +local function ecs_pair_first(world, e) + local pred = ECS_PAIR_FIRST(e) + return ecs_get_alive(world, pred) +end + +local function ecs_pair_second(world, e) + local obj = ECS_PAIR_SECOND(e) + return ecs_get_alive(world, obj) +end + +local function query_match(query, + archetype) + local records = archetype.records + local with = query.filter_with + + for _, id in with do + if not records[id] then + return false + end + end + + local without = query.filter_without + if without then + for _, id in without do + if records[id] then + return false + end + end + end + + return true +end + +local function find_observers(world, event, component) + local cache = world.observable[event] + if not cache then + return nil + end + return cache[component] +end + +local function archetype_move( + entity_index, + to, + dst_row, + from, + src_row +) + local src_columns = from.columns + local dst_columns = to.columns + local dst_entities = to.entities + local src_entities = from.entities + + local last = #src_entities + local id_types = from.types + local records = to.records + + for i, column in ipairs(src_columns) do + if column ~= NULL_ARRAY then + -- Retrieves the new column index from the source archetype's record from each component + -- We have to do this because the columns are tightly packed and indexes may not correspond to each other. + local tr = records[id_types[i]] + -- Sometimes target column may not exist, e.g. when you remove a component. + if tr then + dst_columns[tr][dst_row] = column[src_row] + end + -- If the entity is the last row in the archetype then swapping it would be meaningless. + if src_row ~= last then + -- Swap rempves columns to ensure there are no holes in the archetype. + column[src_row] = column[last] + end + column[last] = nil + end + end + + local moved = #src_entities + + -- Move the entity from the source to the destination archetype. + -- Because we have swapped columns we now have to update the records + -- corresponding to the entities' rows that were swapped. + local e1 = src_entities[src_row] + local e2 = src_entities[moved] + + if src_row ~= moved then + src_entities[src_row] = e2 + end + + src_entities[moved] = nil + dst_entities[dst_row] = e1 + + local sparse_array = entity_index.sparse_array + + local record1 = sparse_array[ECS_ENTITY_T_LO(e1)] + local record2 = sparse_array[ECS_ENTITY_T_LO(e2)] + record1.row = dst_row + record2.row = src_row +end + +local function archetype_append( + entity, + archetype +) + local entities = archetype.entities + local length = #entities + 1 + entities[length] = entity + return length +end + +local function new_entity( + entity, + record, + archetype +) + local row = archetype_append(entity, archetype) + record.archetype = archetype + record.row = row + return record +end + +local function entity_move( + entity_index, + entity, + record, + to +) + local sourceRow = record.row + local from = record.archetype + local dst_row = archetype_append(entity, to) + archetype_move(entity_index, to, dst_row, from, sourceRow) + record.archetype = to + record.row = dst_row +end + +local function hash(arr) + return table.concat(arr, "_") +end + +local function fetch(id, records, columns, row) + local tr = records[id] + + if not tr then + return nil + end + + return columns[tr][row] +end + +local function world_get(world, entity, a, b, c, d, e) + local record = entity_index_try_get_fast(world.entity_index, entity) + if not record then + return nil + end + + local archetype = record.archetype + if not archetype then + return nil + end + + local records = archetype.records + local columns = archetype.columns + local row = record.row + + local va = fetch(a, records, columns, row) + + if not b then + return va + elseif not c then + return va, fetch(b, records, columns, row) + elseif not d then + return va, fetch(b, records, columns, row), fetch(c, records, columns, row) + elseif not e then + return va, fetch(b, records, columns, row), fetch(c, records, columns, row), fetch(d, records, columns, row) + else + error("args exceeded") + end +end + +local function world_has_one_inline(world, entity, id) + local record = entity_index_try_get_fast(world.entity_index, entity) + if not record then + return false + end + + local archetype = record.archetype + if not archetype then + return false + end + + local records = archetype.records + + return records[id] ~= nil +end + +local function ecs_is_tag(world, entity) + local idr = world.component_index[entity] + if idr then + return bit.band(idr.flags, ECS_ID_IS_TAG) ~= 0 + end + return not world_has_one_inline(world, entity, EcsComponent) +end + +local function world_has(world, entity, a, b, c, d, e) + local record = entity_index_try_get_fast(world.entity_index, entity) + if not record then + return false + end + + local archetype = record.archetype + if not archetype then + return false + end + + local records = archetype.records + + return records[a] ~= nil and + (b == nil or records[b] ~= nil) and + (c == nil or records[c] ~= nil) and + (d == nil or records[d] ~= nil) and + (e == nil or error("args exceeded")) +end + +local function world_target(world, entity, relation, index) + local nth = index or 0 + local record = entity_index_try_get_fast(world.entity_index, entity) + if not record then + return nil + end + + local archetype = record.archetype + if not archetype then + return nil + end + + local r = ECS_PAIR(relation, EcsWildcard) + + local count = archetype.counts[r] + if not count then + return nil + end + + if nth >= count then + nth = nth + count + 1 + end + + nth = archetype.types[nth + archetype.records[r]] + if not nth then + return nil + end + + return entity_index_get_alive(world.entity_index, + ECS_PAIR_SECOND(nth)) +end + +local function ECS_ID_IS_WILDCARD(e) + local first = ECS_ENTITY_T_HI(e) + local second = ECS_ENTITY_T_LO(e) + return first == EcsWildcard or second == EcsWildcard +end + +local function id_record_ensure(world, id) + local component_index = world.component_index + local entity_index = world.entity_index + local idr = component_index[id] + + if idr then + return idr + end + + local flags = ECS_ID_MASK + local relation = id + local target = 0 + local is_pair = ECS_IS_PAIR(id) + if is_pair then + relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) + ecs_assert(relation and entity_index_is_alive( + entity_index, relation), ECS_INTERNAL_ERROR) + target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) + ecs_assert(target and entity_index_is_alive( + entity_index, target), ECS_INTERNAL_ERROR) + end + + local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) + local cleanup_policy_target = world_target(world, relation, EcsOnDeleteTarget, 0) + + local has_delete = false + + if cleanup_policy == EcsDelete or cleanup_policy_target == EcsDelete then + has_delete = true + end + + local on_add, on_change, on_remove = world_get(world, + relation, EcsOnAdd, EcsOnChange, EcsOnRemove) + + local is_tag = not world_has_one_inline(world, + relation, EcsComponent) + + if is_tag and is_pair then + is_tag = not world_has_one_inline(world, target, EcsComponent) + end + + if has_delete then + flags = bit.bor(flags, ECS_ID_DELETE) + end + + if is_tag then + flags = bit.bor(flags, ECS_ID_IS_TAG) + end + + idr = { + size = 0, + cache = {}, + counts = {}, + flags = flags, + hooks = { + on_add = on_add, + on_change = on_change, + on_remove = on_remove, + }, + } + + component_index[id] = idr + + return idr +end + +local function archetype_append_to_records( + idr, + archetype, + id, + index +) + local archetype_id = archetype.id + local archetype_records = archetype.records + local archetype_counts = archetype.counts + local idr_columns = idr.cache + local idr_counts = idr.counts + local tr = idr_columns[archetype_id] + if not tr then + idr_columns[archetype_id] = index + idr_counts[archetype_id] = 1 + + archetype_records[id] = index + archetype_counts[id] = 1 + else + local max_count = idr_counts[archetype_id] + 1 + idr_counts[archetype_id] = max_count + archetype_counts[id] = max_count + end +end + +local function archetype_create(world, id_types, ty) + local archetype_id = world.max_archetype_id + 1 + world.max_archetype_id = archetype_id + + local columns = {} + local records = {} + local counts = {} + + local archetype = { + columns = columns, + entities = {}, + id = archetype_id, + records = records, + counts = counts, + type = ty, + types = id_types, + } + + for i, component_id in ipairs(id_types) do + local idr = id_record_ensure(world, component_id) + archetype_append_to_records(idr, archetype, component_id, i) + + if ECS_IS_PAIR(component_id) then + local relation = ECS_PAIR_FIRST(component_id) + local object = ECS_PAIR_SECOND(component_id) + local r = ECS_PAIR(relation, EcsWildcard) + local idr_r = id_record_ensure(world, r) + archetype_append_to_records(idr_r, archetype, r, i) + + local t = ECS_PAIR(EcsWildcard, object) + local idr_t = id_record_ensure(world, t) + archetype_append_to_records(idr_t, archetype, t, i) + end + + if bit.band(idr.flags, ECS_ID_IS_TAG) == 0 then + columns[i] = {} + else + columns[i] = NULL_ARRAY + end + end + + for id in pairs(records) do + local observer_list = find_observers(world, EcsOnArchetypeCreate, id) + if observer_list then + for _, observer in ipairs(observer_list) do + if query_match(observer.query, archetype) then + observer.callback(archetype) + end + end + end + end + + world.archetype_index[ty] = archetype + world.archetypes[archetype_id] = archetype + world.archetype_edges[archetype.id] = {} + + return archetype +end + +local function world_range(world, range_begin, range_end) + local entity_index = world.entity_index + + entity_index.range_begin = range_begin + entity_index.range_end = range_end + + local max_id = entity_index.max_id + + if range_begin > max_id then + local dense_array = entity_index.dense_array + local sparse_array = entity_index.sparse_array + + for i = max_id + 1, range_begin do + dense_array[i] = i + sparse_array[i] = { + dense = 0 + } + end + entity_index.max_id = range_begin - 1 + entity_index.alive_count = range_begin - 1 + end +end + +local function world_entity(world, entity) + local entity_index = world.entity_index + if entity then + local index = ECS_ID(entity) + local max_id = entity_index.max_id + local sparse_array = entity_index.sparse_array + local dense_array = entity_index.dense_array + local alive_count = entity_index.alive_count + local r = sparse_array[index] + if r then + local dense = r.dense + + if not dense or r.dense == 0 then + r.dense = index + dense = index + end + + local any = dense_array[dense] + if dense <= alive_count then + if any ~= entity then + error("Entity ID is already in use with a different generation") + else + return entity + end + end + + local e_swap = dense_array[dense] + local r_swap = entity_index_try_get_any(entity_index, e_swap) + alive_count = alive_count + 1 + entity_index.alive_count = alive_count + r_swap.dense = dense + r.dense = alive_count + dense_array[dense] = e_swap + dense_array[alive_count] = entity + + return entity + else + for i = max_id + 1, index do + sparse_array[i] = { dense = i } + dense_array[i] = i + end + entity_index.max_id = index + + local e_swap = dense_array[alive_count] + local r_swap = sparse_array[alive_count] + r_swap.dense = index + + alive_count = alive_count + 1 + entity_index.alive_count = alive_count + + r = sparse_array[index] + + r.dense = alive_count + + sparse_array[index] = r + + dense_array[index] = e_swap + dense_array[alive_count] = entity + + + return entity + end + end + return entity_index_new_id(entity_index) +end + +local function world_parent(world, entity) + return world_target(world, entity, EcsChildOf, 0) +end + +local function archetype_ensure(world, id_types) + if #id_types < 1 then + return world.ROOT_ARCHETYPE + end + + local ty = hash(id_types) + local archetype = world.archetype_index[ty] + if archetype then + return archetype + end + + return archetype_create(world, id_types, ty) +end + +local function find_insert(id_types, toAdd) + for i, id in ipairs(id_types) do + if id == toAdd then + error("Duplicate component id") + return -1 + end + if id > toAdd then + return i + end + end + return #id_types + 1 +end + +local function find_archetype_without(world, node, id) + local id_types = node.types + local at = table_find(id_types, id) + + local dst = table_clone(id_types) + table.remove(dst, at) + + return archetype_ensure(world, dst) +end + + +local function archetype_traverse_remove(world, id, from) + local edges = world.archetype_edges + local edge = edges[from.id] + + local to = edge[id] + if to == nil then + to = find_archetype_without(world, from, id) + edge[id] = to + edges[to.id][id] = from + end + + return to +end + +local function find_archetype_with(world, id, from) + local id_types = from.types + + local at = find_insert(id_types, id) + local dst = table_clone(id_types) + table.insert(dst, at, id) + + return archetype_ensure(world, dst) +end + +local function archetype_traverse_add(world, id, from) + from = from or world.ROOT_ARCHETYPE + if from.records[id] then + return from + end + local edges = world.archetype_edges + local edge = edges[from.id] + + local to = edge[id] + if not to then + to = find_archetype_with(world, id, from) + edge[id] = to + edges[to.id][id] = from + end + + return to +end + +local function world_add(world, entity, id) + local entity_index = world.entity_index + local record = entity_index_try_get_fast(entity_index, entity) + if not record then + return + end + + local from = record.archetype + local to = archetype_traverse_add(world, id, from) + if from == to then + return + end + if from then + entity_move(entity_index, entity, record, to) + else + if #to.types > 0 then + new_entity(entity, record, to) + end + end + + local idr = world.component_index[id] + local on_add = idr.hooks.on_add + + if on_add then + on_add(entity, id) + end +end + +local function world_set(world, entity, id, data) + local entity_index = world.entity_index + local record = entity_index_try_get_fast(entity_index, entity) + if not record then + return + end + + local from = record.archetype + local to = archetype_traverse_add(world, id, from) + local idr = world.component_index[id] + local idr_hooks = idr.hooks + + if from == to then + local tr = to.records[id] + local column = from.columns[tr] + column[record.row] = data + + -- If the archetypes are the same it can avoid moving the entity + -- and just set the data directly. + local on_change = idr_hooks.on_change + if on_change then + on_change(entity, id, data) + end + + return + end + + if from then + -- If there was a previous archetype, then the entity needs to move the archetype + entity_move(entity_index, entity, record, to) + else + if #to.types > 0 then + -- When there is no previous archetype it should create the archetype + new_entity(entity, record, to) + end + end + + local tr = to.records[id] + local column = to.columns[tr] + + column[record.row] = data + + local on_add = idr_hooks.on_add + if on_add then + on_add(entity, id, data) + end +end + +local function world_component(world) + local id = world.max_component_id + 1 + if id > HI_COMPONENT_ID then + -- IDs are partitioned into ranges because component IDs are not nominal, + -- so it needs to error when IDs intersect into the entity range. + error("Too many components, consider using world:entity() instead to create components.") + end + world.max_component_id = id + + return id +end + +local function world_remove(world, entity, id) + local entity_index = world.entity_index + local record = entity_index_try_get_fast(entity_index, entity) + if not record then + return + end + local from = record.archetype + + if not from then + return + end + + if from.records[id] then + local idr = world.component_index[id] + local on_remove = idr.hooks.on_remove + if on_remove then + on_remove(entity, id) + end + + local to = archetype_traverse_remove(world, id, record.archetype) + + entity_move(entity_index, entity, record, to) + end +end + +local function archetype_fast_delete_last(columns, column_count) + for _, column in ipairs(columns) do + if column ~= NULL_ARRAY then + column[column_count] = nil + end + end +end + +local function archetype_fast_delete(columns, column_count, row) + for _, column in ipairs(columns) do + if column ~= NULL_ARRAY then + column[row] = column[column_count] + column[column_count] = nil + end + end +end + +local function archetype_delete(world, archetype, row) + local entity_index = world.entity_index + local component_index = world.component_index + local columns = archetype.columns + local id_types = archetype.types + local entities = archetype.entities + local column_count = #entities + local last = #entities + local move = entities[last] + -- We assume first that the entity is the last in the archetype + local delete = move + + if row ~= last then + local record_to_move = entity_index_try_get_any(entity_index, move) + if record_to_move then + record_to_move.row = row + end + + delete = entities[row] + entities[row] = move + end + + for _, id in ipairs(id_types) do + local idr = component_index[id] + local on_remove = idr.hooks.on_remove + if on_remove then + on_remove(delete, id) + end + end + + entities[last] = nil + + if row == last then + archetype_fast_delete_last(columns, column_count) + else + archetype_fast_delete(columns, column_count, row) + end +end + +local function world_clear(world, entity) + local entity_index = world.entity_index + local component_index = world.component_index + local archetypes = world.archetypes + local tgt = ECS_PAIR(EcsWildcard, entity) + local idr_t = component_index[tgt] + local idr = component_index[entity] + local rel = ECS_PAIR(entity, EcsWildcard) + local idr_r = component_index[rel] + + if idr then + local count = 0 + local queue = {} + for archetype_id in idr.cache do + local idr_archetype = archetypes[archetype_id] + local entities = idr_archetype.entities + local n = #entities + count = count + n + table.move(entities, 1, n, #queue + 1, queue) + end + for _, e in queue do + world_remove(world, e, entity) + end + end + + if idr_t then + local queue + local ids + + local count = 0 + local archetype_ids = idr_t.cache + for archetype_id in pairs(archetype_ids) do + local idr_t_archetype = archetypes[archetype_id] + local idr_t_types = idr_t_archetype.types + local entities = idr_t_archetype.entities + local removal_queued = false + + for _, id in idr_t_types do + if ECS_IS_PAIR(id) then + local object = entity_index_get_alive( + entity_index, ECS_PAIR_SECOND(id)) + if object ~= entity then + if not ids then + ids = {} + end + ids[id] = true + removal_queued = true + end + end + end + + if removal_queued then + if not queue then + queue = {} + end + + local n = #entities + table.move(entities, 1, n, count + 1, queue) + count = count + n + end + end + + for id in ids do + for _, child in queue do + world_remove(world, child, id) + end + end + end + + if idr_r then + local count = 0 + local archetype_ids = idr_r.cache + local ids = {} + local queue = {} + for archetype_id in archetype_ids do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local tr = idr_r_archetype.records[rel] + local tr_count = idr_r_archetype.counts[rel] + local types = idr_r_archetype.types + for i = tr, tr + tr_count - 1 do + ids[types[i]] = true + end + local n = #entities + table.move(entities, 1, n, count + 1, queue) + count = count + n + end + + for _, e in queue do + for id in ids do + world_remove(world, e, id) + end + end + end +end + +local function archetype_destroy(world, archetype) + if archetype == world.ROOT_ARCHETYPE then + return + end + + local component_index = world.component_index + local archetype_edges = world.archetype_edges + + for id, edge in pairs(archetype_edges[archetype.id]) do + archetype_edges[edge.id][id] = nil + end + + local archetype_id = archetype.id + world.archetypes[archetype_id] = nil + world.archetype_index[archetype.type] = nil + local records = archetype.records + + for id in pairs(records) do + local observer_list = find_observers(world, EcsOnArchetypeDelete, id) + if observer_list then + for _, observer in observer_list do + if query_match(observer.query, archetype) then + observer.callback(archetype) + end + end + end + end + + for id in pairs(records) do + local idr = component_index[id] + idr.cache[archetype_id] = nil + idr.counts[archetype_id] = nil + idr.size = idr.size - 1 + records[id] = nil + if idr.size == 0 then + component_index[id] = nil + end + end +end + +local function world_cleanup(world) + local archetypes = world.archetypes + + for _, archetype in archetypes do + if #archetype.entities == 0 then + archetype_destroy(world, archetype) + end + end + + local new_archetypes = table_create(#archetypes) + local new_archetype_map = {} + + for index, archetype in archetypes do + new_archetypes[index] = archetype + new_archetype_map[archetype.type] = archetype + end + + world.archetypes = new_archetypes + world.archetype_index = new_archetype_map +end + +local function world_delete(world, entity) + local entity_index = world.entity_index + local record = entity_index_try_get(entity_index, entity) + if not record then + return + end + + local archetype = record.archetype + local row = record.row + + if archetype then + -- In the future should have a destruct mode for + -- deleting archetypes themselves. Maybe requires recycling + archetype_delete(world, archetype, row) + end + + local delete = entity + local component_index = world.component_index + local archetypes = world.archetypes + local tgt = ECS_PAIR(EcsWildcard, delete) + local rel = ECS_PAIR(delete, EcsWildcard) + + local idr_t = component_index[tgt] + local idr = component_index[delete] + local idr_r = component_index[rel] + + if idr then + local flags = idr.flags + if bit.band(flags, ECS_ID_DELETE) ~= 0 then + for archetype_id in pairs(idr.cache) do + local idr_archetype = archetypes[archetype_id] + + local entities = idr_archetype.entities + local n = #entities + for i = n, 1, -1 do + world_delete(world, entities[i]) + end + + archetype_destroy(world, idr_archetype) + end + else + for archetype_id in idr.cache do + local idr_archetype = archetypes[archetype_id] + local entities = idr_archetype.entities + local n = #entities + for i = n, 1, -1 do + world_remove(world, entities[i], delete) + end + + archetype_destroy(world, idr_archetype) + end + end + end + + if idr_t then + local children + local ids + + local count = 0 + local archetype_ids = idr_t.cache + for archetype_id in pairs(archetype_ids) do + local idr_t_archetype = archetypes[archetype_id] + local idr_t_types = idr_t_archetype.types + local entities = idr_t_archetype.entities + local removal_queued = false + + for _, id in ipairs(idr_t_types) do + if ECS_IS_PAIR(id) then + local object = entity_index_get_alive( + entity_index, ECS_PAIR_SECOND(id)) + if object == delete then + local id_record = component_index[id] + local flags = id_record.flags + local flags_delete_mask = bit.band(flags, ECS_ID_DELETE) + if flags_delete_mask ~= 0 then + for i = #entities, 1, -1 do + local child = entities[i] + world_delete(world, child) + end + break + else + if not ids then + ids = {} + end + ids[id] = true + removal_queued = true + end + end + end + end + + if removal_queued then + if not children then + children = {} + end + local n = #entities + table.move(entities, 1, n, count + 1, children) + count = count + n + end + end + + if ids then + for _, child in ipairs(children) do + for id in ids do + world_remove(world, child, id) + end + end + end + + for archetype_id in pairs(archetype_ids) do + archetype_destroy(world, archetypes[archetype_id]) + end + end + + if idr_r then + local archetype_ids = idr_r.cache + local flags = idr_r.flags + if (bit.band(flags, ECS_ID_DELETE)) ~= 0 then + for archetype_id in pairs(archetype_ids) do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local n = #entities + for i = n, 1, -1 do + world_delete(world, entities[i]) + end + archetype_destroy(world, idr_r_archetype) + end + else + local children = {} + local count = 0 + local ids = {} + for archetype_id in pairs(archetype_ids) do + local idr_r_archetype = archetypes[archetype_id] + local entities = idr_r_archetype.entities + local tr = idr_r_archetype.records[rel] + local tr_count = idr_r_archetype.counts[rel] + local types = idr_r_archetype.types + for i = tr, tr + tr_count - 1 do + ids[types[i]] = true + end + local n = #entities + table.move(entities, 1, n, count + 1, children) + count = count + n + end + + for _, child in ipairs(children) do + for id in ids do + world_remove(world, child, id) + end + end + + for archetype_id in pairs(archetype_ids) do + archetype_destroy(world, archetypes[archetype_id]) + end + end + end + + local dense_array = entity_index.dense_array + local dense = record.dense + local i_swap = entity_index.alive_count + entity_index.alive_count = i_swap - 1 + + local e_swap = dense_array[i_swap] + local r_swap = entity_index_try_get_any(entity_index, e_swap) + + r_swap.dense = dense + record.archetype = nil + record.row = nil + record.dense = i_swap + + dense_array[dense] = e_swap + dense_array[i_swap] = ECS_GENERATION_INC(entity) +end + +local function world_exists(world, entity) + return entity_index_try_get_any(world.entity_index, entity) ~= nil +end + +local function world_contains(world, entity) + return entity_index_is_alive(world.entity_index, entity) +end + +local function NOOP() end + +local function query_iter_init(query) + local world_query_iter_next + + local compatible_archetypes = query.compatible_archetypes + local lastArchetype = 1 + local archetype = compatible_archetypes[1] + if not archetype then + return NOOP + end + local columns = archetype.columns + local entities = archetype.entities + local i = #entities + local records = archetype.records + + local ids = query.ids + local A, B, C, D, E, F, G, H, I = unpack(ids) + local a, b, c, d, e, f, g, h + + if not B then + a = columns[records[A]] + elseif not C then + a = columns[records[A]] + b = columns[records[B]] + elseif not D then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + elseif not E then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + elseif not F then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + elseif not G then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + elseif not H then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + elseif not I then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + h = columns[records[H]] + end + + if not B then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + end + end + + local row = i + i = i - 1 + + return entity, a[row] + end + elseif not C then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row] + end + elseif not D then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row] + end + elseif not E then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row] + end + elseif not F then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row] + end + elseif not G then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row] + end + elseif not H then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] + end + elseif not I then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + h = columns[records[H]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] + end + else + local output = {} + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + end + end + + local row = i + i = i - 1 + + for j, id in ids do + output[j] = columns[records[id]][row] + end + + return entity, unpack(output) + end + end + + query.next = world_query_iter_next + return world_query_iter_next +end + +local function query_iter(query) + local query_next = query.next + if not query_next then + query_next = query_iter_init(query) + end + return query_next +end + +local function query_without(query, ...) + local without = { ... } + query.filter_without = without + local compatible_archetypes = query.compatible_archetypes + for i = #compatible_archetypes, 1, -1 do + local archetype = compatible_archetypes[i] + local records = archetype.records + local matches = true + + for _, id in without do + if records[id] then + matches = false + break + end + end + + if not matches then + local last = #compatible_archetypes + if last ~= i then + compatible_archetypes[i] = compatible_archetypes[last] + end + compatible_archetypes[last] = nil + end + end + + return query +end + +local function query_with(query, ...) + local compatible_archetypes = query.compatible_archetypes + local with = { ... } + query.filter_with = with + + for i = #compatible_archetypes, 1, -1 do + local archetype = compatible_archetypes[i] + local records = archetype.records + local matches = true + + for _, id in with do + if not records[id] then + matches = false + break + end + end + + if not matches then + local last = #compatible_archetypes + if last ~= i then + compatible_archetypes[i] = compatible_archetypes[last] + end + compatible_archetypes[last] = nil + end + end + + return query +end + +-- Meant for directly iterating over archetypes to minimize +-- function call overhead. Should not be used unless iterating over +-- hundreds of thousands of entities in bulk. +local function query_archetypes(query) + return query.compatible_archetypes +end + +local function query_cached(query) + local with = query.filter_with + local ids = query.ids + if with then + table.move(ids, 1, #ids, #with + 1, with) + else + query.filter_with = ids + end + + local compatible_archetypes = query.compatible_archetypes + local lastArchetype = 1 + + local A, B, C, D, E, F, G, H, I = unpack(ids) + local a, b, c, d, e, f, g, h + + local world_query_iter_next + local columns + local entities + local i + local archetype + local records + local archetypes = query.compatible_archetypes + + local world = query.world + -- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively + -- because the event will be emitted for all components of that Archetype. + local observable = world.observable + local on_create_action = observable[EcsOnArchetypeCreate] + if not on_create_action then + on_create_action = {} + observable[EcsOnArchetypeCreate] = on_create_action + end + local query_cache_on_create = on_create_action[A] + if not query_cache_on_create then + query_cache_on_create = {} + on_create_action[A] = query_cache_on_create + end + + local on_delete_action = observable[EcsOnArchetypeDelete] + if not on_delete_action then + on_delete_action = {} + observable[EcsOnArchetypeDelete] = on_delete_action + end + local query_cache_on_delete = on_delete_action[A] + if not query_cache_on_delete then + query_cache_on_delete = {} + on_delete_action[A] = query_cache_on_delete + end + + local function on_create_callback(archetype) + table.insert(archetypes, archetype) + end + + local function on_delete_callback(archetype) + local i = table_find(archetypes, archetype) + if i == nil then + return + end + local n = #archetypes + archetypes[i] = archetypes[n] + archetypes[n] = nil + end + + local observer_for_create = { query = query, callback = on_create_callback } + local observer_for_delete = { query = query, callback = on_delete_callback } + + table.insert(query_cache_on_create, observer_for_create) + table.insert(query_cache_on_delete, observer_for_delete) + + local function cached_query_iter() + lastArchetype = 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return NOOP + end + entities = archetype.entities + i = #entities + records = archetype.records + columns = archetype.columns + if not B then + a = columns[records[A]] + elseif not C then + a = columns[records[A]] + b = columns[records[B]] + elseif not D then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + elseif not E then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + elseif not F then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + elseif not G then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + elseif not H then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + elseif not I then + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + h = columns[records[H]] + end + + return world_query_iter_next + end + + if not B then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + end + end + + local row = i + i = i - 1 + + return entity, a[row] + end + elseif not C then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row] + end + elseif not D then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row] + end + elseif not E then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row] + end + elseif not F then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row] + end + elseif not G then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row] + end + elseif not H then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row] + end + elseif not I then + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + a = columns[records[A]] + b = columns[records[B]] + c = columns[records[C]] + d = columns[records[D]] + e = columns[records[E]] + f = columns[records[F]] + g = columns[records[G]] + h = columns[records[H]] + end + end + + local row = i + i = i - 1 + + return entity, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row] + end + else + local output = {} + function world_query_iter_next() + local entity = entities[i] + while entity == nil do + lastArchetype = lastArchetype + 1 + archetype = compatible_archetypes[lastArchetype] + if not archetype then + return nil + end + + entities = archetype.entities + i = #entities + if i ~= 0 then + entity = entities[i] + columns = archetype.columns + records = archetype.records + end + end + + local row = i + i = i - 1 + + for j, id in ids do + output[j] = columns[records[id]][row] + end + + return entity, unpack(output) + end + end + + local cached_query = query + cached_query.archetypes = query_archetypes + cached_query.__iter = cached_query_iter + cached_query.iter = cached_query_iter + setmetatable(cached_query, cached_query) + return cached_query +end + +local Query = {} +Query.__index = Query +Query.__iter = query_iter +Query.iter = query_iter_init +Query.without = query_without +Query.with = query_with +Query.archetypes = query_archetypes +Query.cached = query_cached + +local function world_query(world, ...) + local compatible_archetypes = {} + local length = 0 + + local ids = { ... } + + local archetypes = world.archetypes + + local idr + local component_index = world.component_index + + local q = setmetatable({ + ids = ids, + compatible_archetypes = compatible_archetypes, + world = world, + }, Query) + + for _, id in ipairs(ids) do + local map = component_index[id] + if not map then + return q + end + + if idr == nil or (map.size) < (idr.size) then + idr = map + end + end + + if idr == nil then + return q + end + + for archetype_id in pairs(idr.cache) do + local compatibleArchetype = archetypes[archetype_id] + if #compatibleArchetype.entities ~= 0 then + local records = compatibleArchetype.records + local skip = false + + for i, id in ipairs(ids) do + local tr = records[id] + if not tr then + skip = true + break + end + end + + if not skip then + length = length + 1 + compatible_archetypes[length] = compatibleArchetype + end + end + end + + return q +end + +local function world_each(world, id) + local idr = world.component_index[id] + if not idr then + return NOOP + end + + local idr_cache = idr.cache + local archetypes = world.archetypes + local archetype_id = next(idr_cache, nil) + local archetype = archetypes[archetype_id] + if not archetype then + return NOOP + end + + local entities = archetype.entities + local row = #entities + + return function() + local entity = entities[row] + while not entity do + archetype_id = next(idr_cache, archetype_id) + if not archetype_id then + return + end + archetype = archetypes[archetype_id] + entities = archetype.entities + row = #entities + entity = entities[row] + end + row = row - 1 + return entity + end +end + +local function world_children(world, parent) + return world_each(world, ECS_PAIR(EcsChildOf, parent)) +end + +local World = {} +World.__index = World + +World.entity = world_entity +World.query = world_query +World.remove = world_remove +World.clear = world_clear +World.delete = world_delete +World.component = world_component +World.add = world_add +World.set = world_set +World.get = world_get +World.has = world_has +World.target = world_target +World.parent = world_parent +World.contains = world_contains +World.exists = world_exists +World.cleanup = world_cleanup +World.each = world_each +World.children = world_children +World.range = world_range + +local function world_new() + local entity_index = { + dense_array = {}, + sparse_array = {}, + alive_count = 0, + max_id = 0, + } + local self = setmetatable({ + archetype_edges = {}, + + archetype_index = {}, + archetypes = {}, + component_index = {}, + entity_index = entity_index, + ROOT_ARCHETYPE = nil, + + max_archetype_id = 0, + max_component_id = ecs_max_component_id, + + observable = {}, + }, World) + + self.ROOT_ARCHETYPE = archetype_create(self, {}, "") + + for i = 1, HI_COMPONENT_ID do + local e = entity_index_new_id(entity_index) + world_add(self, e, EcsComponent) + end + + for i = HI_COMPONENT_ID + 1, EcsRest do + -- Initialize built-in components + entity_index_new_id(entity_index) + end + + world_add(self, EcsName, EcsComponent) + world_add(self, EcsOnChange, EcsComponent) + world_add(self, EcsOnAdd, EcsComponent) + world_add(self, EcsOnRemove, EcsComponent) + world_add(self, EcsWildcard, EcsComponent) + world_add(self, EcsRest, EcsComponent) + + world_set(self, EcsOnAdd, EcsName, "jecs.OnAdd") + world_set(self, EcsOnRemove, EcsName, "jecs.OnRemove") + world_set(self, EcsOnChange, EcsName, "jecs.OnChange") + world_set(self, EcsWildcard, EcsName, "jecs.Wildcard") + world_set(self, EcsChildOf, EcsName, "jecs.ChildOf") + world_set(self, EcsComponent, EcsName, "jecs.Component") + world_set(self, EcsOnDelete, EcsName, "jecs.OnDelete") + world_set(self, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget") + world_set(self, EcsDelete, EcsName, "jecs.Delete") + world_set(self, EcsRemove, EcsName, "jecs.Remove") + world_set(self, EcsName, EcsName, "jecs.Name") + world_set(self, EcsRest, EcsRest, "jecs.Rest") + + world_add(self, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) + + for i = EcsRest + 1, ecs_max_tag_id do + entity_index_new_id(entity_index) + end + + for i, bundle in pairs(ecs_metadata) do + for ty, value in bundle do + if value == NULL then + world_add(self, i, ty) + else + world_set(self, i, ty, value) + end + end + end + + return self +end + +World.new = world_new + +-- type function ecs_id_t(entity) +-- local ty = entity:components()[2] +-- local __T = ty:readproperty(types.singleton("__T")) +-- if not __T then +-- return ty:readproperty(types.singleton("__jecs_pair_value")) +-- end +-- return __T +-- end + +-- type function ecs_pair_t(first, second) +-- if ecs_id_t(first):is("nil") then +-- return second +-- else +-- return first +-- end +-- end +-- + +return { + World = World, + world = world_new, + component = ECS_COMPONENT, + tag = ECS_TAG, + meta = ECS_META, + is_tag = ecs_is_tag, + OnAdd = EcsOnAdd, + OnRemove = EcsOnRemove, + OnChange = EcsOnChange, + Wildcard = EcsWildcard, + OnDelete = EcsOnDelete, + OnDeleteTarget = EcsOnDeleteTarget, + Delete = EcsDelete, + Name = EcsName, + Rest = EcsRest, + pair = ECS_PAIR, + -- Inwards facing API for testing + ECS_ID = ECS_ENTITY_T_LO, + ECS_GENERATION_INC = ECS_GENERATION_INC, + ECS_GENERATION = ECS_GENERATION, + ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD, + ECS_ID_DELETE = ECS_ID_DELETE, + ECS_META_RESET = ECS_META_RESET, + + -- IS_PAIR = (ECS_IS_PAIR :: any) :: (pair: Pair) -> boolean, + -- ECS_PAIR_FIRST = ECS_PAIR_FIRST :: (pair: Pair) -> Id

, + -- ECS_PAIR_SECOND = ECS_PAIR_SECOND :: (pair: Pair) -> Id, + -- pair_first = (ecs_pair_first :: any) :: (world: World, pair: Pair) -> Id

, + -- pair_second = (ecs_pair_second :: any) :: (world: World, pair: Pair) -> Id, + entity_index_get_alive = entity_index_get_alive, + + archetype_append_to_records = archetype_append_to_records, + id_record_ensure = id_record_ensure, + archetype_create = archetype_create, + archetype_ensure = archetype_ensure, + find_insert = find_insert, + find_archetype_with = find_archetype_with, + find_archetype_without = find_archetype_without, + create_edge_for_remove = create_edge_for_remove, + archetype_traverse_add = archetype_traverse_add, + archetype_traverse_remove = archetype_traverse_remove, + + entity_move = entity_move, + + entity_index_try_get = entity_index_try_get, + entity_index_try_get_any = entity_index_try_get_any, + entity_index_try_get_fast = entity_index_try_get_fast, + entity_index_is_alive = entity_index_is_alive, + entity_index_new_id = entity_index_new_id, + + query_iter = query_iter, + query_iter_init = query_iter_init, + query_with = query_with, + query_without = query_without, + query_archetypes = query_archetypes, + query_match = query_match, + + find_observers = find_observers, +} diff --git a/mirror.luau b/mirror.luau deleted file mode 100644 index c2ceac6..0000000 --- a/mirror.luau +++ /dev/null @@ -1,659 +0,0 @@ ---!optimize 2 ---!native ---!strict ---draft 4 - -type i53 = number -type i24 = number - -type Ty = { i53 } -type ArchetypeId = number - -type Column = { any } - -type Archetype = { - id: number, - edges: { - [i24]: { - add: Archetype, - remove: Archetype, - }, - }, - types: Ty, - type: string | number, - entities: { number }, - columns: { Column }, - records: {}, -} - -type Record = { - archetype: Archetype, - row: number, -} - -type EntityIndex = { [i24]: Record } -type ComponentIndex = { [i24]: ArchetypeMap } - -type ArchetypeRecord = number -type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord }, size: number } -type Archetypes = { [ArchetypeId]: Archetype } - -type ArchetypeDiff = { - added: Ty, - removed: Ty, -} - -local HI_COMPONENT_ID = 256 -local ON_ADD = HI_COMPONENT_ID + 1 -local ON_REMOVE = HI_COMPONENT_ID + 2 -local ON_SET = HI_COMPONENT_ID + 3 -local REST = HI_COMPONENT_ID + 4 - -local function transitionArchetype( - entityIndex: EntityIndex, - to: Archetype, - destinationRow: i24, - from: Archetype, - sourceRow: i24 -) - local columns = from.columns - local sourceEntities = from.entities - local destinationEntities = to.entities - local destinationColumns = to.columns - local tr = to.records - local types = from.types - - for i, column in columns do - -- Retrieves the new column index from the source archetype's record from each component - -- We have to do this because the columns are tightly packed and indexes may not correspond to each other. - local targetColumn = destinationColumns[tr[types[i]]] - - -- Sometimes target column may not exist, e.g. when you remove a component. - if targetColumn then - targetColumn[destinationRow] = column[sourceRow] - end - -- If the entity is the last row in the archetype then swapping it would be meaningless. - local last = #column - if sourceRow ~= last then - -- Swap rempves columns to ensure there are no holes in the archetype. - column[sourceRow] = column[last] - end - column[last] = nil - end - - -- Move the entity from the source to the destination archetype. - local atSourceRow = sourceEntities[sourceRow] - destinationEntities[destinationRow] = atSourceRow - entityIndex[atSourceRow].row = destinationRow - - -- Because we have swapped columns we now have to update the records - -- corresponding to the entities' rows that were swapped. - local movedAway = #sourceEntities - if sourceRow ~= movedAway then - local atMovedAway = sourceEntities[movedAway] - sourceEntities[sourceRow] = atMovedAway - entityIndex[atMovedAway].row = sourceRow - end - - sourceEntities[movedAway] = nil -end - -local function archetypeAppend(entity: number, archetype: Archetype): number - local entities = archetype.entities - local length = #entities + 1 - entities[length] = entity - return length -end - -local function newEntity(entityId: i53, record: Record, archetype: Archetype) - local row = archetypeAppend(entityId, archetype) - record.archetype = archetype - record.row = row - return record -end - -local function moveEntity(entityIndex, entityId: i53, record: Record, to: Archetype) - local sourceRow = record.row - local from = record.archetype - local destinationRow = archetypeAppend(entityId, to) - transitionArchetype(entityIndex, to, destinationRow, from, sourceRow) - record.archetype = to - record.row = destinationRow -end - -local function hash(arr): string | number - return table.concat(arr, "_") -end - -local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?) - local destinationIds = to.types - local records = to.records - local id = to.id - - for i, destinationId in destinationIds do - local archetypesMap = componentIndex[destinationId] - - if not archetypesMap then - archetypesMap = { size = 0, sparse = {} } - componentIndex[destinationId] = archetypesMap - end - - archetypesMap.sparse[id] = i - records[destinationId] = i - end -end - -local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype - local ty = hash(types) - - local id = world.nextArchetypeId + 1 - world.nextArchetypeId = id - - local length = #types - local columns = table.create(length) :: { any } - - for index in types do - columns[index] = {} - end - - local archetype = { - columns = columns, - edges = {}, - entities = {}, - id = id, - records = {}, - type = ty, - types = types, - } - world.archetypeIndex[ty] = archetype - world.archetypes[id] = archetype - if length > 0 then - createArchetypeRecords(world.componentIndex, archetype, prev) - end - - return archetype -end - -local World = {} -World.__index = World -function World.new() - local self = setmetatable({ - archetypeIndex = {}, - archetypes = {}, - componentIndex = {}, - entityIndex = {}, - hooks = { - [ON_ADD] = {}, - }, - nextArchetypeId = 0, - nextComponentId = 0, - nextEntityId = 0, - ROOT_ARCHETYPE = (nil :: any) :: Archetype, - }, World) - return self -end - -local function emit(world, eventDescription) - local event = eventDescription.event - - table.insert(world.hooks[event], { - archetype = eventDescription.archetype, - ids = eventDescription.ids, - offset = eventDescription.offset, - otherArchetype = eventDescription.otherArchetype, - }) -end - -local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty) - if #added > 0 then - emit(world, { - archetype = archetype, - event = ON_ADD, - ids = added, - offset = row, - otherArchetype = otherArchetype, - }) - end -end - -export type World = typeof(World.new()) - -local function ensureArchetype(world: World, types, prev) - if #types < 1 then - return world.ROOT_ARCHETYPE - end - - local ty = hash(types) - local archetype = world.archetypeIndex[ty] - if archetype then - return archetype - end - - return archetypeOf(world, types, prev) -end - -local function findInsert(types: { i53 }, toAdd: i53) - for i, id in types do - if id == toAdd then - return -1 - end - if id > toAdd then - return i - end - end - return #types + 1 -end - -local function findArchetypeWith(world: World, node: Archetype, componentId: i53) - local types = node.types - -- Component IDs are added incrementally, so inserting and sorting - -- them each time would be expensive. Instead this insertion sort can find the insertion - -- point in the types array. - local at = findInsert(types, componentId) - if at == -1 then - -- If it finds a duplicate, it just means it is the same archetype so it can return it - -- directly instead of needing to hash types for a lookup to the archetype. - return node - end - - local destinationType = table.clone(node.types) - table.insert(destinationType, at, componentId) - return ensureArchetype(world, destinationType, node) -end - -local function ensureEdge(archetype: Archetype, componentId: i53) - local edges = archetype.edges - local edge = edges[componentId] - if not edge then - edge = {} :: any - edges[componentId] = edge - end - return edge -end - -local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype - if not from then - -- If there was no source archetype then it should return the ROOT_ARCHETYPE - local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE - if not ROOT_ARCHETYPE then - ROOT_ARCHETYPE = archetypeOf(world, {}, nil) - world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never - end - from = ROOT_ARCHETYPE - end - - local edge = ensureEdge(from, componentId) - local add = edge.add - if not add then - -- Save an edge using the component ID to the archetype to allow - -- faster traversals to adjacent archetypes. - add = findArchetypeWith(world, from, componentId) - edge.add = add :: never - end - - return add -end - -local function ensureRecord(entityIndex, entityId: i53): Record - local record = entityIndex[entityId] - - if not record then - record = {} - entityIndex[entityId] = record - end - - return record :: Record -end - -function World.set(world: World, entityId: i53, componentId: i53, data: unknown) - local record = ensureRecord(world.entityIndex, entityId) - local from = record.archetype - local to = archetypeTraverseAdd(world, componentId, from) - - if from == to then - -- If the archetypes are the same it can avoid moving the entity - -- and just set the data directly. - local archetypeRecord = to.records[componentId] - from.columns[archetypeRecord][record.row] = data - -- Should fire an OnSet event here. - return - end - - if from then - -- If there was a previous archetype, then the entity needs to move the archetype - moveEntity(world.entityIndex, entityId, record, to) - else - if #to.types > 0 then - -- When there is no previous archetype it should create the archetype - newEntity(entityId, record, to) - onNotifyAdd(world, to, from, record.row, { componentId }) - end - end - - local archetypeRecord = to.records[componentId] - to.columns[archetypeRecord][record.row] = data -end - -local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype - local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype - local edge = ensureEdge(from, componentId) - - local remove = edge.remove - if not remove then - local to = table.clone(from.types) - table.remove(to, table.find(to, componentId)) - remove = ensureArchetype(world, to, from) - edge.remove = remove :: never - end - - return remove -end - -function World.remove(world: World, entityId: i53, componentId: i53) - local entityIndex = world.entityIndex - local record = ensureRecord(entityIndex, entityId) - local sourceArchetype = record.archetype - local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) - - if sourceArchetype and not (sourceArchetype == destinationArchetype) then - moveEntity(entityIndex, entityId, record, destinationArchetype) - end -end - --- Keeping the function as small as possible to enable inlining -local function get(record: Record, componentId: i24) - local archetype = record.archetype - local archetypeRecord = archetype.records[componentId] - - if not archetypeRecord then - return nil - end - - return archetype.columns[archetypeRecord][record.row] -end - -function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) - local id = entityId - local record = world.entityIndex[id] - if not record then - return nil - end - - local va = get(record, a) - - if b == nil then - return va - elseif c == nil then - return va, get(record, b) - elseif d == nil then - return va, get(record, b), get(record, c) - elseif e == nil then - return va, get(record, b), get(record, c), get(record, d) - else - error("args exceeded") - end -end - --- the less creation the better -local function actualNoOperation() end -local function noop(_self: Query, ...: i53): () -> (number, ...any) - return actualNoOperation :: any -end - -local EmptyQuery = { - __iter = noop, - without = noop, -} -EmptyQuery.__index = EmptyQuery -setmetatable(EmptyQuery, EmptyQuery) - -export type Query = typeof(EmptyQuery) - -function World.query(world: World, ...: i53): Query - -- breaking? - if (...) == nil then - error("Missing components") - end - - local compatibleArchetypes = {} - local length = 0 - - local components = { ... } - local archetypes = world.archetypes - local queryLength = #components - - local firstArchetypeMap - local componentIndex = world.componentIndex - - for _, componentId in components do - local map = componentIndex[componentId] - if not map then - return EmptyQuery - end - - if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then - firstArchetypeMap = map - end - end - - for id in firstArchetypeMap.sparse do - local archetype = archetypes[id] - local archetypeRecords = archetype.records - local indices = {} - local skip = false - - for i, componentId in components do - local index = archetypeRecords[componentId] - if not index then - skip = true - break - end - indices[i] = index - end - - if skip then - continue - end - - length += 1 - compatibleArchetypes[length] = { archetype, indices } - end - - local lastArchetype, compatibleArchetype = next(compatibleArchetypes) - if not lastArchetype then - return EmptyQuery - end - - local preparedQuery = {} - preparedQuery.__index = preparedQuery - - function preparedQuery:without(...) - local withoutComponents = { ... } - for i = #compatibleArchetypes, 1, -1 do - local archetype = compatibleArchetypes[i][1] - local records = archetype.records - local shouldRemove = false - - for _, componentId in withoutComponents do - if records[componentId] then - shouldRemove = true - break - end - end - - if shouldRemove then - table.remove(compatibleArchetypes, i) - end - end - - lastArchetype, compatibleArchetype = next(compatibleArchetypes) - if not lastArchetype then - return EmptyQuery - end - - return self - end - - local lastRow - local queryOutput = {} - - function preparedQuery:__iter() - return function() - local archetype = compatibleArchetype[1] - local row = next(archetype.entities, lastRow) - while row == nil do - lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) - if lastArchetype == nil then - return - end - archetype = compatibleArchetype[1] - row = next(archetype.entities, row) - end - lastRow = row - - local entityId = archetype.entities[row :: number] - local columns = archetype.columns - local tr = compatibleArchetype[2] - - if queryLength == 1 then - return entityId, columns[tr[1]][row] - elseif queryLength == 2 then - return entityId, columns[tr[1]][row], columns[tr[2]][row] - elseif queryLength == 3 then - return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row] - elseif queryLength == 4 then - return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row] - elseif queryLength == 5 then - return entityId, - columns[tr[1]][row], - columns[tr[2]][row], - columns[tr[3]][row], - columns[tr[4]][row], - columns[tr[5]][row] - elseif queryLength == 6 then - return entityId, - columns[tr[1]][row], - columns[tr[2]][row], - columns[tr[3]][row], - columns[tr[4]][row], - columns[tr[5]][row], - columns[tr[6]][row] - elseif queryLength == 7 then - return entityId, - columns[tr[1]][row], - columns[tr[2]][row], - columns[tr[3]][row], - columns[tr[4]][row], - columns[tr[5]][row], - columns[tr[6]][row], - columns[tr[7]][row] - elseif queryLength == 8 then - return entityId, - columns[tr[1]][row], - columns[tr[2]][row], - columns[tr[3]][row], - columns[tr[4]][row], - columns[tr[5]][row], - columns[tr[6]][row], - columns[tr[7]][row], - columns[tr[8]][row] - end - - for i in components do - queryOutput[i] = columns[tr[i]][row] - end - - return entityId, unpack(queryOutput, 1, queryLength) - end - end - - return setmetatable({}, preparedQuery) :: any -end - -function World.component(world: World) - local componentId = world.nextComponentId + 1 - if componentId > HI_COMPONENT_ID then - -- IDs are partitioned into ranges because component IDs are not nominal, - -- so it needs to error when IDs intersect into the entity range. - error("Too many components, consider using world:entity() instead to create components.") - end - world.nextComponentId = componentId - return componentId -end - -function World.entity(world: World) - local nextEntityId = world.nextEntityId + 1 - world.nextEntityId = nextEntityId - return nextEntityId + REST -end - -function World.delete(world: World, entityId: i53) - local entityIndex = world.entityIndex - local record = entityIndex[entityId] - moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE) - -- Since we just appended an entity to the ROOT_ARCHETYPE we have to remove it from - -- the entities array and delete the record. We know there won't be the hole since - -- we are always removing the last row. - --world.ROOT_ARCHETYPE.entities[record.row] = nil - --entityIndex[entityId] = nil -end - -function World.observer(world: World, ...) - local componentIds = { ... } - local idsCount = #componentIds - local hooks = world.hooks - - return { - event = function(event) - local hook = hooks[event] - hooks[event] = nil - - local last, change - return function() - last, change = next(hook, last) - if not last then - return - end - - local matched = false - local ids = change.ids - - while not matched do - local skip = false - for _, id in ids do - if not table.find(componentIds, id) then - skip = true - break - end - end - - if skip then - last, change = next(hook, last) - ids = change.ids - continue - end - - matched = true - end - - local queryOutput = table.create(idsCount) - local row = change.offset - local archetype = change.archetype - local columns = archetype.columns - local archetypeRecords = archetype.records - for index, id in componentIds do - queryOutput[index] = columns[archetypeRecords[id]][row] - end - - return archetype.entities[row], unpack(queryOutput, 1, idsCount) - end - end, - } -end - -return table.freeze({ - World = World, - ON_ADD = ON_ADD, - ON_REMOVE = ON_REMOVE, - ON_SET = ON_SET, -}) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index ee2861d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,4464 +0,0 @@ -{ - "name": "@rbxts/jecs", - "version": "0.6.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@rbxts/jecs", - "version": "0.6.0", - "license": "MIT", - "devDependencies": { - "@rbxts/compiler-types": "^2.3.0-types.1", - "@rbxts/types": "^1.0.781", - "@typescript-eslint/eslint-plugin": "^5.8.0", - "@typescript-eslint/parser": "^5.8.0", - "eslint": "^8.5.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-roblox-ts": "^0.0.32", - "prettier": "^2.5.1", - "roblox-ts": "^3.0.0", - "typescript": "^5.4.2", - "vitepress": "^1.3.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", - "@algolia/autocomplete-shared": "1.17.7" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "dev": true, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.23.0.tgz", - "integrity": "sha512-AyZ+9CUgWXwaaJ2lSwOJSy+/w0MFBPFqLrjWYs/HEpYMzBuFfGNZ7gEM9a7h4j7jY8hSBARBl8qdvInmj5vOEQ==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.23.0.tgz", - "integrity": "sha512-oeKCPwLBnTEPF/RWr0aaJnrfRDfFRLT5O7KV0OF1NmpEXvmzLmN7RwnwDKsNtPUHNfpJ6esP9xzkPEtJabrZ2w==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.23.0.tgz", - "integrity": "sha512-9jacdC44vXLSaYKNLkFpbU1J4BbBPi/N7uoPhcGO//8ubRuVzigH6+RfK5FbudmQlqFt0J5DGUCVeTlHtgyUeg==", - "dev": true, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.23.0.tgz", - "integrity": "sha512-/Gw5UitweRsnyb24Td4XhjXmsx8PxFzCI0oW6FZZvyr4kjzB9ECP2IjO+PdDq1A2fzDl/LXQ+u8ROudoVnXnQg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.23.0.tgz", - "integrity": "sha512-ivrEZBoXfDatpqpifgHauydxHEe4udNqJ0gy7adR2KODeQ+39MQeaT10I24mu+eylIuiQKJRqORgEdLZycq2qQ==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.23.0.tgz", - "integrity": "sha512-DjSgJWqTcsnlXEKqDsU7Y2vB/W/VYLlr6UfkzJkMuKB554Ia7IJr4keP2AlHVjjbBG62IDpdh5OkEs/+fbWsOA==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.23.0.tgz", - "integrity": "sha512-XAYWUYUhEG4OIdo/N7H/OFFRD9fokfv3bBTky+4Y4/q07bxhnrGSUvcrU6JQ2jJTQyg6kv0ke1EIfiTO/Xxb+g==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/ingestion": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.23.0.tgz", - "integrity": "sha512-ULbykzzhhLVofCDU1m/CqSzTyKmjaxA/z1d6o6hgUuR6X7/dll9/G0lu0e4vmWIOItklWWrhU2V8sXD0YGBIHg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.23.0.tgz", - "integrity": "sha512-oB3wG7CgQJQr+uoijV7bWBphiSHkvGX43At8RGgkDyc7Aeabcp9ik5HgLC1YDgbHVOlQI+tce5HIbDCifzQCIg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.23.0.tgz", - "integrity": "sha512-4PWvCV6VGhnCMAbv2zfQUAlc3ofMs6ovqKlC/xcp7tWaucYd//piHg9CcCM4S0p9OZznEGQMRYPt2uqbk6V9vg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.23.0.tgz", - "integrity": "sha512-bacOsX41pnsupNB0k0Ny+1JDchQxIsZIcp69GKDBT0NgTHG8OayEO141eFalNmGil+GXPY0NUPRpx+5s4RdhGA==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.23.0.tgz", - "integrity": "sha512-tVNFREexJWDrvc23evmRgAcb2KLZuVilOIB/rVnQCl0GDbqIWJuQ1lG22HKqvCEQFthHkgVFGLYE74wQ96768g==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.23.0.tgz", - "integrity": "sha512-XXHbq2heOZc9EFCc4z+uyHS9YRBygZbYQVsWjWZWx8hdAz+tkBX/jLHM9Xg+3zO0/v8JN6pcZzqYEVsdrLeNLg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", - "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", - "dev": true - }, - "node_modules/@docsearch/js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", - "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", - "dev": true, - "dependencies": { - "@docsearch/react": "3.8.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", - "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-core": "1.17.7", - "@algolia/autocomplete-preset-algolia": "1.17.7", - "@docsearch/css": "3.8.2", - "algoliasearch": "^5.14.2" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@iconify-json/simple-icons": { - "version": "1.2.29", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.29.tgz", - "integrity": "sha512-KYrxmxtRz6iOAulRiUsIBMUuXek+H+Evwf8UvYPIkbQ+KDoOqTegHx3q/w3GDDVC0qJYB+D3hXPMZcpm78qIuA==", - "dev": true, - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rbxts/compiler-types": { - "version": "2.3.0-types.2", - "resolved": "https://registry.npmjs.org/@rbxts/compiler-types/-/compiler-types-2.3.0-types.2.tgz", - "integrity": "sha512-ZfCn1LNrfMS0GCfnXvLVOPE9oNNBf5851JRBGy0ptuuOK1bDvV0fljP0Xx9w6LcLFb+NLTM2GKXX+gkoCh7CvA==", - "dev": true - }, - "node_modules/@rbxts/types": { - "version": "1.0.844", - "resolved": "https://registry.npmjs.org/@rbxts/types/-/types-1.0.844.tgz", - "integrity": "sha512-qiUe7FDF8hyY2oxD+MgShgw4irAGethZWxy3APll4xd4IA9RuPo/shJknYoHvTA8XRXgxlKdT6wV1D3hek0YcQ==", - "dev": true - }, - "node_modules/@roblox-ts/luau-ast": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@roblox-ts/luau-ast/-/luau-ast-2.0.0.tgz", - "integrity": "sha512-cmMi093IdwBOLVxwuordhM8AmtbyTIyRpsTbB0D/JauidW4SXsQRQowSwWjHo4QP0DRJBXvOIlxtqEQi50uNzQ==", - "dev": true - }, - "node_modules/@roblox-ts/path-translator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@roblox-ts/path-translator/-/path-translator-1.1.0.tgz", - "integrity": "sha512-D0akTmnNYqBw+ZIek5JxocT3BjmbgGOuOy0x1nIIxHBPNLGCpzseToY8jyYs/0mlvnN2xnSP/k8Tv+jvGOQSwQ==", - "dev": true, - "dependencies": { - "ajv": "^8.12.0", - "fs-extra": "^11.2.0" - } - }, - "node_modules/@roblox-ts/path-translator/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@roblox-ts/path-translator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/@roblox-ts/rojo-resolver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@roblox-ts/rojo-resolver/-/rojo-resolver-1.1.0.tgz", - "integrity": "sha512-QmvVryu1EeME+3QUoG5j/gHGJoJUaffCgZ92mhlG7cJSd1uyhgpY4CNWriZAwZJYkTlzd5Htkpn+18yDFbOFXA==", - "dev": true, - "dependencies": { - "ajv": "^8.17.1", - "fs-extra": "^11.2.0" - } - }, - "node_modules/@roblox-ts/rojo-resolver/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@roblox-ts/rojo-resolver/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz", - "integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz", - "integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz", - "integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz", - "integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz", - "integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz", - "integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz", - "integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz", - "integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz", - "integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz", - "integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz", - "integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz", - "integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz", - "integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz", - "integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz", - "integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz", - "integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz", - "integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz", - "integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz", - "integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz", - "integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", - "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", - "dev": true, - "dependencies": { - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", - "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", - "dev": true, - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^3.1.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", - "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", - "dev": true, - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", - "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", - "dev": true, - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", - "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", - "dev": true, - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/transformers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", - "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", - "dev": true, - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/types": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", - "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", - "dev": true, - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.18.126", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", - "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", - "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.13", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", - "dev": true, - "dependencies": { - "@vue/compiler-core": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.13", - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.48", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz", - "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==", - "dev": true, - "dependencies": { - "@vue/devtools-kit": "^7.7.2" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", - "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", - "dev": true, - "dependencies": { - "@vue/devtools-shared": "^7.7.2", - "birpc": "^0.2.19", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.1" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", - "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", - "dev": true, - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dev": true, - "dependencies": { - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", - "dev": true, - "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", - "dev": true, - "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/runtime-core": "3.5.13", - "@vue/shared": "3.5.13", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", - "dev": true, - "dependencies": { - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13" - }, - "peerDependencies": { - "vue": "3.5.13" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true - }, - "node_modules/@vueuse/core": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", - "dev": true, - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", - "dev": true, - "dependencies": { - "@vueuse/core": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^5", - "drauu": "^0.4", - "focus-trap": "^7", - "fuse.js": "^7", - "idb-keyval": "^6", - "jwt-decode": "^4", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^7" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", - "dev": true, - "dependencies": { - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/algoliasearch": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.23.0.tgz", - "integrity": "sha512-7TCj+hLx6fZKppLL74lYGDEltSBNSu4vqRwgqeIKZ3VQ0q3aOrdEN0f1sDWcvU1b+psn2wnl7aHt9hWtYatUUA==", - "dev": true, - "dependencies": { - "@algolia/client-abtesting": "5.23.0", - "@algolia/client-analytics": "5.23.0", - "@algolia/client-common": "5.23.0", - "@algolia/client-insights": "5.23.0", - "@algolia/client-personalization": "5.23.0", - "@algolia/client-query-suggestions": "5.23.0", - "@algolia/client-search": "5.23.0", - "@algolia/ingestion": "1.23.0", - "@algolia/monitoring": "1.23.0", - "@algolia/recommend": "5.23.0", - "@algolia/requester-browser-xhr": "5.23.0", - "@algolia/requester-fetch": "5.23.0", - "@algolia/requester-node-http": "5.23.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/birpc": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", - "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/copy-anything": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", - "dev": true, - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "dev": true - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-roblox-ts": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/eslint-plugin-roblox-ts/-/eslint-plugin-roblox-ts-0.0.32.tgz", - "integrity": "sha512-zbwahPiQha5KGwY/J3pVXtyR4ORBSP8qouc4DGfnyGcdz0HOFFu+sACWX2u7/c4HVymtZlKRkTL4uR5qZ+THgg==", - "dev": true, - "dependencies": { - "@types/node": "^16.10.4", - "@typescript-eslint/experimental-utils": "^5.0.0", - "typescript": "^4.4.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-roblox-ts/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true - }, - "node_modules/focus-trap": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", - "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", - "dev": true, - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", - "dev": true, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minisearch": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.2.tgz", - "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", - "dev": true - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/oniguruma-to-es": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", - "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", - "dev": true, - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.26.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.4.tgz", - "integrity": "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/property-information": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", - "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", - "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", - "dev": true, - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dev": true, - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/roblox-ts": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/roblox-ts/-/roblox-ts-3.0.0.tgz", - "integrity": "sha512-hwAC2frIFlLJOtHd6F+5opMEhBgfAMK9z5l1mP+bykLBbMO5cn1q5lIwhhXbeh9Pq07rlhF8uGHlmeRLPd/3AA==", - "dev": true, - "dependencies": { - "@roblox-ts/luau-ast": "=2.0.0", - "@roblox-ts/path-translator": "=1.1.0", - "@roblox-ts/rojo-resolver": "=1.1.0", - "chokidar": "^3.6.0", - "fs-extra": "^11.2.0", - "kleur": "^4.1.5", - "resolve": "^1.22.6", - "typescript": "=5.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "rbxtsc": "out/CLI/cli.js" - } - }, - "node_modules/roblox-ts/node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/rollup": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz", - "integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.37.0", - "@rollup/rollup-android-arm64": "4.37.0", - "@rollup/rollup-darwin-arm64": "4.37.0", - "@rollup/rollup-darwin-x64": "4.37.0", - "@rollup/rollup-freebsd-arm64": "4.37.0", - "@rollup/rollup-freebsd-x64": "4.37.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", - "@rollup/rollup-linux-arm-musleabihf": "4.37.0", - "@rollup/rollup-linux-arm64-gnu": "4.37.0", - "@rollup/rollup-linux-arm64-musl": "4.37.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", - "@rollup/rollup-linux-riscv64-gnu": "4.37.0", - "@rollup/rollup-linux-riscv64-musl": "4.37.0", - "@rollup/rollup-linux-s390x-gnu": "4.37.0", - "@rollup/rollup-linux-x64-gnu": "4.37.0", - "@rollup/rollup-linux-x64-musl": "4.37.0", - "@rollup/rollup-win32-arm64-msvc": "4.37.0", - "@rollup/rollup-win32-ia32-msvc": "4.37.0", - "@rollup/rollup-win32-x64-msvc": "4.37.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, - "peer": true - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", - "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", - "dev": true, - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/langs": "2.5.0", - "@shikijs/themes": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superjson": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", - "dev": true, - "dependencies": { - "copy-anything": "^3.0.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vitepress": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.3.tgz", - "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", - "dev": true, - "dependencies": { - "@docsearch/css": "3.8.2", - "@docsearch/js": "3.8.2", - "@iconify-json/simple-icons": "^1.2.21", - "@shikijs/core": "^2.1.0", - "@shikijs/transformers": "^2.1.0", - "@shikijs/types": "^2.1.0", - "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/devtools-api": "^7.7.0", - "@vue/shared": "^3.5.13", - "@vueuse/core": "^12.4.0", - "@vueuse/integrations": "^12.4.0", - "focus-trap": "^7.6.4", - "mark.js": "8.11.1", - "minisearch": "^7.1.1", - "shiki": "^2.1.0", - "vite": "^5.4.14", - "vue": "^3.5.13" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4", - "postcss": "^8" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vitepress/node_modules/@types/node": { - "version": "22.13.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", - "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/vitepress/node_modules/@vitejs/plugin-vue": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", - "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", - "dev": true, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/vitepress/node_modules/vite": { - "version": "5.4.15", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz", - "integrity": "sha512-6ANcZRivqL/4WtwPGTKNaosuNJr5tWiftOC7liM7G9+rMb8+oeJeyzymDu4rTN93seySBmbjSfsS3Vzr19KNtA==", - "dev": true, - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-sfc": "3.5.13", - "@vue/runtime-dom": "3.5.13", - "@vue/server-renderer": "3.5.13", - "@vue/shared": "3.5.13" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index f3cf774..0000000 --- a/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@rbxts/jecs", - "version": "0.6.0", - "description": "Stupidly fast Entity Component System", - "main": "jecs.luau", - "repository": { - "type": "git", - "url": "git+https://github.com/ukendio/jecs.git" - }, - "keywords": [], - "author": "Ukendio", - "contributors": [ - "Ukendio", - "EncodedVenom" - ], - "homepage": "https://github.com/ukendio/jecs", - "license": "MIT", - "types": "jecs.d.ts", - "files": [ - "jecs.luau", - "jecs.d.ts", - "LICENSE.md", - "README.md" - ], - "publishConfig": { - "access": "public" - }, - "devDependencies": { - "@rbxts/compiler-types": "^2.3.0-types.1", - "@rbxts/types": "^1.0.781", - "@typescript-eslint/eslint-plugin": "^5.8.0", - "@typescript-eslint/parser": "^5.8.0", - "eslint": "^8.5.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-roblox-ts": "^0.0.32", - "prettier": "^2.5.1", - "roblox-ts": "^3.0.0", - "typescript": "^5.4.2", - "vitepress": "^1.3.0" - }, - "scripts": { - "docs:dev": "vitepress dev docs", - "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" - } -} diff --git a/rokit.toml b/rokit.toml deleted file mode 100644 index 0076059..0000000 --- a/rokit.toml +++ /dev/null @@ -1,6 +0,0 @@ -[tools] -wally = "upliftgames/wally@0.3.2" -rojo = "rojo-rbx/rojo@7.4.4" -stylua = "johnnymorganz/stylua@2.0.1" -Blink = "1Axen/Blink@0.14.1" -wally-package-types = "JohnnyMorganz/wally-package-types@1.4.2" diff --git a/test/addons/observers.luau b/test/addons/observers.luau deleted file mode 100644 index 182a3ea..0000000 --- a/test/addons/observers.luau +++ /dev/null @@ -1,136 +0,0 @@ -local jecs = require("@jecs") -local testkit = require("@testkit") -local test = testkit.test() -local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK -local observers_add = require("@addons/observers") - -TEST("addons/observers", function() - local world = observers_add(jecs.world()) - - local Test = world:component() :: jecs.Id - type Q = () -> (jecs.Entity, T...) - local query: - ((jecs.World, jecs.Id) -> Q) - & ((jecs.World, jecs.Id, jecs.Id) -> Q) - & ((jecs.World, jecs.Id, jecs.Id, jecs.Id) -> Q) - = 1 :: never - for e, a in query(world, Test) do - - end - - do CASE "Should work even if set after the component has been used" - local A = world:component() - - world:set(world:entity(), A, 2) - local ran = true - world:added(A, function() - ran = false - end) - - local entity = world:entity() - world:set(entity, A, 3) - - CHECK(ran) - end - - do CASE "Should not override hook" - local A = world:component() - - local count = 1 - local function counter() - count += 2 - end - - world:set(A, jecs.OnAdd, counter) - world:added(A, counter) - world:set(world:entity(), A, false) - CHECK(count == 3) - world:set(world:entity(), A, false) - - CHECK(count == 5) - end - - do CASE "Ensure ordering between signals and observers" - local A = world:component() - local B = world:component() - - local count = 1 - local function counter() - count += 2 - end - - world:observer(world:query(A, B), counter) - - world:added(A, counter) - world:added(A, counter) - - local e = world:entity() - world:add(e, A) - CHECK(count == 3) - - world:add(e, B) - CHECK(count == 4) - end - - do CASE "Rematch entities in observers" - local A = world:component() - - local count = 1 - local function counter() - count += 2 - end - - world:observer(world:query(A), counter) - - local e = world:entity() - world:set(e, A, false) - CHECK(count == 2) - world:remove(e, A) - CHECK(count == 2) - world:set(e, A, false) - CHECK(count == 3) - world:set(e, A, false) - CHECK(count == 4) - end - - do CASE "Don't report changed components in monitor" - local A = world:component() - local count = 1 - local function counter() - count += 2 - end - - world:monitor(world:query(A), counter) - - local e = world:entity() - world:set(e, A, false) - CHECK(count == 2) - world:remove(e, A) - CHECK(count == 3) - world:set(e, A, false) - CHECK(count == 4) - world:set(e, A, false) - CHECK(count == 4) - end - - do CASE "Call off pairs" - local A = world:component() - - local callcount = 1 - world:added(A, function(entity) - callcount += 2 - end) - world:added(A, function(entity) - callcount += 2 - end) - - local e = world:entity() - local e2 = world:entity() - - world:add(e2, jecs.pair(A, e)) - world:add(e, jecs.pair(A, e2)) - CHECK(callcount == 5) - end -end) - -return FINISH() diff --git a/test/lol.luau b/test/lol.luau deleted file mode 100644 index 0749543..0000000 --- a/test/lol.luau +++ /dev/null @@ -1,158 +0,0 @@ -local c = { - white_underline = function(s: any) - return `\27[1;4m{s}\27[0m` - end, - - white = function(s: any) - return `\27[37;1m{s}\27[0m` - end, - - green = function(s: any) - return `\27[32;1m{s}\27[0m` - end, - - red = function(s: any) - return `\27[31;1m{s}\27[0m` - end, - - yellow = function(s: any) - return `\27[33;1m{s}\27[0m` - end, - - red_highlight = function(s: any) - return `\27[41;1;30m{s}\27[0m` - end, - - green_highlight = function(s: any) - return `\27[42;1;30m{s}\27[0m` - end, - - gray = function(s: any) - return `\27[30;1m{s}\27[0m` - end, -} - - -local ECS_PAIR_FLAG = 0x8 -local ECS_ID_FLAGS_MASK = 0x10 -local ECS_ENTITY_MASK = bit32.lshift(1, 24) -local ECS_GENERATION_MASK = bit32.lshift(1, 16) - -type i53 = number -type i24 = number - -local function ECS_ENTITY_T_LO(e: i53): i24 - return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e -end - -local function ECS_GENERATION(e: i53): i24 - return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0 -end - -local ECS_ID = ECS_ENTITY_T_LO - -local function ECS_COMBINE(source: number, target: number): i53 - return (source * 268435456) + (target * ECS_ID_FLAGS_MASK) -end - -local function ECS_GENERATION_INC(e: i53) - if e > ECS_ENTITY_MASK then - local flags = e // ECS_ID_FLAGS_MASK - local id = flags // ECS_ENTITY_MASK - local generation = flags % ECS_GENERATION_MASK - - local next_gen = generation + 1 - if next_gen > ECS_GENERATION_MASK then - return id - end - - return ECS_COMBINE(id, next_gen) + flags - end - return ECS_COMBINE(e, 1) -end - -local function bl() - print("") -end - -local function pe(e) - local gen = ECS_GENERATION(e) - return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`) -end - -local function dprint(tbl: { [number]: number }) - bl() - print("--------") - for i, e in tbl do - print("| "..pe(e).." |") - print("--------") - end - bl() -end - -local max_id = 0 -local alive_count = 0 -local dense = {} -local sparse = {} -local function alloc() - if alive_count ~= #dense then - alive_count += 1 - print("*recycled", pe(dense[alive_count])) - return dense[alive_count] - end - max_id += 1 - local id = max_id - alive_count += 1 - dense[alive_count] = id - sparse[id] = { - dense = alive_count - } - print("*allocated", pe(id)) - return id -end - -local function remove(entity) - local id = ECS_ID(entity) - local r = sparse[id] - local index_of_deleted_entity = r.dense - local last_entity_alive_at_index = alive_count -- last entity alive - alive_count -= 1 - local last_alive_entity = dense[last_entity_alive_at_index] - local r_swap = sparse[ECS_ID(last_alive_entity)] - r_swap.dense = r.dense - r.dense = last_entity_alive_at_index - dense[index_of_deleted_entity] = last_alive_entity - dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity) - print("*dellocated", pe(id)) -end - -local function alive(e) - local r = sparse[ECS_ID(e)] - - return dense[r.dense] == e -end - -local function pa(e) - print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`) -end - -local tprint = require("@testkit").print -local e1v0 = alloc() -local e2v0 = alloc() -local e3v0 = alloc() -local e4v0 = alloc() -local e5v0 = alloc() -pa(e1v0) -pa(e4v0) -remove(e5v0) -pa(e5v0) - -local e5v1 = alloc() -pa(e5v0) -pa(e5v1) -pa(e2v0) -print(ECS_ID(e2v0)) - -dprint(dense) -remove(e2v0) -dprint(dense) diff --git a/test/stress.client.luau b/test/stress.client.luau deleted file mode 100644 index 9bc7775..0000000 --- a/test/stress.client.luau +++ /dev/null @@ -1,122 +0,0 @@ -local RunService = game:GetService("RunService") -local ReplicatedStorage = game:GetService("ReplicatedStorage") -_G.__JECS_HI_COMPONENT_ID = 300 -local ecs = require(ReplicatedStorage.ecs) - --- 500 entities --- 2-30 components on each entity --- 300 unique components --- 200 systems --- 1-10 components to query per system - -local startTime = os.clock() - -local world = ecs.World.new() - -local components = {} - -for i = 1, 300 do -- 300 components - components[i] = world:component() -end - -local archetypes = {} -for i = 1, 50 do -- 50 archetypes - local archetype = {} - - for _ = 1, math.random(2, 30) do - local componentId = math.random(1, #components) - - table.insert(archetype, components[componentId]) - end - - archetypes[i] = archetype -end - -for _ = 1, 1000 do -- 1000 entities in the world - local componentsToAdd = {} - - local archetypeId = math.random(1, #archetypes) - local e = world:entity() - for _, component in ipairs(archetypes[archetypeId]) do - world:set(e, component, { - DummyData = math.random(1, 5000), - }) - end -end - -local function values(t) - local array = {} - for _, v in t do - table.insert(array, v) - end - return array -end - -local contiguousComponents = values(components) -local systemComponentsToQuery = {} - -for _ = 1, 200 do -- 200 systems - local numComponentsToQuery = math.random(1, 10) - local componentsToQuery = {} - - for _ = 1, numComponentsToQuery do - table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)]) - end - - table.insert(systemComponentsToQuery, componentsToQuery) -end - -local worldCreateTime = os.clock() - startTime -local results = {} -startTime = os.clock() - -RunService.Heartbeat:Connect(function() - local added = 0 - local systemStartTime = os.clock() - debug.profilebegin("systems") - for _, componentsToQuery in ipairs(systemComponentsToQuery) do - debug.profilebegin("system") - for entityId, firstComponent in world:query(unpack(componentsToQuery)) do - world:set( - entityId, - { - DummyData = firstComponent.DummyData + 1, - } - ) - added += 1 - end - debug.profileend() - end - debug.profileend() - - if os.clock() - startTime < 4 then - -- discard first 4 seconds - return - end - - if results == nil then - return - elseif #results < 1000 then - table.insert(results, os.clock() - systemStartTime) - else - print("added", added) - print("World created in", worldCreateTime * 1000, "ms") - local sum = 0 - for _, result in ipairs(results) do - sum += result - end - print(("Average frame time: %fms"):format((sum / #results) * 1000)) - - results = nil - - local n = #world.archetypes - - print( - ("X entities\n%d components\n%d systems\n%d archetypes"):format( - #components, - #systemComponentsToQuery, - n - ) - ) - end -end) diff --git a/test/stress.project.json b/test/stress.project.json deleted file mode 100644 index 7962bad..0000000 --- a/test/stress.project.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Stress", - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "$className": "ReplicatedStorage", - "$path": "DevPackages", - "ecs": { - "$path": "jecs.luau" - } - }, - "ReplicatedFirst": { - "$className": "ReplicatedFirst", - "stres": { - "$path": "stress.client.luau" - } - } - } -} diff --git a/test/tests.luau b/test/tests.luau deleted file mode 100644 index d5e578f..0000000 --- a/test/tests.luau +++ /dev/null @@ -1,1855 +0,0 @@ -local jecs = require("@jecs") - -local testkit = require("@testkit") -local BENCH, START = testkit.benchmark() -local __ = jecs.Wildcard -local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION -local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC -local IS_PAIR = jecs.IS_PAIR -local pair = jecs.pair -local ecs_pair_first = jecs.pair_first -local ecs_pair_second = jecs.pair_second -local ChildOf = jecs.ChildOf - -local it = testkit.test() -local TEST, CASE = it.TEST, it.CASE -local CHECK, FINISH = it.CHECK, it.FINISH -local SKIP, FOCUS = it.SKIP, it.FOCUS -local CHECK_EXPECT_ERR = it.CHECK_EXPECT_ERR - -type World = jecs.World -type Entity = jecs.Entity -type Id = jecs.Id - -local entity_visualiser = require("@tools/entity_visualiser") -local lifetime_tracker_add = require("@tools/lifetime_tracker") -local dwi = entity_visualiser.stringify - -TEST("repro", function() - local Model = jecs.component() - local Relation = jecs.component() - local Relation2 = jecs.component() - - local world = jecs.world() - - local e1 = world:entity() - world:set(e1, Model, 2) - - local e2 = world:entity() - world:set(e2, Model, 2) - world:set(e2, jecs.pair(Relation, e1), 5) - - local e3 = world:entity() - world:set(e3, Model, 2) - world:set(e3, jecs.pair(Relation, e1), 5) - - world:delete(e1) - - for _ in world:query(Model) do end - jecs.ECS_META_RESET() -end) - -TEST("world:add()", function() - do CASE "idempotent" - local world = jecs.world() - local d = dwi(world) - local _1, _2 = world:component(), world:component() - - local e = world:entity() - world:add(e, _1) - world:add(e, _2) - - world:add(e, _2) -- should have 0 effects - CHECK(d.archetype(e) == "1_2") - end - - do CASE("archetype move") - local world = jecs.World.new() - - local d = dwi(world) - - local _1 = world:component() - local e = world:entity() - -- An entity starts without an archetype or row - -- should therefore not need to copy over data - CHECK(d.tbl(e) == nil) - CHECK(d.row(e) == nil) - - local archetypes = #world.archetypes - -- This should create a new archetype - world:add(e, _1) - CHECK(#world.archetypes == archetypes + 1) - - CHECK(d.archetype(e) == "1") - CHECK(d.tbl(e)) - end -end) - -TEST("world:children()", function() - local world = jecs.world() - local C = jecs.component() - local T = jecs.tag() - - local e1 = world:entity() - world:set(e1, C, true) - - local e2 = world:entity() - - world:add(e2, T) - world:add(e2, pair(ChildOf, e1)) - - local e3 = world:entity() - world:add(e3, pair(ChildOf, e1)) - - local count = 0 - for entity in world:children(e1) do - count += 1 - if entity == e2 or entity == e3 then - CHECK(true) - continue - end - CHECK(false) - end - CHECK(count == 2) - - world:remove(e2, pair(ChildOf, e1)) - - count = 0 - for entity in world:children(e1) do - count += 1 - end - - CHECK(count == 1) - - jecs.ECS_META_RESET() -end) - -TEST("world:clear()", function() - do CASE "should remove its components" - local world = jecs.world() - local A = world:component() - local B = world:component() - - local e = world:entity() - local e1 = world:entity() - local _e2 = world:entity() - - world:set(e, A, true) - world:set(e, B, true) - - world:set(e1, A, true) - world:set(e1, B, true) - - CHECK(world:get(e, A)) - CHECK(world:get(e, B)) - - world:clear(A) - CHECK(world:get(e, A) == nil) - CHECK(world:get(e, B)) - CHECK(world:get(e1, A) == nil) - CHECK(world:get(e1, B)) - end - - do CASE "remove cleared ID from entities" - local world = jecs.world() - local A = world:component() - local B = world:component() - local C = world:component() - - do - local id1 = world:entity() - local id2 = world:entity() - local id3 = world:entity() - - world:set(id1, A, true) - - world:set(id2, A, true) - world:set(id2, B, true) - - world:set(id3, A, true) - world:set(id3, B, true) - world:set(id3, C, true) - - world:clear(A) - - CHECK(not world:has(id1, A)) - CHECK(not world:has(id2, A)) - CHECK(not world:has(id3, A)) - - CHECK(world:has(id2, B)) - CHECK(world:has(id3, B, C)) - - world:clear(C) - - CHECK(world:has(id2, B)) - CHECK(world:has(id3, B)) - - CHECK(world:contains(A)) - CHECK(world:contains(C)) - CHECK(world:has(A, jecs.Component)) - CHECK(world:has(B, jecs.Component)) - end - - do - local id1 = world:entity() - local id2 = world:entity() - local id3 = world:entity() - - local tgt = world:entity() - - world:add(id1, pair(A, tgt)) - world:add(id1, pair(B, tgt)) - world:add(id1, pair(C, tgt)) - - world:add(id2, pair(A, tgt)) - world:add(id2, pair(B, tgt)) - world:add(id2, pair(C, tgt)) - - world:add(id3, pair(A, tgt)) - world:add(id3, pair(B, tgt)) - world:add(id3, pair(C, tgt)) - - world:clear(B) - CHECK(world:has(id1, pair(A, tgt), pair(C, tgt))) - CHECK(not world:has(id1, pair(B, tgt))) - CHECK(world:has(id2, pair(A, tgt), pair(C, tgt))) - CHECK(not world:has(id1, pair(B, tgt))) - CHECK(world:has(id3, pair(A, tgt), pair(C, tgt))) - - end - - end -end) - -TEST("world:component()", function() - do CASE "allow IDs to be registered" - local A = jecs.component() - local B = jecs.component() - - local world = jecs.world() - local C = world:component() - CHECK((A :: any) == 1) - CHECK((B :: any) == 2) - CHECK((C :: any) == 3) - - local e = world:entity() - - world:set(e, A, "foo") - world:set(e, B, "foo") - world:set(e, C, "foo") - - CHECK(world:has(e, A, B, C)) - - jecs.ECS_META_RESET() -- Reset the ECS metadata because they may have side effects - end - do CASE "only components should have EcsComponent trait" - local world = jecs.world() - local A = world:component() - local e = world:entity() - - CHECK(world:has(A, jecs.Component)) - CHECK(not world:has(e, jecs.Component)) - end - - do CASE "tag" - local world = jecs.world() - local A = world:component() - local B = world:entity() - local C = world:entity() - local e = world:entity() - world:set(e, A, "test") - world:add(e, B) - CHECK_EXPECT_ERR(function() - world:set(e, C, 11 :: any) - end) - - CHECK(world:has(e, A)) - CHECK(world:get(e, A) == "test") - CHECK(world:get(e, B) == nil) - CHECK(world:get(e, C) == nil) - end -end) - -TEST("world:contains()", function() - - local tag = jecs.tag() - local world = jecs.world() - local id = world:entity() - CHECK(world:contains(id)) - - do - CASE("should not exist after delete") - world:delete(id) - CHECK(not world:contains(id)) - end - - - CHECK(world:contains(tag)) - jecs.ECS_META_RESET() -end) - -TEST("world:delete()", function() - do CASE "remove pair when relationship is deleted" - local world = jecs.world() - local e1 = world:entity() - local e2 = world:entity() - - local A = world:component() - local B = world:component() - local C = world:component() - - world:add(e1, pair(A, e2)) - world:add(e1, pair(B, e2)) - world:add(e1, pair(C, e2)) - world:delete(A) - - CHECK(not world:has(e1, pair(A, e2))) - CHECK(world:has(e1, pair(B, e2))) - CHECK(world:has(e1, pair(C, e2))) - end - - do CASE "invoke OnRemove hook on all components of deleted entity" - local world = jecs.world() - - local e1 = world:entity() - local e2 = world:entity() - - local Stable = world:component() - world:set(Stable, jecs.OnRemove, function(e) - CHECK(e == e1) - end) - - world:set(e1, Stable, true) - world:set(e2, Stable, true) - - world:delete(e1) - end - - do CASE "invoke OnRemove hook on relationship if target was deleted" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local A = world:entity() - local B = world:entity() - - world:add(Relation, pair(jecs.OnDelete, jecs.Delete)) - - local entity = world:entity() - - local called = false - world:set(A, jecs.OnRemove, function(e) - called = true - end) - - world:add(entity, A) - world:add(entity, pair(Relation, B)) - - world:delete(B) - - CHECK(called) - end - - do CASE "delete recycled entity id used as component" - local world = jecs.world() - local id = world:entity() - world:add(id, jecs.Component) - - local e = world:entity() - world:set(e, id, 1) - CHECK(world:get(e, id) == 1) - world:delete(id) - local recycled = world:entity() - world:add(recycled, jecs.Component) - world:set(e, recycled, 1) - CHECK(world:has(recycled, jecs.Component)) - CHECK(world:get(e, recycled) == 1) - end - do CASE "bug: Empty entity does not respect cleanup policy" - local world = jecs.world() - local parent = world:entity() - local tag = world:entity() - - local child = world:entity() - world:add(child, jecs.pair(jecs.ChildOf, parent)) - world:delete(parent) - - CHECK(not world:contains(parent)) - CHECK(not world:contains(child)) - - local entity = world:entity() - world:add(entity, tag) - world:delete(tag) - CHECK(world:contains(entity)) - CHECK(not world:contains(tag)) - CHECK(not world:has(entity, tag)) -- => true - end - - do CASE "should allow deleting components" - local world = jecs.world() - - local Health = world:component() - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) - local id1 = world:entity() - world:set(id1, Poison, 500) - world:set(id1, Health, 50) - - world:delete(id) - CHECK(not world:contains(id)) - CHECK(world:get(id, Poison) == nil) - CHECK(world:get(id, Health) == nil) - - CHECK(world:get(id1, Poison) == 500) - CHECK(world:get(id1, Health) == 50) - end - - do CASE "delete entities using another Entity as component with Delete cleanup action" - local world = jecs.world() - - local Health = world:entity() - world:add(Health, pair(jecs.OnDelete, jecs.Delete)) - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - CHECK_EXPECT_ERR(function() - world:set(id, Health, 50 :: any) - end) - local id1 = world:entity() - world:set(id1, Poison, 500) - CHECK_EXPECT_ERR(function() - world:set(id1, Health, 50 :: any) - end) - - CHECK(world:has(id, Poison, Health)) - CHECK(world:has(id1, Poison, Health)) - world:delete(Poison) - - CHECK(world:contains(id)) - CHECK(not world:has(id, Poison)) - CHECK(not world:has(id1, Poison)) - - world:delete(Health) - CHECK(not world:contains(id)) - CHECK(not world:contains(id1)) - CHECK(not world:has(id, Health)) - CHECK(not world:has(id1, Health)) - end - - - do CASE "delete children" - local world = jecs.world() - - local Health = world:component() - local Poison = world:component() - local FriendsWith = world:component() - - local e = world:entity() - world:set(e, Poison, 5) - world:set(e, Health, 50) - - local children = {} - for i = 1, 10 do - local child = world:entity() - world:set(child, Poison, 9999) - world:set(child, Health, 100) - world:add(child, pair(jecs.ChildOf, e)) - table.insert(children, child) - end - - BENCH("delete children of entity", function() - world:delete(e) - end) - - for i, child in children do - CHECK(not world:contains(child)) - CHECK(not world:has(child, pair(jecs.ChildOf, e))) - CHECK(not world:has(child, Health)) - end - - e = world:entity() - - local friends = {} - for i = 1, 10 do - local friend = world:entity() - world:set(friend, Poison, 9999) - world:set(friend, Health, 100) - world:add(friend, pair(FriendsWith, e)) - table.insert(friends, friend) - end - - BENCH("remove friends of entity", function() - world:delete(e) - end) - - for i, friend in friends do - CHECK(not world:has(friend, pair(FriendsWith, e))) - CHECK(world:has(friend, Health)) - CHECK(world:contains(friend)) - end - end - - do CASE "remove deleted ID from entities" - local world = jecs.world() - do - local A = world:component() - local B = world:component() - local C = world:component() - local id1 = world:entity() - local id2 = world:entity() - local id3 = world:entity() - - world:set(id1, A, true) - - world:set(id2, A, true) - world:set(id2, B, true) - - world:set(id3, A, true) - world:set(id3, B, true) - world:set(id3, C, true) - - world:delete(A) - - CHECK(not world:has(id1, A)) - CHECK(not world:has(id2, A)) - CHECK(not world:has(id3, A)) - - CHECK(world:has(id2, B)) - CHECK(world:has(id3, B, C)) - - world:delete(C) - - CHECK(world:has(id2, B)) - CHECK(world:has(id3, B)) - - CHECK(not world:contains(A)) - CHECK(not world:contains(C)) - end - - do - local A = world:component() - world:add(A, pair(jecs.OnDeleteTarget, jecs.Delete)) - local B = world:component() - local C = world:component() - world:add(C, pair(jecs.OnDeleteTarget, jecs.Delete)) - - local id1 = world:entity() - local id2 = world:entity() - local id3 = world:entity() - - world:set(id1, C, true) - - world:set(id2, pair(A, id1), true) - world:set(id2, B, true) - - world:set(id3, B, true) - world:set(id3, pair(C, id2), true) - - world:delete(id1) - - CHECK(not world:contains(id1)) - CHECK(not world:contains(id2)) - CHECK(not world:contains(id3)) - end - - - do - local A = world:component() - local B = world:component() - local C = world:component() - local id1 = world:entity() - local id2 = world:entity() - local id3 = world:entity() - - - world:set(id2, A, true) - world:set(id2, pair(B, id1), true) - - world:set(id3, A, true) - world:set(id3, pair(B, id1), true) - world:set(id3, C, true) - - world:delete(id1) - - CHECK(not world:contains(id1)) - CHECK(world:contains(id2)) - CHECK(world:contains(id3)) - - CHECK(world:has(id2, A)) - CHECK(world:has(id3, A, C)) - - CHECK(not world:target(id2, B)) - CHECK(not world:target(id3, B)) - end - end - - do CASE "fast delete" - local world = jecs.World.new() - - local entities = {} - local Health = world:component() - local Poison = world:component() - - for i = 1, 100 do - local child = world:entity() - world:set(child, Poison, 9999) - world:set(child, Health, 100) - table.insert(entities, child) - end - - BENCH("simple deletion of entity", function() - for i = 1, START(100) do - local e = entities[i] - world:delete(e) - end - end) - - for _, entity in entities do - CHECK(not world:contains(entity)) - end - end - - do CASE "cycle" - local world = jecs.World.new() - local Likes = world:component() - world:add(Likes, pair(jecs.OnDeleteTarget, jecs.Delete)) - local bob = world:entity() - local alice = world:entity() - - world:add(bob, pair(Likes, alice)) - world:add(alice, pair(Likes, bob)) - - world:delete(bob) - CHECK(not world:contains(bob)) - CHECK(not world:contains(alice)) - end -end) - -TEST("world:each()", function() - local world = jecs.world() - local A = world:component() - local B = world:component() - local C = world:component() - - local e3 = world:entity() - local e1 = world:entity() - local e2 = world:entity() - - world:set(e1, A, true) - - world:set(e2, A, true) - world:set(e2, B, true) - - world:set(e3, A, true) - world:set(e3, B, true) - world:set(e3, C, true) - - for entity in world:each(A) do - if entity == e1 or entity == e2 or entity == e3 then - CHECK(true) - continue - end - CHECK(false) - end -end) - -TEST("world:range()", function() - do CASE "under range start" - local world = jecs.world() - world:range(400, 1000) - local id = world:entity() :: number - local e = world:entity(id + 5) - CHECK(e == id + 5) - CHECK(world:contains(e)) - local e2 = world:entity(399) - CHECK(world:contains(e2)) - world:delete(e2) - CHECK(not world:contains(e2)) - local e2v1 = world:entity(399) :: number - CHECK(world:contains(e2v1)) - CHECK(ECS_ID(e2v1) == 399) - CHECK(ECS_GENERATION(e2v1) == 0) - end - - do CASE "over range start" - local world = jecs.world() - world:range(400, 1000) - local e2 = world:entity(405) - CHECK(world:contains(e2)) - world:delete(e2) - CHECK(not world:contains(e2)) - local e2v1 = world:entity(405) :: number - CHECK(world:contains(e2v1)) - CHECK(ECS_ID(e2v1) == 405) - CHECK(ECS_GENERATION(e2v1) == 0) - - world:delete(e2v1) - local e2v2 = world:entity(e2v1) :: number - CHECK(ECS_ID(e2v2) == 405) - CHECK(ECS_GENERATION(e2v2) == 0) - end -end) - -TEST("world:entity()", function() - do CASE "desired id" - local world = jecs.world() - local id = world:entity() :: number - local e = world:entity(id + 5) - CHECK(e == id + 5) - CHECK(world:contains(e)) - local e2 = world:entity(399) - CHECK(world:contains(e2)) - end - local N = 2^8 - - do CASE "unique IDs" - local world = jecs.world() - local set = {} - for i = 1, N do - local e = world:entity() - CHECK(not set[e]) - set[e] = true - end - end - do CASE "generations" - local world = jecs.world() - local e = world:entity() :: any - CHECK(ECS_ID(e) == 1 + jecs.Rest :: any) - CHECK(ECS_GENERATION(e) == 0) -- 0 - e = ECS_GENERATION_INC(e) - CHECK(ECS_GENERATION(e) == 1) -- 1 - end - - do CASE "pairs" - local world = jecs.world() - local _e = world:entity() - local e2 = world:entity() - local e3 = world:entity() - - -- Incomplete pair, must have a bit flag that notes it is a pair - CHECK(IS_PAIR(world:entity()) == false) - - local p = pair(e2, e3) - CHECK(IS_PAIR(p) == true) - - CHECK(ecs_pair_first(world, p) == e2) - CHECK(ecs_pair_second(world, p) == e3) - - world:delete(e2) - local e2v2 = world:entity() :: any - CHECK(IS_PAIR(e2v2) == false) - - CHECK(IS_PAIR(pair(e2v2, e3) :: any) == true) - end - - do CASE "Recycling" - local world = jecs.world() - local e = world:entity() - world:delete(e) - local e1 = world:entity() - world:delete(e1) - local e2 = world:entity() - CHECK(ECS_ID(e2 :: any) :: any == e) - CHECK(ECS_GENERATION(e2 :: any) == 2) - CHECK(world:contains(e2)) - CHECK(not world:contains(e1)) - CHECK(not world:contains(e)) - end - - do CASE "Recycling max generation" - local world = jecs.world() - local pin = (jecs.Rest :: any) :: number + 1 - for i = 1, 2^16-1 do - local e = world:entity() - world:delete(e) - end - local e = world:entity() :: number - CHECK(ECS_ID(e) == pin) - CHECK(ECS_GENERATION(e) == 2^16-1) - world:delete(e) - e = world:entity() :: number - CHECK(ECS_ID(e) == pin) - CHECK(ECS_GENERATION(e) == 0) - end -end) - -TEST("world:has()", function() - do CASE "should find Tag on entity" - local world = jecs.World.new() - - local Tag = world:entity() - - local e = world:entity() - world:add(e, Tag) - - CHECK(world:has(e, Tag)) - end - - do CASE "should return false when missing one tag" - local world = jecs.World.new() - - local A = world:entity() - local B = world:entity() - local C = world:entity() - local D = world:entity() - - local e = world:entity() - world:add(e, A) - world:add(e, C) - world:add(e, D) - - CHECK(world:has(e, A, B, C, D) == false) - end -end) - -TEST("world:query()", function() - local N = 2^8 - do CASE "cached" - local world = jecs.world() - local Foo = world:component() - local Bar = world:component() - local Baz = world:component() - local e = world:entity() - local q = world:query(Foo, Bar):without(Baz):cached() - world:set(e, Foo, true) - world:set(e, Bar, false) - local i = 0 - - local iter = 0 - for _ in q:iter() do - iter += 1 - i=1 - end - CHECK (iter == 1) - CHECK(i == 1) - for _ in q:iter() do - i=2 - end - CHECK(i == 2) - for _ in q do - i=3 - end - CHECK(i == 3) - for _ in q do - i=4 - end - CHECK(i == 4) - - CHECK(#q:archetypes() == 1) - CHECK(not table.find(q:archetypes(), world.archetype_index[table.concat({Foo, Bar, Baz}, "_")])) - world:delete(Foo) - CHECK(#q:archetypes() == 0) - end - do CASE "multiple iter" - local world = jecs.World.new() - local A = world:component() :: jecs.Entity - local B = world:component() :: jecs.Entity - local e = world:entity() - world:add(e, A) - world:add(e, B) - local q = world:query(A, B) - local counter = 0 - for x in q:iter() do - counter += 1 - end - for x in q:iter() do - counter += 1 - end - CHECK(counter == 2) - end - do CASE "tag" - local world = jecs.World.new() - local A = world:entity() - local e = world:entity() - CHECK_EXPECT_ERR(function() - world:set(e, A, "test" :: any) - end) - local count = 0 - for id, a in world:query(A) :: any do - count += 1 - CHECK(a == nil) - end - CHECK(count == 1) - end - do CASE "pairs" - local world = jecs.World.new() - - local C1 = world:component() :: jecs.Id - local C2 = world:component() :: jecs.Id - local T1 = world:entity() - local T2 = world:entity() - - local e = world:entity() - - local C1_C2 = pair(C1, C2) - world:set(e, C1_C2, true) - world:set(e, pair(C1, T1), true) - world:set(e, pair(T1, C1), true) - CHECK_EXPECT_ERR(function() - world:set(e, pair(T1, T2), true :: any) - end) - - for id, a, b, c, d in world:query(pair(C1, C2), pair(C1, T1), pair(T1, C1), pair(T1, T2)):iter() do - CHECK(a == true) - CHECK(b == true) - CHECK(c == true) - CHECK(d == nil) - end - end - do CASE "iterate wildcard pairs in cached query" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local Wildcard = jecs.Wildcard - local A = world:entity() - - local relationship = pair(Relation, Wildcard) - local query = world:query(relationship):cached() - - local entity = world:entity() - - local p = pair(Relation, A) - CHECK(jecs.pair_first(world, p) == Relation) - CHECK(jecs.pair_second(world, p) == A) - world:add(entity, pair(Relation, A)) - - local counter = 0 - for e in query:iter() do - counter += 1 - end - CHECK(counter == 1) - end - - do CASE "iterate wildcard pairs in uncached query" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local Wildcard = jecs.Wildcard - local A = world:entity() - - local relationship = pair(Relation, Wildcard) - - local entity = world:entity() - - world:add(entity, pair(Relation, A)) - - local counter = 0 - for e in world:query(relationship):iter() do - counter += 1 - end - CHECK(counter == 1) - end - - do CASE "query single component" - do - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - world:set(id, A, true) - if i > 5 then - world:set(id, B, true) - end - entities[i] = id - end - - for id in world:query(A) :: any do - table.remove(entities, CHECK(table.find(entities, id))) - end - - CHECK(#entities == 0) - end - - do - local world = jecs.world() - local A = world:component() - local B = world:component() - local eA = world:entity() - world:set(eA, A, true) - local eB = world:entity() - world:set(eB, B, true) - local eAB = world:entity() - world:set(eAB, A, true) - world:set(eAB, B, true) - - -- Should drain the iterator - local q = world:query(A) - - local i = 0 - local j = 0 - for _ in q :: any do - i += 1 - end - for _ in q :: any do - j += 1 - end - CHECK(i == 2) - CHECK(j == 0) - end - end - - do CASE "query missing component" - local world = jecs.world() - local A = world:component() - local B = world:component() - local C = world:component() - - local e1 = world:entity() - local e2 = world:entity() - - world:set(e1, A, "abc") - world:set(e2, A, "def") - world:set(e1, B, 123) - world:set(e2, B, 457) - - local counter = 0 - for _ in world:query(B, C) :: any do - counter += 1 - end - CHECK(counter == 0) - end - - do CASE "query more than 8 components" - local world = jecs.world() - local components = {} - - for i = 1, 9 do - local id = world:component() - components[i] = id - end - local e1 = world:entity() - for i, id in components do - world:set(e1, id, 13 ^ i) - end - - for entity, a, b, c, d, e, f, g, h, i in world:query(unpack(components)) :: any do - CHECK(a == 13 ^ 1) - CHECK(b == 13 ^ 2) - CHECK(c == 13 ^ 3) - CHECK(d == 13 ^ 4) - CHECK(e == 13 ^ 5) - CHECK(f == 13 ^ 6) - CHECK(g == 13 ^ 7) - CHECK(h == 13 ^ 8) - CHECK(i == 13 ^ 9) - end - end - - do CASE "should be able to get next results" - local world = jecs.world() - world:component() - local A = world:component() - local B = world:component() - local eA = world:entity() - world:set(eA, A, true) - local eB = world:entity() - world:set(eB, B, true) - local eAB = world:entity() - world:set(eAB, A, true) - world:set(eAB, B, true) - - local it = world:query(A):iter() - - local e, data = it() - while e do - if e == eA then - CHECK(data) - elseif e == eAB then - CHECK(data) - else - CHECK(false) - end - - e, data = it() - end - CHECK(true) - end - - do CASE "should query all matching entities when irrelevant component is removed" - local world = jecs.world() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - -- specifically put them in disorder to track regression - -- https://github.com/Ukendio/jecs/pull/15 - world:set(id, B, true) - world:set(id, A, true) - if i > 5 then - world:remove(id, B) - end - entities[i] = id - end - - local added = 0 - for id in world:query(A) :: any do - added += 1 - table.remove(entities, CHECK(table.find(entities, id))) - end - - CHECK(added == N) - end - - do CASE "should query all entities without B" - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local entities = {} - for i = 1, N do - local id = world:entity() - - world:set(id, A, true) - if i < 5 then - entities[i] = id - else - world:set(id, B, true) - end - end - - for id in world:query(A):without(B) :: any do - table.remove(entities, CHECK(table.find(entities, id))) - end - - CHECK(#entities == 0) - end - - do CASE "should allow querying for relations" - local world = jecs.World.new() - local Eats = world:component() - local Apples = world:component() - local bob = world:entity() - - world:set(bob, pair(Eats, Apples), true) - for e, bool in world:query(pair(Eats, Apples)) :: any do - CHECK(e == bob) - CHECK(bool) - end - end - - do CASE "should allow wildcards in queries" - local world = jecs.World.new() - local Eats = world:component() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, pair(Eats, Apples), "bob eats apples") - - local w = jecs.Wildcard - for e, data in world:query(pair(Eats, w)) :: any do - CHECK(e == bob) - CHECK(data == "bob eats apples") - end - for e, data in world:query(pair(w, Apples)) :: any do - CHECK(e == bob) - CHECK(data == "bob eats apples") - end - end - - do CASE "should match against multiple pairs" - local world = jecs.World.new() - local Eats = world:component() - local Apples = world:entity() - local Oranges = world:entity() - local bob = world:entity() - local alice = world:entity() - - world:set(bob, pair(Eats, Apples), "bob eats apples") - world:set(alice, pair(Eats, Oranges), "alice eats oranges") - - local w = jecs.Wildcard - local count = 0 - for e, data in world:query(pair(Eats, w)) :: any do - count += 1 - if e == bob then - CHECK(data == "bob eats apples") - else - CHECK(data == "alice eats oranges") - end - end - - CHECK(count == 2) - count = 0 - - for e, data in world:query(pair(w, Apples)) :: any do - count += 1 - CHECK(data == "bob eats apples") - end - CHECK(count == 1) - end - - do CASE "should only relate alive entities" - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:component() - local Oranges = world:component() - local bob = world:entity() - local alice = world:entity() - - world:set(bob, Apples, "apples") - world:set(bob, pair(Eats, Apples), "bob eats apples") - world:set(alice, pair(Eats, Oranges), "alice eats oranges") - - world:delete(Apples) - local Wildcard = jecs.Wildcard - - local count = 0 - for _, data in world:query(pair(Wildcard, Apples)) :: any do - count += 1 - end - - world:delete(pair(Eats, Apples)) - - CHECK(count == 0) - CHECK(world:get(bob, pair(Eats, Apples)) == nil) - - end - - do - CASE("should error when setting invalid pair") - local world = jecs.World.new() - local Eats = world:component() - local Apples = world:component() - local bob = world:entity() - - world:delete(Apples) - CHECK_EXPECT_ERR(function() - world:set(bob, pair(Eats, Apples), "bob eats apples") - end) - end - - do - CASE("should find target for ChildOf") - local world = jecs.World.new() - local ChildOf = jecs.ChildOf - - local Name = world:component() - - local bob = world:entity() - local alice = world:entity() - local sara = world:entity() - - world:add(bob, pair(ChildOf, alice)) - world:set(bob, Name, "bob") - world:add(sara, pair(ChildOf, alice)) - world:set(sara, Name, "sara") - CHECK(world:parent(bob) == alice) -- O(1) - - local count = 0 - for _, name in world:query(Name, pair(ChildOf, alice)) :: any do - count += 1 - end - CHECK(count == 2) - end - - do - CASE("despawning while iterating") - local world = jecs.World.new() - local A = world:component() - local B = world:component() - - local e1 = world:entity() - local e2 = world:entity() - world:add(e1, A) - world:add(e2, A) - world:add(e2, B) - - local count = 0 - for id in world:query(A) :: any do - world:clear(id) - count += 1 - end - CHECK(count == 2) - end - - do CASE "should not find any entities" - local world = jecs.World.new() - - local Hello = world:component() - local Bob = world:component() - - local helloBob = world:entity() - world:add(helloBob, pair(Hello, Bob)) - world:add(helloBob, Bob) - - local withoutCount = 0 - for _ in world:query(pair(Hello, Bob)):without(Bob) :: any do - withoutCount += 1 - end - - CHECK(withoutCount == 0) - end - - do CASE "world:query():without()" - -- REGRESSION TEST - local world = jecs.World.new() - local _1, _2, _3 = world:component(), world:component(), world:component() - - local counter = 0 - for e, a, b in world:query(_1, _2):without(_3) :: any do - counter += 1 - end - CHECK(counter == 0) - end -end) - -TEST("world:remove()", function() - do - CASE("should allow remove a component that doesn't exist on entity") - local world = jecs.World.new() - - local Health = world:component() - local Poison = world:component() - - local id = world:entity() - do - world:remove(id, Poison) - CHECK(true) -- Didn't error - end - - world:set(id, Health, 50) - world:remove(id, Poison) - - CHECK(world:get(id, Poison) == nil) - CHECK(world:get(id, Health) == 50) - end -end) - -TEST("world:set()", function() - do CASE "archetype move" - local world = jecs.world() - - local d = dwi(world) - - local _1 = world:component() - local _2 = world:component() - local e = world:entity() - -- An entity starts without an archetype or row - -- should therefore not need to copy over data - CHECK(d.tbl(e) == nil) - CHECK(d.row(e) == nil) - - local archetypes = #world.archetypes - -- This should create a new archetype since it is the first - -- entity to have moved there - world:set(e, _1, 1) - local oldRow = d.row(e) - local oldArchetype = d.archetype(e) - CHECK(#world.archetypes == archetypes + 1) - CHECK(oldArchetype == "1") - CHECK(d.tbl(e)) - CHECK(oldRow == 1) - - world:set(e, _2, 2) - CHECK(d.archetype(e) == "1_2") - -- Should have tuple of fields to the next archetype and set the component data - CHECK(d.tuple(e, 1, 2)) - -- Should have moved the data from the old archetype - CHECK(world.archetype_index[oldArchetype].columns[_1 :: any][oldRow] == nil) - end - - do CASE "pairs" - local world = jecs.world() - - 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) - - CHECK_EXPECT_ERR(function() - world:set(e, pair(T1, T2), true :: any) - end) - - CHECK(world:get(e, pair(C1, C2))) - CHECK(world:get(e, pair(C1, T1))) - CHECK(world:get(e, pair(T1, C1))) - CHECK(not world:get(e, pair(T1, T2))) - - local e2 = world:entity() - - CHECK_EXPECT_ERR(function() - world:set(e2, pair(jecs.ChildOf, e), true :: any) - end) - CHECK(not world:get(e2, pair(jecs.ChildOf, e))) - end -end) - -TEST("world:target", function() - do CASE "nth index" - local world = jecs.world() - local A = world:component() - world:set(A, jecs.Name, "A") - local B = world:component() - world:set(B, jecs.Name, "B") - local C = world:component() - world:set(C, jecs.Name, "C") - local D = world:component() - world:set(D, jecs.Name, "D") - local E = world:component() - world:set(E, jecs.Name, "E") - local e = world:entity() - - world:add(e, pair(A, B)) - world:add(e, pair(A, C)) - world:add(e, pair(A, D)) - world:add(e, pair(A, E)) - world:add(e, pair(B, C)) - world:add(e, pair(B, D)) - world:add(e, pair(C, D)) - - CHECK((pair(A, B) :: any) < (pair(A, C) :: any)) - CHECK((pair(A, C) :: any) < (pair(A, D) :: any)) - CHECK((pair(C, A) :: any) < (pair(C, D) :: any)) - - CHECK(jecs.pair_first(world, pair(B, C)) == B) - local r = (jecs.entity_index_try_get(world.entity_index :: any, e :: any) :: any) :: jecs.Record - local archetype = r.archetype - local records = archetype.records - local counts = archetype.counts - CHECK(counts[pair(A, __)] == 4) - CHECK(records[pair(B, C)] > records[pair(A, E)]) - CHECK(world:target(e, A, 0) == B) - CHECK(world:target(e, A, 1) == C) - CHECK(world:target(e, A, 2) == D) - CHECK(world:target(e, A, 3) == E) - 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) - - CHECK(archetype.records[pair(A, B):: any] == 1) - CHECK(archetype.records[pair(A, C):: any] == 2) - CHECK(archetype.records[pair(A, D):: any] == 3) - CHECK(archetype.records[pair(A, E):: any] == 4) - - CHECK(world:target(e, C, 0) == D) - CHECK(world:target(e, C, 1) == nil) - end - - do CASE "infer index when unspecified" - local world = jecs.world() - 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(world:target(e, A) == world:target(e, A, 0)) - CHECK(world:target(e, B) == world:target(e, B, 0)) - CHECK(world:target(e, C) == world:target(e, C, 0)) - end - - do CASE "loop until no target" - local world = jecs.world() - - 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("#adding a recycled target", function() - local world = jecs.world() - local R = world:component() - - local e = world:entity() - local T = world:entity() - world:add(e, pair(R, T)) - world:delete(T) - CHECK(not world:has(e, pair(R, T))) - local T2 = world:entity() - world:add(e, pair(R, T2)) - CHECK(world:target(e, R) ~= T) - CHECK(world:target(e, R) ~= 0 :: any) - -end) - -TEST("#repro2", function() - local world = jecs.world() - local Lifetime = world:component() :: Id - local Particle = world:entity() - local Beam = world:entity() - - local entity = world:entity() - world:set(entity, pair(Lifetime, Particle), 1) - world:set(entity, pair(Lifetime, Beam), 2) - world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise - - -- entity_visualizer.components(world, entity) - - for e in world:each(pair(Lifetime, __)) do - local i = 0 - local nth = world:target(e, Lifetime, i) - while nth do - -- entity_visualizer.components(world, e) - - local data = world:get(e, pair(Lifetime, nth)) :: number - data -= 1 - if data <= 0 then - world:remove(e, pair(Lifetime, nth)) - else - world:set(e, pair(Lifetime, nth), data) - end - i += 1 - nth = world:target(e, Lifetime, i) - end - end - - CHECK(not world:has(entity, pair(Lifetime, Particle))) - CHECK(world:get(entity, pair(Lifetime, Beam)) == 1) -end) - -TEST("another", function() - local world = jecs.world() - -- world = lifetime_tracker_add(world, {padding_enabled=false}) - local e1 = world:entity() - local e2 = world:entity() - local e3 = world:entity() - world:delete(e2) - local e2_e3 = pair(e2, e3) - CHECK(jecs.pair_first(world, e2_e3) == 0 :: any) - CHECK(jecs.pair_second(world, e2_e3) == e3) - CHECK_EXPECT_ERR(function() - world:add(e1, pair(e2, e3)) - end) -end) - -TEST("#repro", function() - local world = jecs.world() - - local function getTargets(relation) - local tgts = {} - local pairwildcard = pair(relation, jecs.Wildcard) - for _, archetype in world:query(pairwildcard):archetypes() do - local tr = archetype.records[pairwildcard] - local count = archetype.counts[pairwildcard] - local types = archetype.types - for _, entity in archetype.entities do - for i = 0, count - 1 do - local tgt = jecs.pair_second(world, types[i + tr] :: any) - table.insert(tgts, tgt) - end - end - end - return tgts - end - - local Attacks = world:component() - local Eats = world:component() - - local function setAttacksAndEats(entity1, entity2) - world:add(entity1, pair(Attacks, entity2)) - world:add(entity1, pair(Eats, entity2)) - end - - local e1 = world:entity() - local e2 = world:entity() - local e3 = world:entity() - setAttacksAndEats(e3, e1) - setAttacksAndEats(e3, e2) - setAttacksAndEats(e1, e2) - local d = dwi(world) - world:delete(e2) - local types1 = { pair(Attacks, e1), pair(Eats, e1) } - table.sort(types1) - - - CHECK(d.tbl(e1).type == "") - CHECK(d.tbl(e3).type == table.concat(types1, "_")) - - for _, entity in getTargets(Attacks) do - CHECK(entity == e1) - end - for _, entity in getTargets(Eats) do - CHECK(entity == e1) - end -end) - -TEST("Hooks", function() - do CASE "OnAdd" - local world = jecs.world() - local Transform = world:component() - local e1 = world:entity() - world:set(Transform, jecs.OnAdd, function(entity) - CHECK(e1 == entity) - end) - world:add(e1, Transform) - end - - do CASE "OnSet" - local world = jecs.world() - local Number = world:component() - local e1 = world:entity() - - local call = 0 - world:set(Number, jecs.OnChange, function(entity, data) - CHECK(e1 == entity) - if call == 1 then - CHECK(false) - elseif call == 2 then - CHECK(world:get(entity, Number) == data) - end - CHECK(data == 1) - end) - - call = 1 - world:set(e1, Number, 1) - call = 2 - world:set(e1, Number, 1) - end - - do CASE("OnRemove") - do - -- basic - local world = jecs.world() - local A = world:component() :: Id - local e1 = world:entity() - world:set(A, jecs.OnRemove, function(entity) - CHECK(e1 == entity) - CHECK(world:has(e1, A)) - end) - world:add(e1, A) - - world:remove(e1, A) - CHECK(not world:has(e1, A)) - end - do - -- [BUG] https://github.com/Ukendio/jecs/issues/118 - local world = jecs.world() - local A = world:component() - local B = world:component() - local e = world:entity() - - world:set(A, jecs.OnRemove, function(entity) - world:set(entity, B, true) - CHECK(world:get(entity, A)) - CHECK(world:get(entity, B)) - end) - - world:set(e, A, true) - world:remove(e, A) - CHECK(not world:get(e, A)) - CHECK(world:get(e, B)) - end - end -end) - -TEST("change tracking", function() - do CASE "#1" - local world = jecs.world() - local Foo = world:component() :: Id - local Previous = jecs.Rest - - local q1 = world - :query(Foo) - :without(pair(Previous, Foo)) - :cached() - - local e1 = world:entity() - world:set(e1, Foo, 1) - local e2 = world:entity() - world:set(e2, Foo, 2) - - local i = 0 - for e, new in q1 :: any do - i += 1 - world:set(e, pair(Previous, Foo), new) - end - - CHECK(i == 2) - local j = 0 - for e, new in q1 :: any do - j += 1 - world:set(e, pair(Previous, Foo), new) - end - - CHECK(j == 0) - end - - do CASE "#2" - local world = jecs.world() - local component = world:component() :: Id - local tag = world:entity() - local previous = jecs.Rest - - local q1 = world:query(component):without(pair(previous, component), tag):cached() - - local testEntity = world:entity() - - world:set(testEntity, component, 10) - - local i = 0 - for entity, number in q1 :: any do - i += 1 - world:add(testEntity, tag) - end - - CHECK(i == 1) - - for e, n in q1 :: any do - world:set(e, pair(previous, component), n) - end - end - -end) - -TEST("repro", function() - do CASE "#1" - local world = jecs.world() - local reproEntity = world:component() - local components = { Cooldown = world:component() :: Id } - world:set(reproEntity, components.Cooldown, 2) - - local function updateCooldowns(dt: number) - local toRemove = {} - - local it = world:query(components.Cooldown):iter() - for id, cooldown in it do - cooldown -= dt - - if cooldown <= 0 then - table.insert(toRemove, id) - -- world:remove(id, components.Cooldown) - else - world:set(id, components.Cooldown, cooldown) - end - end - - for _, id in toRemove do - world:remove(id, components.Cooldown) - CHECK(not world:get(id, components.Cooldown)) - end - end - - updateCooldowns(1.5) - updateCooldowns(1.5) - end - - do CASE "#2" -- ISSUE #171 - local world = jecs.world() - local component1 = world:component() - local tag1 = world:entity() - - local query = world:query(component1):with(tag1):cached() - - local entity = world:entity() - world:set(entity, component1, "some data") - - local counter = 0 - for x in query:iter() do - counter += 1 - end - CHECK(counter == 0) - end -end) - -TEST("wildcard query", function() - do CASE "#1" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local Wildcard = jecs.Wildcard - local A = world:entity() - - local relationship = pair(Relation, Wildcard) - local query = world:query(relationship):cached() - - local entity = world:entity() - - local p = pair(Relation, A) - CHECK(jecs.pair_first(world, p) == Relation) - CHECK(jecs.pair_second(world, p) == A) - world:add(entity, pair(Relation, A)) - - local counter = 0 - for e in query:iter() do - counter += 1 - end - CHECK(counter == 1) - end - do CASE "#2" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local Wildcard = jecs.Wildcard - local A = world:entity() - - local relationship = pair(Relation, Wildcard) - - local entity = world:entity() - - world:add(entity, pair(Relation, A)) - - local counter = 0 - for e in world:query(relationship):iter() do - counter += 1 - end - CHECK(counter == 1) - end - do CASE "#3" - local world = jecs.world() - local pair = jecs.pair - - local Relation = world:entity() - local Wildcard = jecs.Wildcard - local A = world:entity() - - local entity = world:entity() - - world:add(entity, pair(Relation, A)) - - local relationship = pair(Relation, Wildcard) - local query = world:query(relationship):cached() - - local counter = 0 - for e in query:iter() do - counter += 1 - end - CHECK(counter == 1) - end -end) - -return FINISH() diff --git a/test/tools/entity_visualiser.luau b/test/tools/entity_visualiser.luau deleted file mode 100644 index 9698939..0000000 --- a/test/tools/entity_visualiser.luau +++ /dev/null @@ -1,24 +0,0 @@ -local jecs = require("@jecs") -local pair = jecs.pair -local ChildOf = jecs.ChildOf -local lifetime_tracker_add = require("@tools/lifetime_tracker") -local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false}) -local FriendsWith = world:component() -world:print_snapshot() -local e1 = world:entity() -local e2 = world:entity() -world:delete(e2) - -world:print_snapshot() -local e3 = world:entity() -world:add(e3, pair(ChildOf, e1)) -local e4 = world:entity() -world:add(e4, pair(FriendsWith, e3)) -world:print_snapshot() -world:delete(e1) -world:delete(e3) -world:print_snapshot() -world:print_entity_index() -world:entity() -world:entity() -world:print_snapshot() diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 77b57f6..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "downlevelIteration": true, - "jsx": "react", - "jsxFactory": "Roact.createElement", - "jsxFragmentFactory": "Roact.Fragment", - "module": "commonjs", - "moduleResolution": "Node", - "noLib": true, - "resolveJsonModule": true, - "strict": true, - "target": "ESNext", - "typeRoots": [ - "node_modules/@rbxts" - ], - "rootDir": "lib", - "outDir": "out", - "baseUrl": "lib", - "incremental": true, - "tsBuildInfoFile": "out/tsconfig.tsbuildinfo", - "moduleDetection": "force" - } -} diff --git a/wally.toml b/wally.toml deleted file mode 100644 index 24905d0..0000000 --- a/wally.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "ukendio/jecs" -version = "0.6.0" -registry = "https://github.com/UpliftGames/wally-index" -realm = "shared" -license = "MIT" -include = [ - "default.project.json", - "jecs.luau", - "wally.toml", - "README.md", - "CHANGELOG.md", - "LICENSE", -] -exclude = ["**"]