From 85e2621cce9a7c788fb904aca7240b1227a1e81b Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 13 May 2024 20:15:09 +0200 Subject: [PATCH 01/25] CompatibleArchetype as a map --- lib/init.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index b7486e4..f45e842 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -623,7 +623,10 @@ function World.query(world: World, ...: i53): Query end length += 1 - compatibleArchetypes[length] = {archetype, indices} + compatibleArchetypes[length] = { + archetype = archetype, + indices = indices + } end local lastArchetype, compatibleArchetype = next(compatibleArchetypes) @@ -637,7 +640,7 @@ function World.query(world: World, ...: i53): Query function preparedQuery:without(...) local withoutComponents = {...} for i = #compatibleArchetypes, 1, -1 do - local archetype = compatibleArchetypes[i][1] + local archetype = compatibleArchetypes[i].archetype local records = archetype.records local shouldRemove = false @@ -666,21 +669,21 @@ function World.query(world: World, ...: i53): Query function preparedQuery:__iter() return function() - local archetype = compatibleArchetype[1] + local archetype = compatibleArchetype.archetype local row: number = next(archetype.entities, lastRow) :: number while row == nil do lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) if lastArchetype == nil then return end - archetype = compatibleArchetype[1] + archetype = compatibleArchetype.archetype row = next(archetype.entities, row) :: number end lastRow = row local entityId = archetype.entities[row :: number] local columns = archetype.columns - local tr = compatibleArchetype[2] + local tr = compatibleArchetype.indices if queryLength == 1 then return entityId, columns[tr[1]][row] From 6710e3cdcb9ac103dc7ba5beb9c2358d4da96c67 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 14 May 2024 03:22:05 +0200 Subject: [PATCH 02/25] Bump wally to 0.3.2 --- aftman.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aftman.toml b/aftman.toml index ace6bc1..56cddbd 100644 --- a/aftman.toml +++ b/aftman.toml @@ -1,5 +1,5 @@ [tools] -wally = "upliftgames/wally@0.3.1" +wally = "upliftgames/wally@0.3.2" rojo = "rojo-rbx/rojo@7.4.1" stylua = "johnnymorganz/stylua@0.19.1" selene = "kampfkarren/selene@0.26.1" From e86b4c7f4c803d41209680ad53781a8d0ca668a3 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 14 May 2024 17:52:41 +0200 Subject: [PATCH 03/25] Rename functions --- lib/init.lua | 74 ++++++++++++++++++++++++------------------------- tests/world.lua | 8 +++--- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index f45e842..aabfbed 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -75,7 +75,7 @@ local function addFlags(isPair: boolean) return typeFlags end -local function newId(source: number, target: number) +local function ECS_COMBINE(source: number, target: number): i53 local e = source * 2^28 + target * ECS_ID_FLAGS_MASK return e end @@ -96,7 +96,7 @@ local function ECS_GENERATION(e: i53) return e % ECS_GENERATION_MASK end -local function ECS_ID(e: i53) +local function ECS_ENTITY_T_LO(e: i53) e //= 0x10 return e // ECS_ENTITY_MASK end @@ -104,51 +104,51 @@ end local function ECS_GENERATION_INC(e: i53) local id, generation, flags = separate(e) - return newId(id, generation + 1) + flags + return ECS_COMBINE(id, generation + 1) + flags end -- gets the high ID -local function ECS_PAIR_FIRST(entity: i53): i24 +local function ECS_ENTITY_T_HI(entity: i53): i24 entity //= 0x10 local first = entity % ECS_ENTITY_MASK return first end --- gets the low ID -local ECS_PAIR_SECOND = ECS_ID +local function ECS_PAIR(pred: number, obj: number) + local first + local second: number = WILDCARD -local function ECS_PAIR(first: number, second: number) - local target = WILDCARD - local relation - - if first == WILDCARD then - relation = second - elseif second == WILDCARD then - relation = first + if pred == WILDCARD then + first = obj + elseif obj == WILDCARD then + first = pred else - relation = second - target = ECS_PAIR_SECOND(first) + first = obj + second = ECS_ENTITY_T_LO(pred) end - return newId( - ECS_PAIR_SECOND(relation), target) + addFlags(--[[isPair]] true) + return ECS_COMBINE( + ECS_ENTITY_T_LO(first), second) + addFlags(--[[isPair]] true) end local function getAlive(entityIndex: EntityIndex, id: i53) return entityIndex.dense[id] end -local function ecs_get_source(entityIndex, e) +-- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits +local function ECS_PAIR_RELATION(entityIndex, e) assert(ECS_IS_PAIR(e)) - return getAlive(entityIndex, ECS_PAIR_FIRST(e)) -end -local function ecs_get_target(entityIndex, e) - assert(ECS_IS_PAIR(e)) - return getAlive(entityIndex, ECS_PAIR_SECOND(e)) + return getAlive(entityIndex, ECS_ENTITY_T_HI(e)) end -local function nextEntityId(entityIndex, index: i24) - local id = newId(index, 0) +-- ECS_PAIR_SECOND gets the relationship / pred / LOW bits +local function ECS_PAIR_OBJECT(entityIndex, e) + assert(ECS_IS_PAIR(e)) + return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) +end + +local function nextEntityId(entityIndex, index: i24): i53 + local id = ECS_COMBINE(index, 0) entityIndex.sparse[id] = { dense = index } :: Record @@ -267,14 +267,14 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet columns[i] = {} if ECS_IS_PAIR(componentId) then - local first = ecs_get_source(entityIndex, componentId) - local second = ecs_get_target(entityIndex, componentId) - local firstPair = ECS_PAIR(first, WILDCARD) - local secondPair = ECS_PAIR(WILDCARD, second) - createArchetypeRecord(componentIndex, id, firstPair, i) - createArchetypeRecord(componentIndex, id, secondPair, i) - records[firstPair] = i - records[secondPair] = i + local pred = ECS_PAIR_RELATION(entityIndex, componentId) + local obj = ECS_PAIR_OBJECT(entityIndex, componentId) + local first = ECS_PAIR(pred, WILDCARD) + local second = ECS_PAIR(WILDCARD, obj) + createArchetypeRecord(componentIndex, id, first, i) + createArchetypeRecord(componentIndex, id, second, i) + records[first] = i + records[second] = i end end @@ -783,13 +783,13 @@ return table.freeze({ w = WILDCARD, Rest = REST, - ECS_ID = ECS_ID, IS_PAIR = ECS_IS_PAIR, + ECS_ID = ECS_ENTITY_T_LO, ECS_PAIR = ECS_PAIR, ECS_GENERATION_INC = ECS_GENERATION_INC, ECS_GENERATION = ECS_GENERATION, - ecs_get_target = ecs_get_target, - ecs_get_source = ecs_get_source, + ECS_PAIR_RELATION = ECS_PAIR_RELATION, + ECS_PAIR_OBJECT = ECS_PAIR_OBJECT, pair = ECS_PAIR, getAlive = getAlive, diff --git a/tests/world.lua b/tests/world.lua index 1aff493..cf7f47f 100644 --- a/tests/world.lua +++ b/tests/world.lua @@ -5,8 +5,8 @@ local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC local IS_PAIR = jecs.IS_PAIR local ECS_PAIR = jecs.ECS_PAIR local getAlive = jecs.getAlive -local ecs_get_source = jecs.ecs_get_source -local ecs_get_target = jecs.ecs_get_target +local ECS_PAIR_RELATION = jecs.ECS_PAIR_RELATION +local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT local TEST, CASE, CHECK, FINISH, SKIP = testkit.test() @@ -189,8 +189,8 @@ TEST("world", function() local pair = ECS_PAIR(e2, e3) CHECK(IS_PAIR(pair) == true) - CHECK(ecs_get_source(world.entityIndex, pair) == e2) - CHECK(ecs_get_target(world.entityIndex, pair) == e3) + CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2) + CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3) end do CASE "should allow querying for relations" From cf0683cf0307b2dd5fb9a74315023967c5d5c4a3 Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 17 May 2024 00:17:53 +0200 Subject: [PATCH 04/25] Add World:target (#39) --- README.md | 59 +++++++--------- lib/init.lua | 184 ++++++++++++++++++++++++++++++++++-------------- tests/world.lua | 74 +++++++++++++++++++ 3 files changed, 232 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 6bb4607..8740429 100644 --- a/README.md +++ b/README.md @@ -22,47 +22,38 @@ jecs is a stupidly fast Entity Component System (ECS). ### Example ```lua -local world = World.new() +local world = jecs.World.new() +local pair = jecs.pair -local player = world:entity() -local opponent = world:entity() +local ChildOf = world:component() +local Name = world:component() -local Health = world:component() -local Position = world:component() --- Notice how components can just be entities as well? --- It allows you to model relationships easily! -local Damage = world:entity() -local DamagedBy = world:entity() +local function parent(entity) + return world:target(entity, ChildOf) +end +local function name() -world:set(player, Health, 100) -world:set(player, Damage, 8) -world:set(player, Position, Vector3.new(0, 5, 0)) +local alice = world:entity() +world:set(alice, Name, "alice") -world:set(opponent, Health, 100) -world:set(opponent, Damage, 21) -world:set(opponent, Position, Vector3.new(0, 5, 3)) +local bob = world:entity() +world:add(bob, pair(ChildOf, alice)) +world:set(bob, Name, "bob") -for playerId, playerPosition, health in world:query(Position, Health) do - local totalDamage = 0 - for opponentId, opponentPosition, damage in world:query(Position, Damage) do - if playerId == opponentId then - continue - end - if (playerPosition - opponentPosition).Magnitude < 5 then - totalDamage += damage - end - -- We create a pair between the relation component `DamagedBy` and the entity id of the opponent. - -- This will allow us to specifically query for damage exerted by a specific opponent. - world:set(playerId, ECS_PAIR(DamagedBy, opponentId), totalDamage) - end +local sara = world:entity() +world:add(sara, pair(ChildOf, alice)) +world:set(sara, Name, "sara") + +print(getName(parent(sara))) + +for e in world:query(pair(ChildOf, alice)) do + print(getName(e), "is the child of alice") end --- Gets the damage inflicted by our specific opponent! -for playerId, health, inflicted in world:query(Health, ECS_PAIR(DamagedBy, opponent)) do - world:set(playerId, health - inflicted) -end - -assert(world:get(player, Health) == 79) +-- Output +-- "alice" +-- bob is the child of alice +-- sara is the child of alice ``` 125 archetypes, 4 random components queried. diff --git a/lib/init.lua b/lib/init.lua index aabfbed..5f11982 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -14,7 +14,7 @@ type Column = {any} type Archetype = { id: number, edges: { - [i24]: { + [i53]: { add: Archetype, remove: Archetype, }, @@ -26,17 +26,37 @@ type Archetype = { records: {}, } + type Record = { archetype: Archetype, row: number, dense: i24, + componentRecord: ArchetypeMap } type EntityIndex = {dense: {[i24]: i53}, sparse: {[i53]: Record}} -type ComponentIndex = {[i24]: ArchetypeMap} type ArchetypeRecord = number -type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number} +--[[ +TODO: +{ + index: number, + count: number, + column: number +} + +]] + +type ArchetypeMap = { + cache: {[number]: ArchetypeRecord}, + first: ArchetypeMap, + second: ArchetypeMap, + parent: ArchetypeMap, + size: number +} + +type ComponentIndex = {[i24]: ArchetypeMap} + type Archetypes = {[ArchetypeId]: Archetype} type ArchetypeDiff = { @@ -96,6 +116,7 @@ local function ECS_GENERATION(e: i53) return e % ECS_GENERATION_MASK end +-- SECOND local function ECS_ENTITY_T_LO(e: i53) e //= 0x10 return e // ECS_ENTITY_MASK @@ -107,7 +128,7 @@ local function ECS_GENERATION_INC(e: i53) return ECS_COMBINE(id, generation + 1) + flags end --- gets the high ID +-- FIRST gets the high ID local function ECS_ENTITY_T_HI(entity: i53): i24 entity //= 0x10 local first = entity % ECS_ENTITY_MASK @@ -131,8 +152,13 @@ local function ECS_PAIR(pred: number, obj: number) ECS_ENTITY_T_LO(first), second) + addFlags(--[[isPair]] true) end -local function getAlive(entityIndex: EntityIndex, id: i53) - return entityIndex.dense[id] +local function getAlive(entityIndex: EntityIndex, id: i24) + local entityId = entityIndex.dense[id] + local record = entityIndex.sparse[entityIndex.dense[id]] + if not record then + error(id.." is not alive") + end + return entityId end -- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits @@ -239,17 +265,29 @@ local function hash(arr): string | number return table.concat(arr, "_") end -local function createArchetypeRecord(componentIndex, id, componentId, i) +local function ensureComponentRecord(componentIndex: ComponentIndex, archetypeId, componentId, i): ArchetypeMap local archetypesMap = componentIndex[componentId] if not archetypesMap then - archetypesMap = {size = 0, sparse = {}} + archetypesMap = {size = 0, cache = {}, first = {}, second = {}} :: ArchetypeMap componentIndex[componentId] = archetypesMap end - archetypesMap.sparse[id] = i + + archetypesMap.cache[archetypeId] = i + archetypesMap.size += 1 + + return archetypesMap end -local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype +local function ECS_ID_IS_WILDCARD(e) + assert(ECS_IS_PAIR(e)) + local first = ECS_ENTITY_T_HI(e) + local second = ECS_ENTITY_T_LO(e) + return first == WILDCARD or second == WILDCARD +end + + +local function archetypeOf(world: any, types: {i24}, prev: Archetype?): Archetype local ty = hash(types) local id = world.nextArchetypeId + 1 @@ -257,25 +295,27 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet local length = #types local columns = table.create(length) + local componentIndex = world.componentIndex local records = {} - local componentIndex = world.componentIndex - local entityIndex = world.entityIndex for i, componentId in types do - createArchetypeRecord(componentIndex, id, componentId, i) + ensureComponentRecord(componentIndex, id, componentId, i) records[componentId] = i - columns[i] = {} - if ECS_IS_PAIR(componentId) then - local pred = ECS_PAIR_RELATION(entityIndex, componentId) - local obj = ECS_PAIR_OBJECT(entityIndex, componentId) - local first = ECS_PAIR(pred, WILDCARD) - local second = ECS_PAIR(WILDCARD, obj) - createArchetypeRecord(componentIndex, id, first, i) - createArchetypeRecord(componentIndex, id, second, i) - records[first] = i - records[second] = i + local relation = ECS_PAIR_RELATION(world.entityIndex, componentId) + local object = ECS_PAIR_OBJECT(world.entityIndex, componentId) + + local idr_r = ECS_PAIR(relation, WILDCARD) + ensureComponentRecord( + componentIndex, id, idr_r, i) + records[idr_r] = i + + local idr_t = ECS_PAIR(WILDCARD, object) + ensureComponentRecord( + componentIndex, id, idr_t, i) + records[idr_t] = i end + columns[i] = {} end local archetype = { @@ -333,6 +373,29 @@ function World.entity(world: World) return nextEntityId(world.entityIndex, entityId + REST) end +-- TODO: +-- should have an additional `index` parameter which selects the nth target +-- this is important when an entity can have multiple relationships with the same target +function World.target(world: World, entity: i53, relation: i24): i24? + local entityIndex = world.entityIndex + local record = entityIndex.sparse[entity] + local archetype = record.archetype + if not archetype then + return nil + end + local componentRecord = world.componentIndex[ECS_PAIR(relation, WILDCARD)] + if not componentRecord then + return nil + end + + local archetypeRecord = componentRecord.cache[archetype.id] + if not archetypeRecord then + return nil + end + + return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord]) +end + -- should reuse this logic in World.set instead of swap removing in transition archetype local function destructColumns(columns, count, row) if row == count then @@ -347,41 +410,54 @@ local function destructColumns(columns, count, row) end end -local function archetypeDelete(entityIndex, record: Record, entityId: i53, destruct: boolean) +local function archetypeDelete(world: World, id: i53) + local componentIndex = world.componentIndex + local archetypesMap = componentIndex[id] + local archetypes = world.archetypes + if archetypesMap then + for archetypeId in archetypesMap.cache do + for _, entity in archetypes[archetypeId].entities do + world:remove(entity, id) + end + end + + componentIndex[id] = nil + end +end + +function World.delete(world: World, entityId: i53) + local record = world.entityIndex.sparse[entityId] + if not record then + return + end + local entityIndex = world.entityIndex local sparse, dense = entityIndex.sparse, entityIndex.dense local archetype = record.archetype local row = record.row - local entities = archetype.entities - local last = #entities - local entityToMove = entities[last] + archetypeDelete(world, entityId) + archetypeDelete(world, ECS_PAIR(entityId, WILDCARD)) + archetypeDelete(world, ECS_PAIR(WILDCARD, entityId)) - if row ~= last then - dense[record.dense] = entityToMove - sparse[entityToMove] = record + if archetype then + local entities = archetype.entities + local last = #entities + + if row ~= last then + local entityToMove = entities[last] + dense[record.dense] = entityToMove + sparse[entityToMove] = record + end + + entities[row], entities[last] = entities[last], nil + + local columns = archetype.columns + + destructColumns(columns, last, row) end sparse[entityId] = nil dense[#dense] = nil - - entities[row], entities[last] = entities[last], nil - - local columns = archetype.columns - - if not destruct then - return - end - - destructColumns(columns, last, row) -end - -function World.delete(world: World, entityId: i53) - local entityIndex = world.entityIndex - local record = entityIndex.sparse[entityId] - if not record then - return - end - archetypeDelete(entityIndex, record, entityId, true) end export type World = typeof(World.new()) @@ -530,6 +606,10 @@ end -- Keeping the function as small as possible to enable inlining local function get(record: Record, componentId: i24) local archetype = record.archetype + if not archetype then + return nil + end + local archetypeRecord = archetype.records[componentId] if not archetypeRecord then @@ -575,7 +655,7 @@ EmptyQuery.__index = EmptyQuery setmetatable(EmptyQuery, EmptyQuery) export type Query = typeof(EmptyQuery) - +local testkit = require("../testkit") function World.query(world: World, ...: i53): Query -- breaking? if (...) == nil then @@ -603,9 +683,10 @@ function World.query(world: World, ...: i53): Query end end - for id in firstArchetypeMap.sparse do + for id in firstArchetypeMap.cache do local archetype = archetypes[id] local archetypeRecords = archetype.records + local indices = {} local skip = false @@ -615,6 +696,7 @@ function World.query(world: World, ...: i53): Query skip = true break end + -- index should be index.offset indices[i] = index end diff --git a/tests/world.lua b/tests/world.lua index cf7f47f..2d4bb6d 100644 --- a/tests/world.lua +++ b/tests/world.lua @@ -1,5 +1,6 @@ local testkit = require("../testkit") local jecs = require("../lib/init") +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 @@ -9,7 +10,16 @@ local ECS_PAIR_RELATION = jecs.ECS_PAIR_RELATION local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT local TEST, CASE, CHECK, FINISH, SKIP = testkit.test() +local function CHECK_NO_ERR(s: string, fn: (T...) -> (), ...: T...) + local ok, err: string? = pcall(fn, ...) + if not CHECK(not ok, 2) then + local i = string.find(err :: string, " ") + assert(i) + local msg = string.sub(err :: string, i+1) + CHECK(msg == s, 2) + end +end local N = 10 TEST("world", function() @@ -256,6 +266,70 @@ TEST("world", function() end CHECK(count == 1) end + + do CASE "should only relate alive entities" + + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local Oranges = world:entity() + local bob = world:entity() + local alice = world:entity() + + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") + world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") + + world:delete(Apples) + local Wildcard = jecs.Wildcard + + local count = 0 + for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do + count += 1 + end + + CHECK(count == 0) + end + + do CASE "should error when setting invalid pair" + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local bob = world:entity() + + world:delete(Apples) + + CHECK_NO_ERR("Apples should be dead", function() + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") + end) + end + + do CASE "should find target for ChildOf" + local world = jecs.World.new() + + local ChildOf = world:component() + local Name = world:component() + + local function parent(entity) + return world:target(entity, ChildOf) + end + + local bob = world:entity() + local alice = world:entity() + local sara = world:entity() + + world:add(bob, ECS_PAIR(ChildOf, alice)) + world:set(bob, Name, "bob") + world:add(sara, ECS_PAIR(ChildOf, alice)) + world:set(sara, Name, "sara") + CHECK(parent(bob) == alice) -- O(1) + + local count = 0 + for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do + print(name) + count += 1 + end + CHECK(count == 2) + end end) FINISH() \ No newline at end of file From 659b858f5a48c4b57fc41a9e5f8fe07cf37f8c59 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Fri, 17 May 2024 00:19:53 +0200 Subject: [PATCH 05/25] Fix name function --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8740429..2bb28e2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ local Name = world:component() local function parent(entity) return world:target(entity, ChildOf) end -local function name() +local function name(entity) + return world:get(entity, Name) +end local alice = world:entity() world:set(alice, Name, "alice") From 5533cd1c649d3c2698c0b76d1c8921712d8e7f79 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Fri, 17 May 2024 00:37:47 +0200 Subject: [PATCH 06/25] Remove random testkit call --- lib/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/init.lua b/lib/init.lua index 5f11982..aaa975a 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -655,7 +655,7 @@ EmptyQuery.__index = EmptyQuery setmetatable(EmptyQuery, EmptyQuery) export type Query = typeof(EmptyQuery) -local testkit = require("../testkit") + function World.query(world: World, ...: i53): Query -- breaking? if (...) == nil then From 6c2f47bf701122421cb89e3a4ff21f0144844901 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Fri, 17 May 2024 01:05:40 +0200 Subject: [PATCH 07/25] Add todo --- lib/init.lua | 5 +++-- tests/world.lua | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index aaa975a..5cf38ea 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -420,7 +420,7 @@ local function archetypeDelete(world: World, id: i53) world:remove(entity, id) end end - + componentIndex[id] = nil end end @@ -436,9 +436,10 @@ function World.delete(world: World, entityId: i53) local row = record.row archetypeDelete(world, entityId) + -- TODO: should traverse linked )component records to pairs including entityId archetypeDelete(world, ECS_PAIR(entityId, WILDCARD)) archetypeDelete(world, ECS_PAIR(WILDCARD, entityId)) - + if archetype then local entities = archetype.entities local last = #entities diff --git a/tests/world.lua b/tests/world.lua index 2d4bb6d..f0eff7d 100644 --- a/tests/world.lua +++ b/tests/world.lua @@ -276,6 +276,7 @@ TEST("world", function() local bob = world:entity() local alice = world:entity() + world:set(bob, Apples, "apples") world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") @@ -283,11 +284,14 @@ TEST("world", function() local Wildcard = jecs.Wildcard local count = 0 - for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do + for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do count += 1 end + + world:delete(ECS_PAIR(Eats, Apples)) CHECK(count == 0) + CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil) end do CASE "should error when setting invalid pair" From f55993180b01f7e334c80262048d565a764ce0b3 Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 19 May 2024 04:17:22 +0200 Subject: [PATCH 08/25] Release (#43) * Add release * Release 0.1.0 --- .github/workflows/release.yaml | 73 ++++++++++++++++++++++++++++++++++ wally.toml | 2 +- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..b485f17 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,73 @@ +name: Release + +on: + push: + tags: ["v*"] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Install Aftman + uses: ok-nick/setup-aftman@v0.3.0 + + - name: Install Dependencies + run: wally install + + - name: Build + run: rojo build --output build.rbxm default.project.json + + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + with: + name: build + path: build.rbxm + + release: + name: Release + needs: [build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Download Jecs Build + uses: actions/download-artifact@v3 + with: + name: build + path: build + + - name: Rename Build + run: mv build/build.rbxm jecs.rbxm + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + name: Matter ${{ github.ref_name }} + body: | + Matter ${{ github.ref_name }} is now available! + files: | + jecs.rbxm + + publish: + name: Publish + needs: [release] + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v3 + + - name: Install Aftman + uses: ok-nick/setup-aftman@v0.3.0 + + - name: Wally Login + run: wally login --token ${{ secrets.WALLY_AUTH_TOKEN }} + + - name: Publish + run: wally publish \ No newline at end of file diff --git a/wally.toml b/wally.toml index 41797b9..879b3e7 100644 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "ukendio/jecs" -version = "0.1.0-rc.6" +version = "0.1.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" include = ["default.project.json", "lib/**", "lib", "wally.toml", "README.md"] From b8f7bed84cd7001949a3789a92ba680ef19f9ffc Mon Sep 17 00:00:00 2001 From: Marcus Date: Sun, 19 May 2024 04:30:20 +0200 Subject: [PATCH 09/25] Release (#44) * Add release * Release 0.1.0 * Move the benches to bench.project.json --- bench.project.json | 31 +++ benches/exhaustive.lua | 372 ---------------------------- benches/visual/insertion.bench.lua | 1 - benches/visual/wally.toml | 11 + lib/init.spec.lua | 382 ----------------------------- test.project.json | 12 - tests.server.lua | 9 - wally.toml | 5 +- 8 files changed, 43 insertions(+), 780 deletions(-) create mode 100644 bench.project.json delete mode 100644 benches/exhaustive.lua create mode 100644 benches/visual/wally.toml delete mode 100644 lib/init.spec.lua delete mode 100644 tests.server.lua diff --git a/bench.project.json b/bench.project.json new file mode 100644 index 0000000..e55b3ec --- /dev/null +++ b/bench.project.json @@ -0,0 +1,31 @@ +{ + "name": "jecs-test", + "tree": { + "$className": "DataModel", + "StarterPlayer": { + "$className": "StarterPlayer", + "StarterPlayerScripts": { + "$className": "StarterPlayerScripts", + "$path": "tests" + } + }, + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "Lib": { + "$path": "lib" + }, + "rgb": { + "$path": "rgb.lua" + }, + "benches": { + "$path": "benches" + }, + "mirror": { + "$path": "mirror" + }, + "DevPackages": { + "$path": "benches/visual/DevPackages" + } + } + } +} \ No newline at end of file diff --git a/benches/exhaustive.lua b/benches/exhaustive.lua deleted file mode 100644 index 3095c70..0000000 --- a/benches/exhaustive.lua +++ /dev/null @@ -1,372 +0,0 @@ -local testkit = require("../testkit") -local jecs = require("../lib/init") -local ecr = require("../DevPackages/_Index/centau_ecr@0.8.0/ecr/src/ecr") - - -local BENCH, START = testkit.benchmark() - -local function TITLE(title: string) - print() - print(testkit.color.white(title)) -end - -local N = 2^16-2 - -type i53 = number - -do TITLE "create" - BENCH("entity", function() - local world = jecs.World.new() - for i = 1, START(N) do - world:entity() - end - end) -end - ---- component benchmarks - ---todo: perform the same benchmarks for multiple components.? --- these kind of operations only support 1 component at a time, which is --- a shame, especially for archetypes where moving components is expensive. - -do TITLE "set" - BENCH("add 1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - entities[i] = world:entity() - end - - for i = 1, START(N) do - world:set(entities[i], A, i) - end - end) - - BENCH("change 1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local e = world:entity() - world:set(e, A, 1) - - for i = 1, START(N) do - world:set(e, A, 2) - end - end) - -end - -do TITLE "remove" - BENCH("1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - end - - for i = 1, START(N) do - world:remove(entities[i], A) - end - - end) -end - -do TITLE "get" - BENCH("1 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - end - - for i = 1, START(N) do - -- ? curious why the overhead is roughly 80 ns. - world:get(entities[i], A) - end - - end) - - BENCH("2 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B) - end - - end) - - BENCH("3 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - local C = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B, C) - end - - end) - - BENCH("4 component", function() - local world = jecs.World.new() - local entities = {} - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - - for i = 1, N do - local id = world:entity() - entities[i] = id - world:set(id, A, true) - world:set(id, B, true) - world:set(id, C, true) - world:set(id, D, true) - end - - for i = 1, START(N) do - world:get(entities[i], A, B, C, D) - end - - end) -end - -do TITLE (testkit.color.white_underline("Jecs query")) - - local function count(query: () -> ()) - local n = 0 - for _ in query do - n += 1 - end - return n - end - - local function flip() - return math.random() > 0.5 - end - - local function view_bench( - world: jecs.World, - A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53 - ) - - BENCH("1 component", function() - START(count(world:query(A))) - for _ in world:query(A) do end - end) - - BENCH("2 component", function() - START(count(world:query(A, B))) - for _ in world:query(A, B) do end - end) - - BENCH("4 component", function() - START(count(world:query(A, B, C, D))) - for _ in world:query(A, B, C, D) do end - end) - - BENCH("8 component", function() - START(count(world:query(A, B, C, D, E, F, G, H))) - for _ in world:query(A, B, C, D, E, F, G, H) do end - end) - end - - do TITLE "random components" - - local world = jecs.World.new() - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local E = world:component() - local F = world:component() - local G = world:component() - local H = world:component() - local I = world:component() - - for i = 1, N do - local id = world:entity() - if flip() then world:set(id, A, true) end - if flip() then world:set(id, B, true) end - if flip() then world:set(id, C, true) end - if flip() then world:set(id, D, true) end - if flip() then world:set(id, E, true) end - if flip() then world:set(id, F, true) end - if flip() then world:set(id, G, true) end - if flip() then world:set(id, H, true) end - if flip() then world:set(id, I, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - - do TITLE "one component in common" - - local world = jecs.World.new() - - local A = world:component() - local B = world:component() - local C = world:component() - local D = world:component() - local E = world:component() - local F = world:component() - local G = world:component() - local H = world:component() - local I = world:component() - - for i = 1, N do - local id = world:entity() - local a = true - if flip() then world:set(id, B, true) else a = false end - if flip() then world:set(id, C, true) else a = false end - if flip() then world:set(id, D, true) else a = false end - if flip() then world:set(id, E, true) else a = false end - if flip() then world:set(id, F, true) else a = false end - if flip() then world:set(id, G, true) else a = false end - if flip() then world:set(id, H, true) else a = false end - if flip() then world:set(id, I, true) else a = false end - if a then world:set(id, A, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - -end - -do TITLE (testkit.color.white_underline("ECR query")) - - local A = ecr.component() - local B = ecr.component() - local C = ecr.component() - local D = ecr.component() - local E = ecr.component() - local F = ecr.component() - local G = ecr.component() - local H = ecr.component() - local I = ecr.component() - - local function count(query: () -> ()) - local n = 0 - for _ in query do - n += 1 - end - return n - end - - local function flip() - return math.random() > 0.5 - end - - local function view_bench( - world: ecr.Registry, - A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53 - ) - - BENCH("1 component", function() - START(count(world:view(A))) - for _ in world:view(A) do end - end) - - BENCH("2 component", function() - START(count(world:view(A, B))) - for _ in world:view(A, B) do end - end) - - BENCH("4 component", function() - START(count(world:view(A, B, C, D))) - for _ in world:view(A, B, C, D) do end - end) - - BENCH("8 component", function() - START(count(world:view(A, B, C, D, E, F, G, H))) - for _ in world:view(A, B, C, D, E, F, G, H) do end - end) - end - - - do TITLE "random components" - local world = ecr.registry() - - for i = 1, N do - local id = world.create() - if flip() then world:set(id, A, true) end - if flip() then world:set(id, B, true) end - if flip() then world:set(id, C, true) end - if flip() then world:set(id, D, true) end - if flip() then world:set(id, E, true) end - if flip() then world:set(id, F, true) end - if flip() then world:set(id, G, true) end - if flip() then world:set(id, H, true) end - if flip() then world:set(id, I, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - - do TITLE "one component in common" - - local world = ecr.registry() - - for i = 1, N do - local id = world.create() - local a = true - if flip() then world:set(id, B, true) else a = false end - if flip() then world:set(id, C, true) else a = false end - if flip() then world:set(id, D, true) else a = false end - if flip() then world:set(id, E, true) else a = false end - if flip() then world:set(id, F, true) else a = false end - if flip() then world:set(id, G, true) else a = false end - if flip() then world:set(id, H, true) else a = false end - if flip() then world:set(id, I, true) else a = false end - if a then world:set(id, A, true) end - - end - - view_bench(world, A, B, C, D, E, F, G, H, I) - - end - -end \ No newline at end of file diff --git a/benches/visual/insertion.bench.lua b/benches/visual/insertion.bench.lua index e8e50be..8e24f29 100644 --- a/benches/visual/insertion.bench.lua +++ b/benches/visual/insertion.bench.lua @@ -2,7 +2,6 @@ --!native local ReplicatedStorage = game:GetService("ReplicatedStorage") -local rgb = require(ReplicatedStorage.rgb) local Matter = require(ReplicatedStorage.DevPackages.Matter) local jecs = require(ReplicatedStorage.Lib) local ecr = require(ReplicatedStorage.DevPackages.ecr) diff --git a/benches/visual/wally.toml b/benches/visual/wally.toml new file mode 100644 index 0000000..cb0f731 --- /dev/null +++ b/benches/visual/wally.toml @@ -0,0 +1,11 @@ +[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/lib/init.spec.lua b/lib/init.spec.lua deleted file mode 100644 index 8de8de9..0000000 --- a/lib/init.spec.lua +++ /dev/null @@ -1,382 +0,0 @@ -local jecs = require(script.Parent) -local world = jecs.World.new() - -local A, B, C, D = world:entity(), world:entity(), world:entity(), world:entity() -local E, F, G, H = world:entity(), world:entity(), world:entity(), world:entity() -print("A", A) -print("B", B) -print("C", C) -print("D", D) -print("E", E) -print("F", F) -print("G", G) -print("H", H) - -local common = 0 -local N = 2^16-2 -local archetypes = {} -local function flip() - return math.random() >= 0.5 -end - -local amountOfCombination = 0 -for i = 1, N do - local entity = world:entity() - local combination = "" - - if flip() then - combination ..= "2_" - world:set(entity, B, { value = true}) - end - if flip() then - combination ..= "3_" - world:set(entity, C, { value = true}) - end - if flip() then - combination ..= "4_" - world:set(entity, D, { value = true}) - end - if flip() then - combination ..= "5_" - world:set(entity, E, { value = true}) - end - if flip() then - combination ..= "6_" - world:set(entity, F, { value = true}) - end - if flip() then - combination ..= "7_" - world:set(entity, G, { value = true}) - end - if flip() then - combination ..= "8" - world:set(entity, H, { value = true}) - end - - if #combination == 7 then - combination = "1_" .. combination - common += 1 - world:set(entity, A, { value = true}) - end - - if combination:find("2") - and combination:find("3") - and combination:find("4") - and combination:find("6") - then - amountOfCombination += 1 - end - archetypes[combination] = true -end - -return function() - describe("World", function() - it("should add component", function() - local id = world:entity() - world:set(id, A, true) - world:set(id, B, 1) - - local id1 = world:entity() - world:set(id1, A, "hello") - expect(world:get(id, A)).to.equal(true) - expect(world:get(id, B)).to.equal(1) - expect(world:get(id1, A)).to.equal("hello") - end) - - it("should remove component", function() - local Tag = world:entity() - local entities = {} - for i = 1, 10 do - local entity = world:entity() - entities[i] = entity - world:set(entity, Tag) - end - - for i = 1, 10 do - local entity = entities[i] - expect(world:get(entity, Tag)).to.equal(nil) - world:remove(entity, Tag) - end - - end) - - it("should override component data", function() - - local id = world:entity() - world:set(id, A, true) - expect(world:get(id, A)).to.equal(true) - - world:set(id, A, false) - expect(world:get(id, A)).to.equal(false) - - end) - - it("should not query a removed component", function() - local Tag = world:entity() - local AnotherTag = world:entity() - - local entity = world:entity() - world:set(entity, Tag) - world:set(entity, AnotherTag) - world:remove(entity, AnotherTag) - - local added = 0 - for e, t, a in world:query(Tag, AnotherTag) do - added += 1 - end - expect(added).to.equal(0) - end) - - it("should query correct number of compatible archetypes", function() - local added = 0 - for _ in world:query(B, C, D, F) do - added += 1 - end - expect(added).to.equal(amountOfCombination) - end) - - it("should not query poisoned players", function() - local Player = world:entity() - local Health = world:entity() - local Poison = world:entity() - - local one = world:entity() - world:set(one, Player, { name = "alice"}) - world:set(one, Health, 100) - world:set(one, Poison) - - local two = world:entity() - world:set(two, Player, { name = "bob"}) - world:set(two, Health, 90) - - local withoutCount = 0 - for _id, _player in world:query(Player):without(Poison) do - withoutCount += 1 - end - - expect(withoutCount).to.equal(1) - end) - - it("should allow calling world:entity before world:component", function() - for _ = 1, 256 do - world:entity() - end - expect(world:component()).to.be.ok() - end) - - it("should skip iteration", function() - local Position, Velocity = world:entity(), world:entity() - local e = world:entity() - world:set(e, Position, Vector3.zero) - world:set(e, Velocity, Vector3.one) - local added = 0 - for i in world:query(Position):without(Velocity) do - added += 1 - end - expect(added).to.equal(0) - end) - - it("should query all matching entities", function() - - 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) do - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(#entities).to.equal(0) - end) - - it("should query all matching entities when irrelevant component is removed", function() - - - 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) - world:set(id, B, true) - if i > 5 then world:remove(id, B, true) end - entities[i] = id - end - - local added = 0 - for id in world:query(A) do - added += 1 - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(added).to.equal(N) - end) - - it("should query all entities without B", function() - 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) do - local i = table.find(entities, id) - expect(i).to.be.ok() - table.remove(entities, i) - end - - expect(#entities).to.equal(0) - end) - - it("should allow setting components in arbitrary order", function() - local world = jecs.World.new() - - local Health = world:entity() - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) - - expect(world:get(id, Poison)).to.equal(5) - end) - - it("Should allow deleting components", function() - local world = jecs.World.new() - - local Health = world:entity() - local Poison = world:component() - - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) - world:delete(id) - - expect(world:get(id, Poison)).to.never.be.ok() - expect(world:get(id, Health)).to.never.be.ok() - end) - - it("should allow iterating the whole world", function() - local world = jecs.World.new() - - local A, B = world:entity(), world:entity() - - 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 count = 0 - for id, data in world do - count += 1 - if id == eA then - expect(data[A]).to.be.ok() - expect(data[B]).to.never.be.ok() - elseif id == eB then - expect(data[B]).to.be.ok() - expect(data[A]).to.never.be.ok() - elseif id == eAB then - expect(data[A]).to.be.ok() - expect(data[B]).to.be.ok() - end - end - - expect(count).to.equal(5) - end) - - it("should allow querying for relations", function() - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, jecs.pair(Eats, Apples), true) - for e, bool in world:query(jecs.pair(Eats, Apples)) do - expect(e).to.equal(bob) - expect(bool).to.equal(bool) - end - end) - - it("should allow wildcards in queries", function() - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, jecs.pair(Eats, Apples), "bob eats apples") - for e, data in world:query(jecs.pair(Eats, jecs.w)) do - expect(e).to.equal(bob) - expect(data).to.equal("bob eats apples") - end - for e, data in world:query(jecs.pair(jecs.w, Apples)) do - expect(e).to.equal(bob) - expect(data).to.equal("bob eats apples") - end - end) - - it("should match against multiple pairs", function() - local world = jecs.World.new() - local pair = jecs.pair - local Eats = world:entity() - 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)) do - count += 1 - if e == bob then - expect(data).to.equal("bob eats apples") - else - expect(data).to.equal("alice eats oranges") - end - end - - expect(count).to.equal(2) - count = 0 - - for e, data in world:query(pair(w, Apples)) do - count += 1 - expect(data).to.equal("bob eats apples") - end - expect(count).to.equal(1) - end) - end) -end \ No newline at end of file diff --git a/test.project.json b/test.project.json index bdcbd0b..0a3901a 100644 --- a/test.project.json +++ b/test.project.json @@ -22,18 +22,6 @@ }, "mirror": { "$path": "mirror" - }, - "DevPackages": { - "$path": "DevPackages" - } - }, - "TestService": { - "$properties": { - "ExecuteWithStudioRun": true - }, - "$className": "TestService", - "run": { - "$path": "tests.server.lua" } } } diff --git a/tests.server.lua b/tests.server.lua deleted file mode 100644 index 683913d..0000000 --- a/tests.server.lua +++ /dev/null @@ -1,9 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -require(ReplicatedStorage.DevPackages.TestEZ).TestBootstrap:run({ - ReplicatedStorage.Lib, - nil, - { - noXpcallByDefault = true, - }, -}) diff --git a/wally.toml b/wally.toml index 879b3e7..a19b86f 100644 --- a/wally.toml +++ b/wally.toml @@ -4,7 +4,4 @@ version = "0.1.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" include = ["default.project.json", "lib/**", "lib", "wally.toml", "README.md"] -exclude = ["**"] - -[dev-dependencies] -TestEZ = "roblox/testez@0.4.1" \ No newline at end of file +exclude = ["**"] \ No newline at end of file From f1ba9c4a5579ba539891c8020b53c6e3bea6e0a6 Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 24 May 2024 02:58:33 +0200 Subject: [PATCH 10/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bb28e2..059e172 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Just an ECS jecs is a stupidly fast Entity Component System (ECS). - Entity Relationships as first class citizens -- Process tens of thousands of entities with ease every frame +- Iterate 350,000 entities at 60 frames per second - Type-safe [Luau](https://luau-lang.org/) API - Zero-dependency package - Optimized for column-major operations From 0567856a5998fc8e9c0a0bfb8debe0ff16ccba92 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 27 May 2024 03:39:20 +0200 Subject: [PATCH 11/25] Add docs (#45) * Initial commit * Add section for standalone * Fix docs * Add pages to docs * Remove redundant files --- README.md | 2 +- docs/api-types.md | 45 ++ docs/tutorials/quick-start/getting-started.md | 19 + docs/tutorials/quick-start/rbxm.png | Bin 0 -> 34402 bytes lib/init.lua | 273 +++++---- mkdocs.yml | 186 ++++++ tests/world.lua | 552 +++++++++--------- 7 files changed, 665 insertions(+), 412 deletions(-) create mode 100644 docs/api-types.md create mode 100644 docs/tutorials/quick-start/getting-started.md create mode 100644 docs/tutorials/quick-start/rbxm.png create mode 100644 mkdocs.yml diff --git a/README.md b/README.md index 059e172..c5ece64 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ local Name = world:component() local function parent(entity) return world:target(entity, ChildOf) end -local function name(entity) +local function getName(entity) return world:get(entity, Name) end diff --git a/docs/api-types.md b/docs/api-types.md new file mode 100644 index 0000000..b446999 --- /dev/null +++ b/docs/api-types.md @@ -0,0 +1,45 @@ +# World + +A World contains all ECS data +Games can have multiple worlds, although typically only one is necessary. These worlds are isolated from each other, meaning they donot share the same entities nor component IDs. + +--- + +# Entity + +An unique id. + +Entities consist out of a number unique to the entity in the lower 32 bits, and a counter used to track entity liveliness in the upper 32 bits. When an id is recycled, its generation count is increased. This causes recycled ids to be very large (>4 billion), which is normal. + +--- + +# QueryIter + +A result from the `World:query` function. + +Queries are used to iterate over entities that match against the set collection of components. + +Calling it in a loop will allow iteration over the results. + +```lua +for id, enemy, charge, model in world:query(Enemy, Charge, Model) do + -- Do something +end +``` + +### QueryIter.without + +QueryIter.without(iter: QueryIter + ...: [Entity](../api-types/Entity)): QueryIter + + +Create a new Query Iterator from the filter + +#### Parameters + world The world. + ... The collection of components to filter archetypes against. + +#### Returns + +The new query iterator. + diff --git a/docs/tutorials/quick-start/getting-started.md b/docs/tutorials/quick-start/getting-started.md new file mode 100644 index 0000000..bd702d2 --- /dev/null +++ b/docs/tutorials/quick-start/getting-started.md @@ -0,0 +1,19 @@ +# Getting Started +This section will provide a walk through setting up your development environment and a quick overview of the different features and concepts in Jecs with short examples. + +## Installing Jecs + +To use Jecs, you will need to add the library to your project's source folder. + +## Installing as standalone +Head over to the [Releases](https://github.com/ukendio/jecs/releases/latest) page and install the rbxm file. +![jecs.rbxm](rbxm.png) + +## Installing with Wally +Jecs is available as a package on [wally.run](https://wally.run/package/ukendio/jecs) + +Add it to your project's Wally.toml like this: +```toml +[dependencies] +jecs = "0.1.0" # Make sure this is the latest version +``` \ No newline at end of file diff --git a/docs/tutorials/quick-start/rbxm.png b/docs/tutorials/quick-start/rbxm.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8f38d5b842c87789249317e03a70c24f45ab48 GIT binary patch literal 34402 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

J;wRKgqu9eJayK1e z&$AVytU`@SAnaioMNr@;{ouuamPluxB~H|%&-?gOOH5F|#~1C+>D+ViJ>nTRXxkbt zIyHasbYvF$#9}I6QXIVsJfkJ%YoVW{aw7kU(F~YIAbQdbIDZ_A#%uC;Pd>q@*h?$q zM~olW;yp`L6RFy6fTwpI68_=r%=2#(al7Q%p6<)mru)FCgu7=WC3mc|V>rV%nN(9m z-Stm5P6($YvOortT#dB~Rj7F#?vPTF)U4TBBID`%-BXA}xc_h^lKe#e#!dUzMd;nfpLyjmu*jPWfq_Sb(~V;vA5AbQYCi8?PmI2zrUE%e61hY zN1TH7X{3hey*k}XOS?yEB09Iq%=t>#ob;V+5WStfns$`7D7BR`&LmjKdX()`JC%e} z)3WgfkqR>S*99TsL00vpqBy1{_bdD>H*+?(4f72Zew5j-Lp=KVe?R!R7L6oa|9xj& z|CQ6}hSJ1+dm!785Y=}5$jH_Yuk{AjAvGSXl8_ZJg)J)5u4-?c6UA>LU!q=m;}@$G z(7WoSbk1*z2(Y$q=DMJ>Hz`u~QMx(?o@-V7KPv{Zbmw8m?`n8`AfRe}Do;f3pC9WN zSDCiTNjbaK@1tWWK7vMlkt6!$A`DC85q0S@QUn+^c+#iBm6@&qe; zZ-q4nlgt7;v?clAD5)v^=?hp7{lNOPchSU6K%24-Qln&fr>h?>3hHk5!L%82-HHf@ zbagbBW`y-B9tlQq4pL7b)c@fS()lLp+~%KT!ewnZtKA;5pN12$gl|9Ng8G4RA=5z)H9uFX zCpxQSL}PL<>^F~HGm%rM7#!34p@H8gslH!oI>OqaFA-;AQS8?J>!OpR5GeZhG$A;@ zB3AF*p$QUTYmrDAlyG#+X%y1!4{6ph+1a08w0CZPs3-!aewW%RzhC@9ToSE2-r8GP zCH;?w=LtJ-Dp3maeK}D&WsmkL;Q{)we(!qxI?~M43r1~B6uARB9=OZRo zcVz=aiRQNME2YDSjXQuR1JV7-R+rdTL+N<(?lIF=lAcJ(WpCx#oP3>I+n(|slpLK+ zOgx@xs6VnUWqeNWOyQ-|_R>wr`k!uQ z@Ac^HEepgVS~NQ|oo=G*fMK(7RYi~7Oee6NwM|Hf9Z1<$^brq9e0UDK~^OyxR|)B>e7Fy2MHR z#xp<*!f`AVIh)qmvj-GLvcdfx zfKusWY$=f`)b6B^RU5vJ_RCHE$Av{0B_7uUdChy}RF2OO$O5^(55kxmM*cxPkNyS; zY^lV2y3FwGC!qDW!x%KId|{aw&p|(!|8}6i+TWM>M_-xz?Y!PH5xez`{~wL}x8vA< zb^W5vNrJ^0zer$~-hVZ#!@CM24@~M%_zls8{0}g!yOfS749u?GWco?vV##yL!l{Vk zADpJp4WDmepaK&3HHZWEGkwApHAE;J>QX5~9{3r;G7kR|8Ee8L~|w4;n(n9e!RX1lJ`4QzdWfq1$eXwlqP@A zcK{DO0rQtB9!K9T#TMU8A=?q^1Atk+LpSf!FL#!(rm*hl96z+kM7RCs)ic1qXVOYi J`A-br{vYTowmAR* literal 0 HcmV?d00001 diff --git a/lib/init.lua b/lib/init.lua index 5cf38ea..9ef0b10 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -6,10 +6,10 @@ type i53 = number type i24 = number -type Ty = {i53} +type Ty = { i53 } type ArchetypeId = number -type Column = {any} +type Column = { any } type Archetype = { id: number, @@ -21,20 +21,19 @@ type Archetype = { }, types: Ty, type: string | number, - entities: {number}, - columns: {Column}, + entities: { number }, + columns: { Column }, records: {}, } - type Record = { archetype: Archetype, row: number, dense: i24, - componentRecord: ArchetypeMap + componentRecord: ArchetypeMap, } -type EntityIndex = {dense: {[i24]: i53}, sparse: {[i53]: Record}} +type EntityIndex = { dense: { [i24]: i53 }, sparse: { [i53]: Record } } type ArchetypeRecord = number --[[ @@ -48,16 +47,16 @@ TODO: ]] type ArchetypeMap = { - cache: {[number]: ArchetypeRecord}, + cache: { [number]: ArchetypeRecord }, first: ArchetypeMap, second: ArchetypeMap, parent: ArchetypeMap, - size: number + size: number, } -type ComponentIndex = {[i24]: ArchetypeMap} +type ComponentIndex = { [i24]: ArchetypeMap } -type Archetypes = {[ArchetypeId]: Archetype} +type Archetypes = { [ArchetypeId]: Archetype } type ArchetypeDiff = { added: Ty, @@ -70,114 +69,102 @@ local ON_ADD = HI_COMPONENT_ID + 1 local ON_REMOVE = HI_COMPONENT_ID + 2 local ON_SET = HI_COMPONENT_ID + 3 local WILDCARD = HI_COMPONENT_ID + 4 -local REST = HI_COMPONENT_ID + 5 +local REST = HI_COMPONENT_ID + 5 local ECS_ID_FLAGS_MASK = 0x10 local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_GENERATION_MASK = bit32.lshift(1, 16) -local function addFlags(isPair: boolean) - local typeFlags = 0x0 +local function addFlags(isPair: boolean) + local typeFlags = 0x0 - if isPair then - typeFlags = bit32.bor(typeFlags, FLAGS_PAIR) -- HIGHEST bit in the ID. - end + if isPair then + typeFlags = bit32.bor(typeFlags, FLAGS_PAIR) -- HIGHEST bit in the ID. + end if false then - typeFlags = bit32.bor(typeFlags, 0x4) -- Set the second flag to true - end - if false then - typeFlags = bit32.bor(typeFlags, 0x2) -- Set the third flag to true - end - if false then - typeFlags = bit32.bor(typeFlags, 0x1) -- LAST BIT in the ID. - end + typeFlags = bit32.bor(typeFlags, 0x4) -- Set the second flag to true + end + if false then + typeFlags = bit32.bor(typeFlags, 0x2) -- Set the third flag to true + end + if false then + typeFlags = bit32.bor(typeFlags, 0x1) -- LAST BIT in the ID. + end - return typeFlags + return typeFlags end local function ECS_COMBINE(source: number, target: number): i53 - local e = source * 2^28 + target * ECS_ID_FLAGS_MASK - return e + local e = source * 268435456 + target * ECS_ID_FLAGS_MASK + return e end -local function ECS_IS_PAIR(e: number) - return (e % 2^4) // FLAGS_PAIR ~= 0 -end - -function separate(entity: number) - local _typeFlags = entity % 0x10 - entity //= ECS_ID_FLAGS_MASK - return entity // ECS_ENTITY_MASK, entity % ECS_GENERATION_MASK, _typeFlags +local function ECS_IS_PAIR(e: number) + return (e % 2 ^ 4) // FLAGS_PAIR ~= 0 end -- HIGH 24 bits LOW 24 bits local function ECS_GENERATION(e: i53) - e //= 0x10 - return e % ECS_GENERATION_MASK -end - --- SECOND -local function ECS_ENTITY_T_LO(e: i53) - e //= 0x10 - return e // ECS_ENTITY_MASK + e = e // 0x10 + return e % ECS_GENERATION_MASK end local function ECS_GENERATION_INC(e: i53) - local id, generation, flags = separate(e) + local flags = e // 0x10 + local id = flags // ECS_ENTITY_MASK + local generation = flags % ECS_GENERATION_MASK - return ECS_COMBINE(id, generation + 1) + flags + return ECS_COMBINE(id, generation + 1) + flags end -- FIRST gets the high ID -local function ECS_ENTITY_T_HI(entity: i53): i24 - entity //= 0x10 - local first = entity % ECS_ENTITY_MASK - return first +local function ECS_ENTITY_T_HI(e: i53): i24 + e = e // 0x10 + return e % ECS_ENTITY_MASK end -local function ECS_PAIR(pred: number, obj: number) - local first +-- SECOND +local function ECS_ENTITY_T_LO(e: i53) + e = e // 0x10 + return e // ECS_ENTITY_MASK +end + +local function ECS_PAIR(pred: i53, obj: i53): i53 + local first local second: number = WILDCARD - if pred == WILDCARD then + if pred == WILDCARD then first = obj elseif obj == WILDCARD then first = pred else - first = obj + first = obj second = ECS_ENTITY_T_LO(pred) end - return ECS_COMBINE( - ECS_ENTITY_T_LO(first), second) + addFlags(--[[isPair]] true) -end + return ECS_COMBINE(ECS_ENTITY_T_LO(first), second) + addFlags(--[[isPair]] true) +end -local function getAlive(entityIndex: EntityIndex, id: i24) +local function getAlive(entityIndex: EntityIndex, id: i24) local entityId = entityIndex.dense[id] - local record = entityIndex.sparse[entityIndex.dense[id]] - if not record then - error(id.." is not alive") - end - return entityId + return entityId end -- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits -local function ECS_PAIR_RELATION(entityIndex, e) - assert(ECS_IS_PAIR(e)) - return getAlive(entityIndex, ECS_ENTITY_T_HI(e)) +local function ECS_PAIR_RELATION(entityIndex, e) + return getAlive(entityIndex, ECS_ENTITY_T_HI(e)) end -- ECS_PAIR_SECOND gets the relationship / pred / LOW bits -local function ECS_PAIR_OBJECT(entityIndex, e) - assert(ECS_IS_PAIR(e)) - return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) +local function ECS_PAIR_OBJECT(entityIndex, e) + return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) end local function nextEntityId(entityIndex, index: i24): i53 local id = ECS_COMBINE(index, 0) entityIndex.sparse[id] = { - dense = index - } :: Record + dense = index, + } :: Record entityIndex.dense[index] = id return id @@ -224,7 +211,7 @@ local function transitionArchetype( local e1 = sourceEntities[sourceRow] local e2 = sourceEntities[movedAway] - if sourceRow ~= movedAway then + if sourceRow ~= movedAway then sourceEntities[sourceRow] = e2 end @@ -252,7 +239,7 @@ local function newEntity(entityId: i53, record: Record, archetype: Archetype) return record end -local function moveEntity(entityIndex, entityId: i53, record: Record, to: Archetype) +local function moveEntity(entityIndex: EntityIndex, entityId: i53, record: Record, to: Archetype) local sourceRow = record.row local from = record.archetype local destinationRow = archetypeAppend(entityId, to) @@ -265,11 +252,16 @@ local function hash(arr): string | number return table.concat(arr, "_") end -local function ensureComponentRecord(componentIndex: ComponentIndex, archetypeId, componentId, i): ArchetypeMap +local function ensureComponentRecord( + componentIndex: ComponentIndex, + archetypeId: number, + componentId: number, + i: number +): ArchetypeMap local archetypesMap = componentIndex[componentId] if not archetypesMap then - archetypesMap = {size = 0, cache = {}, first = {}, second = {}} :: ArchetypeMap + archetypesMap = { size = 0, cache = {}, first = {}, second = {} } :: ArchetypeMap componentIndex[componentId] = archetypesMap end @@ -279,15 +271,14 @@ local function ensureComponentRecord(componentIndex: ComponentIndex, archetypeId return archetypesMap end -local function ECS_ID_IS_WILDCARD(e) +local function ECS_ID_IS_WILDCARD(e) assert(ECS_IS_PAIR(e)) local first = ECS_ENTITY_T_HI(e) local second = ECS_ENTITY_T_LO(e) return first == WILDCARD or second == WILDCARD end - -local function archetypeOf(world: any, types: {i24}, prev: Archetype?): Archetype +local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archetype local ty = hash(types) local id = world.nextArchetypeId + 1 @@ -301,31 +292,29 @@ local function archetypeOf(world: any, types: {i24}, prev: Archetype?): Archetyp for i, componentId in types do ensureComponentRecord(componentIndex, id, componentId, i) records[componentId] = i - if ECS_IS_PAIR(componentId) then + if ECS_IS_PAIR(componentId) then local relation = ECS_PAIR_RELATION(world.entityIndex, componentId) local object = ECS_PAIR_OBJECT(world.entityIndex, componentId) - + local idr_r = ECS_PAIR(relation, WILDCARD) - ensureComponentRecord( - componentIndex, id, idr_r, i) + ensureComponentRecord(componentIndex, id, idr_r, i) records[idr_r] = i - + local idr_t = ECS_PAIR(WILDCARD, object) - ensureComponentRecord( - componentIndex, id, idr_t, i) + ensureComponentRecord(componentIndex, id, idr_t, i) records[idr_t] = i end columns[i] = {} end local archetype = { - columns = columns; - edges = {}; - entities = {}; - id = id; - records = records; - type = ty; - types = types; + columns = columns, + edges = {}, + entities = {}, + id = id, + records = records, + type = ty, + types = types, } world.archetypeIndex[ty] = archetype world.archetypes[id] = archetype @@ -337,20 +326,20 @@ local World = {} World.__index = World function World.new() local self = setmetatable({ - archetypeIndex = {}; - archetypes = {} :: Archetypes; - componentIndex = {} :: ComponentIndex; + archetypeIndex = {}, + archetypes = {} :: Archetypes, + componentIndex = {} :: ComponentIndex, entityIndex = { dense = {}, - sparse = {} - } :: EntityIndex; + sparse = {}, + } :: EntityIndex, hooks = { - [ON_ADD] = {}; - }; - nextArchetypeId = 0; - nextComponentId = 0; - nextEntityId = 0; - ROOT_ARCHETYPE = (nil :: any) :: Archetype; + [ON_ADD] = {}, + }, + nextArchetypeId = 0, + nextComponentId = 0, + nextEntityId = 0, + ROOT_ARCHETYPE = (nil :: any) :: Archetype, }, World) self.ROOT_ARCHETYPE = archetypeOf(self, {}) return self @@ -380,16 +369,16 @@ function World.target(world: World, entity: i53, relation: i24): i24? local entityIndex = world.entityIndex local record = entityIndex.sparse[entity] local archetype = record.archetype - if not archetype then + if not archetype then return nil end local componentRecord = world.componentIndex[ECS_PAIR(relation, WILDCARD)] - if not componentRecord then + if not componentRecord then return nil end local archetypeRecord = componentRecord.cache[archetype.id] - if not archetypeRecord then + if not archetypeRecord then return nil end @@ -397,37 +386,37 @@ function World.target(world: World, entity: i53, relation: i24): i24? end -- should reuse this logic in World.set instead of swap removing in transition archetype -local function destructColumns(columns, count, row) - if row == count then - for _, column in columns do +local function destructColumns(columns, count, row) + if row == count then + for _, column in columns do column[count] = nil end else - for _, column in columns do + for _, column in columns do column[row] = column[count] column[count] = nil end end end -local function archetypeDelete(world: World, id: i53) - local componentIndex = world.componentIndex +local function archetypeDelete(world: World, id: i53) + local componentIndex = world.componentIndex local archetypesMap = componentIndex[id] local archetypes = world.archetypes - if archetypesMap then - for archetypeId in archetypesMap.cache do - for _, entity in archetypes[archetypeId].entities do + if archetypesMap then + for archetypeId in archetypesMap.cache do + for _, entity in archetypes[archetypeId].entities do world:remove(entity, id) end end - + componentIndex[id] = nil end end -function World.delete(world: World, entityId: i53) +function World.delete(world: World, entityId: i53) local record = world.entityIndex.sparse[entityId] - if not record then + if not record then return end local entityIndex = world.entityIndex @@ -439,12 +428,12 @@ function World.delete(world: World, entityId: i53) -- TODO: should traverse linked )component records to pairs including entityId archetypeDelete(world, ECS_PAIR(entityId, WILDCARD)) archetypeDelete(world, ECS_PAIR(WILDCARD, entityId)) - - if archetype then + + if archetype then local entities = archetype.entities local last = #entities - if row ~= last then + if row ~= last then local entityToMove = entities[last] dense[record.dense] = entityToMove sparse[entityToMove] = record @@ -477,7 +466,7 @@ local function ensureArchetype(world: World, types, prev) return archetypeOf(world, types, prev) end -local function findInsert(types: {i53}, toAdd: i53) +local function findInsert(types: { i53 }, toAdd: i53) for i, id in types do if id == toAdd then return -1 @@ -494,7 +483,7 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53 -- 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 destinationType = table.clone(node.types) local at = findInsert(types, componentId) if at == -1 then @@ -532,7 +521,7 @@ local function archetypeTraverseAdd(world: World, componentId: i53, from: Archet return add end -function World.add(world: World, entityId: i53, componentId: i53) +function World.add(world: World, entityId: i53, componentId: i53) local entityIndex = world.entityIndex local record = entityIndex.sparse[entityId] local from = record.archetype @@ -582,7 +571,7 @@ local function archetypeTraverseRemove(world: World, componentId: i53, from: Arc if not remove then local to = table.clone(from.types) local at = table.find(to, componentId) - if not at then + if not at then return from end table.remove(to, at) @@ -607,7 +596,7 @@ end -- Keeping the function as small as possible to enable inlining local function get(record: Record, componentId: i24) local archetype = record.archetype - if not archetype then + if not archetype then return nil end @@ -644,20 +633,20 @@ end -- the less creation the better local function actualNoOperation() end -local function noop(_self: Query, ...: i53): () -> (number, ...any) +local function noop(_self: Query, ...): () -> () return actualNoOperation :: any end local EmptyQuery = { - __iter = noop; - without = noop; + __iter = noop, + without = noop, } EmptyQuery.__index = EmptyQuery setmetatable(EmptyQuery, EmptyQuery) export type Query = typeof(EmptyQuery) -function World.query(world: World, ...: i53): Query +function World.query(world: World, ...): Query -- breaking? if (...) == nil then error("Missing components") @@ -666,7 +655,7 @@ function World.query(world: World, ...: i53): Query local compatibleArchetypes = {} local length = 0 - local components = {...} + local components = { ... } local archetypes = world.archetypes local queryLength = #components @@ -707,8 +696,8 @@ function World.query(world: World, ...: i53): Query length += 1 compatibleArchetypes[length] = { - archetype = archetype, - indices = indices + archetype = archetype, + indices = indices, } end @@ -721,7 +710,7 @@ function World.query(world: World, ...: i53): Query preparedQuery.__index = preparedQuery function preparedQuery:without(...) - local withoutComponents = {...} + local withoutComponents = { ... } for i = #compatibleArchetypes, 1, -1 do local archetype = compatibleArchetypes[i].archetype local records = archetype.records @@ -828,16 +817,16 @@ function World.__iter(world: World): () -> (number?, unknown?) local sparse = world.entityIndex.sparse local last - return function() + return function() local lastEntity, entityId = next(dense, last) - if not lastEntity then + if not lastEntity then return end last = lastEntity local record = sparse[entityId] local archetype = record.archetype - if not archetype then + if not archetype then -- Returns only the entity id as an entity without data should not return -- data and allow the user to get an error if they don't handle the case. return entityId @@ -851,17 +840,17 @@ function World.__iter(world: World): () -> (number?, unknown?) -- We use types because the key should be the component ID not the column index entityData[types[i]] = column[row] end - + return entityId, entityData end end return table.freeze({ - World = World; + World = World, - OnAdd = ON_ADD; - OnRemove = ON_REMOVE; - OnSet = ON_SET; + OnAdd = ON_ADD, + OnRemove = ON_REMOVE, + OnSet = ON_SET, Wildcard = WILDCARD, w = WILDCARD, Rest = REST, diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..5029861 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,186 @@ +site_name: Fusion +site_url: https://elttob.uk/Fusion/ +repo_name: dphfox/Fusion +repo_url: https://github.com/dphfox/Fusion + +extra: + version: + provider: mike + +theme: + name: material + custom_dir: docs/assets/overrides + logo: assets/logo + favicon: assets/logo-dark.svg + palette: + - media: "(prefers-color-scheme: dark)" + scheme: fusiondoc-dark + toggle: + icon: octicons/sun-24 + title: Switch to light theme + - media: "(prefers-color-scheme: light)" + scheme: fusiondoc-light + toggle: + icon: octicons/moon-24 + title: Switch to dark theme + font: + text: Plus Jakarta Sans + code: JetBrains Mono + features: + - navigation.tabs + - navigation.top + - navigation.sections + - navigation.instant + - navigation.indexes + - search.suggest + - search.highlight + icon: + repo: octicons/mark-github-16 + +extra_css: + - assets/theme/fusiondoc.css + - assets/theme/colours.css + - assets/theme/code.css + - assets/theme/paragraph.css + - assets/theme/page.css + - assets/theme/admonition.css + - assets/theme/404.css + - assets/theme/api-reference.css + - assets/theme/dev-tools.css + +extra_javascript: + - assets/scripts/smooth-scroll.js + +nav: + - Home: index.md + - Tutorials: + - Get Started: tutorials/index.md + - Installing Fusion: tutorials/get-started/installing-fusion.md + - Developer Tools: tutorials/get-started/developer-tools.md + - Getting Help: tutorials/get-started/getting-help.md + - Fundamentals: + - Scopes: tutorials/fundamentals/scopes.md + - Values: tutorials/fundamentals/values.md + - Observers: tutorials/fundamentals/observers.md + - Computeds: tutorials/fundamentals/computeds.md + - Tables: + - ForValues: tutorials/tables/forvalues.md + - ForKeys: tutorials/tables/forkeys.md + - ForPairs: tutorials/tables/forpairs.md + - Animation: + - Tweens: tutorials/animation/tweens.md + - Springs: tutorials/animation/springs.md + - Roblox: + - Hydration: tutorials/roblox/hydration.md + - New Instances: tutorials/roblox/new-instances.md + - Parenting: tutorials/roblox/parenting.md + - Events: tutorials/roblox/events.md + - Change Events: tutorials/roblox/change-events.md + - Outputs: tutorials/roblox/outputs.md + - References: tutorials/roblox/references.md + - Best Practices: + - Components: tutorials/best-practices/components.md + - Instance Handling: tutorials/best-practices/instance-handling.md + - Callbacks: tutorials/best-practices/callbacks.md + - State: tutorials/best-practices/state.md + - Sharing Values: tutorials/best-practices/sharing-values.md + - Error Safety: tutorials/best-practices/error-safety.md + - Optimisation: tutorials/best-practices/optimisation.md + + - Examples: + - Home: examples/index.md + - Cookbook: + - examples/cookbook/index.md + - Player List: examples/cookbook/player-list.md + - Animated Computed: examples/cookbook/animated-computed.md + - Fetch Data From Server: examples/cookbook/fetch-data-from-server.md + - Light & Dark Theme: examples/cookbook/light-and-dark-theme.md + - Button Component: examples/cookbook/button-component.md + - Loading Spinner: examples/cookbook/loading-spinner.md + - Drag & Drop: examples/cookbook/drag-and-drop.md + - API Reference: + - api-reference/index.md + - General: + - Errors: api-reference/general/errors.md + - Types: + - Contextual: api-reference/general/types/contextual.md + - Version: api-reference/general/types/version.md + - Members: + - Contextual: api-reference/general/members/contextual.md + - Safe: api-reference/general/members/safe.md + - version: api-reference/general/members/version.md + - Memory: + - Types: + - Scope: api-reference/memory/types/scope.md + - ScopedObject: api-reference/memory/types/scopedobject.md + - Task: api-reference/memory/types/task.md + - Members: + - deriveScope: api-reference/memory/members/derivescope.md + - doCleanup: api-reference/memory/members/docleanup.md + - scoped: api-reference/memory/members/scoped.md + - State: + - Types: + - UsedAs: api-reference/state/types/usedas.md + - Computed: api-reference/state/types/computed.md + - Dependency: api-reference/state/types/dependency.md + - Dependent: api-reference/state/types/dependent.md + - For: api-reference/state/types/for.md + - Observer: api-reference/state/types/observer.md + - StateObject: api-reference/state/types/stateobject.md + - Use: api-reference/state/types/use.md + - Value: api-reference/state/types/value.md + - Members: + - Computed: api-reference/state/members/computed.md + - ForKeys: api-reference/state/members/forkeys.md + - ForPairs: api-reference/state/members/forpairs.md + - ForValues: api-reference/state/members/forvalues.md + - Observer: api-reference/state/members/observer.md + - peek: api-reference/state/members/peek.md + - Value: api-reference/state/members/value.md + - Roblox: + - Types: + - Child: api-reference/roblox/types/child.md + - PropertyTable: api-reference/roblox/types/propertytable.md + - SpecialKey: api-reference/roblox/types/specialkey.md + - Members: + - Attribute: api-reference/roblox/members/attribute.md + - AttributeChange: api-reference/roblox/members/attributechange.md + - AttributeOut: api-reference/roblox/members/attributeout.md + - Children: api-reference/roblox/members/children.md + - Hydrate: api-reference/roblox/members/hydrate.md + - New: api-reference/roblox/members/new.md + - OnChange: api-reference/roblox/members/onchange.md + - OnEvent: api-reference/roblox/members/onevent.md + - Out: api-reference/roblox/members/out.md + - Ref: api-reference/roblox/members/ref.md + - Animation: + - Types: + - Animatable: api-reference/animation/types/animatable.md + - Spring: api-reference/animation/types/spring.md + - Tween: api-reference/animation/types/tween.md + - Members: + - Tween: api-reference/animation/members/tween.md + - Spring: api-reference/animation/members/spring.md + - Extras: + - Home: extras/index.md + - Backgrounds: extras/backgrounds.md + - Brand Guidelines: extras/brand-guidelines.md + +markdown_extensions: + - admonition + - attr_list + - meta + - md_in_html + - pymdownx.superfences + - pymdownx.betterem + - pymdownx.details + - pymdownx.tabbed: + alternate_style: true + - pymdownx.inlinehilite + - toc: + permalink: true + - pymdownx.highlight: + guess_lang: false + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg diff --git a/tests/world.lua b/tests/world.lua index f0eff7d..79a2686 100644 --- a/tests/world.lua +++ b/tests/world.lua @@ -1,5 +1,5 @@ -local testkit = require("../testkit") local jecs = require("../lib/init") +local testkit = require("../testkit") local __ = jecs.Wildcard local ECS_ID, ECS_GENERATION = jecs.ECS_ID, jecs.ECS_GENERATION local ECS_GENERATION_INC = jecs.ECS_GENERATION_INC @@ -11,329 +11,343 @@ local ECS_PAIR_OBJECT = jecs.ECS_PAIR_OBJECT local TEST, CASE, CHECK, FINISH, SKIP = testkit.test() local function CHECK_NO_ERR(s: string, fn: (T...) -> (), ...: T...) - local ok, err: string? = pcall(fn, ...) + local ok, err: string? = pcall(fn, ...) - if not CHECK(not ok, 2) then - local i = string.find(err :: string, " ") - assert(i) - local msg = string.sub(err :: string, i+1) - CHECK(msg == s, 2) - end + if not CHECK(not ok, 2) then + local i = string.find(err :: string, " ") + assert(i) + local msg = string.sub(err :: string, i + 1) + CHECK(msg == s, 2) + end end local N = 10 -TEST("world", function() - do CASE "should be iterable" - local world = jecs.World.new() - 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) +TEST("world", function() + do + CASE("should be iterable") + local world = jecs.World.new() + 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 count = 0 - for id, data in world do - count += 1 - if id == eA then - CHECK(data[A] == true) - CHECK(data[B] == nil) - elseif id == eB then - CHECK(data[A] == nil) - CHECK(data[B] == true) - elseif id == eAB then - CHECK(data[A] == true) - CHECK(data[B] == true) - end - end + local count = 0 + for id, data in world do + count += 1 + if id == eA then + CHECK(data[A] == true) + CHECK(data[B] == nil) + elseif id == eB then + CHECK(data[A] == nil) + CHECK(data[B] == true) + elseif id == eAB then + CHECK(data[A] == true) + CHECK(data[B] == true) + end + end - -- components are registered in the entity index as well - -- so this test has to add 2 to account for them - CHECK(count == 3 + 2) - end + -- components are registered in the entity index as well + -- so this test has to add 2 to account for them + CHECK(count == 3 + 2) + end - do CASE "should query all matching entities" - local world = jecs.World.new() - local A = world:component() - local B = world:component() + do + CASE("should query all matching entities") + local world = jecs.World.new() + local A = world:component() + local B = world:component() - local entities = {} - for i = 1, N do - local id = world:entity() + 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 + 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) do - table.remove(entities, CHECK(table.find(entities, id))) - end + for id in world:query(A) do + table.remove(entities, CHECK(table.find(entities, id))) + end - CHECK(#entities == 0) + CHECK(#entities == 0) + end - end + do + CASE("should query all matching entities when irrelevant component is removed") + local world = jecs.World.new() + local A = world:component() + local B = world:component() + local C = world:component() - do CASE "should query all matching entities when irrelevant component is removed" - local world = jecs.World.new() - local A = world:component() - local B = world:component() - local C = world:component() + local entities = {} + for i = 1, N do + local id = world:entity() - 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 - -- 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) do + added += 1 + table.remove(entities, CHECK(table.find(entities, id))) + end - local added = 0 - for id in world:query(A) do - added += 1 - table.remove(entities, CHECK(table.find(entities, id))) - end + CHECK(added == N) + 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() - 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() - 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 - 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) do + table.remove(entities, CHECK(table.find(entities, id))) + end - for id in world:query(A):without(B) do - table.remove(entities, CHECK(table.find(entities, id))) - end + CHECK(#entities == 0) + end - CHECK(#entities == 0) + do + CASE("should allow setting components in arbitrary order") + local world = jecs.World.new() - end + local Health = world:entity() + local Poison = world:component() - do CASE "should allow setting components in arbitrary order" - local world = jecs.World.new() + local id = world:entity() + world:set(id, Poison, 5) + world:set(id, Health, 50) - local Health = world:entity() - local Poison = world:component() + CHECK(world:get(id, Poison) == 5) + end - local id = world:entity() - world:set(id, Poison, 5) - world:set(id, Health, 50) + do + CASE("should allow deleting components") + local world = jecs.World.new() - CHECK(world:get(id, Poison) == 5) - end + local Health = world:entity() + local Poison = world:component() - do CASE "should allow deleting components" - local world = jecs.World.new() + 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) - local Health = world:entity() - local Poison = world:component() + world:delete(id) - 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) + 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 - world:delete(id) + do + CASE("should allow remove that doesn't exist on entity") + local world = jecs.World.new() - CHECK(world:get(id, Poison) == nil) - CHECK(world:get(id, Health) == nil) - CHECK(world:get(id1, Poison) == 500) - CHECK(world:get(id1, Health) == 50) + local Health = world:entity() + local Poison = world:component() - end + local id = world:entity() + world:set(id, Health, 50) + world:remove(id, Poison) - do CASE "should allow remove that doesn't exist on entity" - local world = jecs.World.new() + CHECK(world:get(id, Poison) == nil) + CHECK(world:get(id, Health) == 50) + end - local Health = world:entity() - local Poison = world:component() + do + CASE("should increment generation") + local world = jecs.World.new() + local e = world:entity() + CHECK(ECS_ID(e) == 1 + jecs.Rest) + CHECK(getAlive(world.entityIndex, ECS_ID(e)) == e) + CHECK(ECS_GENERATION(e) == 0) -- 0 + e = ECS_GENERATION_INC(e) + CHECK(ECS_GENERATION(e) == 1) -- 1 + end - local id = world:entity() - world:set(id, Health, 50) - world:remove(id, Poison) + do + CASE("should get alive from index in the dense array") + local world = jecs.World.new() + local _e = world:entity() + local e2 = world:entity() + local e3 = world:entity() - CHECK(world:get(id, Poison) == nil) - CHECK(world:get(id, Health) == 50) - end + CHECK(IS_PAIR(world:entity()) == false) - do CASE "should increment generation" - local world = jecs.World.new() - local e = world:entity() - CHECK(ECS_ID(e) == 1 + jecs.Rest) - CHECK(getAlive(world.entityIndex, ECS_ID(e)) == e) - CHECK(ECS_GENERATION(e) == 0) -- 0 - e = ECS_GENERATION_INC(e) - CHECK(ECS_GENERATION(e) == 1) -- 1 - end + local pair = ECS_PAIR(e2, e3) + CHECK(IS_PAIR(pair) == true) + CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2) + CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3) + end - do CASE "should get alive from index in the dense array" - local world = jecs.World.new() - local _e = world:entity() - local e2 = world:entity() - local e3 = world:entity() + do + CASE("should allow querying for relations") + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local bob = world:entity() - CHECK(IS_PAIR(world:entity()) == false) + world:set(bob, ECS_PAIR(Eats, Apples), true) + for e, bool in world:query(ECS_PAIR(Eats, Apples)) do + CHECK(e == bob) + CHECK(bool) + end + end - local pair = ECS_PAIR(e2, e3) - CHECK(IS_PAIR(pair) == true) - CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2) - CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3) - end + do + CASE("should allow wildcards in queries") + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local bob = world:entity() - do CASE "should allow querying for relations" - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, ECS_PAIR(Eats, Apples), true) - for e, bool in world:query(ECS_PAIR(Eats, Apples)) do - CHECK(e == bob) - CHECK(bool) - end - end - - do CASE "should allow wildcards in queries" - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() - - world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") - - local w = jecs.Wildcard - for e, data in world:query(ECS_PAIR(Eats, w)) do - CHECK(e == bob) - CHECK(data == "bob eats apples") - end - for e, data in world:query(ECS_PAIR(w, Apples)) do - CHECK(e == bob) - CHECK(data == "bob eats apples") - end - end + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") - do CASE "should match against multiple pairs" - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local Oranges =world:entity() - local bob = world:entity() - local alice = world:entity() - - world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") - world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") - - local w = jecs.Wildcard - local count = 0 - for e, data in world:query(ECS_PAIR(Eats, w)) do - count += 1 - if e == bob then - CHECK(data == "bob eats apples") - else - CHECK(data == "alice eats oranges") - end - end + local w = jecs.Wildcard + for e, data in world:query(ECS_PAIR(Eats, w)) do + CHECK(e == bob) + CHECK(data == "bob eats apples") + end + for e, data in world:query(ECS_PAIR(w, Apples)) do + CHECK(e == bob) + CHECK(data == "bob eats apples") + end + end - CHECK(count == 2) - count = 0 + do + CASE("should match against multiple pairs") + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local Oranges = world:entity() + local bob = world:entity() + local alice = world:entity() - for e, data in world:query(ECS_PAIR(w, Apples)) do - count += 1 - CHECK(data == "bob eats apples") - end - CHECK(count == 1) - end + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") + world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") - do CASE "should only relate alive entities" - - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local Oranges = world:entity() - local bob = world:entity() - local alice = world:entity() - - world:set(bob, Apples, "apples") - world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") - world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") + local w = jecs.Wildcard + local count = 0 + for e, data in world:query(ECS_PAIR(Eats, w)) do + count += 1 + if e == bob then + CHECK(data == "bob eats apples") + else + CHECK(data == "alice eats oranges") + end + end - world:delete(Apples) - local Wildcard = jecs.Wildcard - - local count = 0 - for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do - count += 1 - end + CHECK(count == 2) + count = 0 - world:delete(ECS_PAIR(Eats, Apples)) - - CHECK(count == 0) - CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil) - end + for e, data in world:query(ECS_PAIR(w, Apples)) do + count += 1 + CHECK(data == "bob eats apples") + end + CHECK(count == 1) + end - do CASE "should error when setting invalid pair" - local world = jecs.World.new() - local Eats = world:entity() - local Apples = world:entity() - local bob = world:entity() + do + CASE("should only relate alive entities") - world:delete(Apples) + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local Oranges = world:entity() + local bob = world:entity() + local alice = world:entity() - CHECK_NO_ERR("Apples should be dead", function() - world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") - end) - end + world:set(bob, Apples, "apples") + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") + world:set(alice, ECS_PAIR(Eats, Oranges), "alice eats oranges") - do CASE "should find target for ChildOf" - local world = jecs.World.new() + world:delete(Apples) + local Wildcard = jecs.Wildcard - local ChildOf = world:component() - local Name = world:component() + local count = 0 + for _, data in world:query(ECS_PAIR(Wildcard, Apples)) do + count += 1 + end - local function parent(entity) - return world:target(entity, ChildOf) - end + world:delete(ECS_PAIR(Eats, Apples)) - local bob = world:entity() - local alice = world:entity() - local sara = world:entity() - - world:add(bob, ECS_PAIR(ChildOf, alice)) - world:set(bob, Name, "bob") - world:add(sara, ECS_PAIR(ChildOf, alice)) - world:set(sara, Name, "sara") - CHECK(parent(bob) == alice) -- O(1) + CHECK(count == 0) + CHECK(world:get(bob, ECS_PAIR(Eats, Apples)) == nil) + end - local count = 0 - for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do - print(name) - count += 1 - end - CHECK(count == 2) - end + do + CASE("should error when setting invalid pair") + local world = jecs.World.new() + local Eats = world:entity() + local Apples = world:entity() + local bob = world:entity() + + world:delete(Apples) + + world:set(bob, ECS_PAIR(Eats, Apples), "bob eats apples") + end + + do + CASE("should find target for ChildOf") + local world = jecs.World.new() + + local ChildOf = world:component() + local Name = world:component() + + local function parent(entity) + return world:target(entity, ChildOf) + end + + local bob = world:entity() + local alice = world:entity() + local sara = world:entity() + + world:add(bob, ECS_PAIR(ChildOf, alice)) + world:set(bob, Name, "bob") + world:add(sara, ECS_PAIR(ChildOf, alice)) + world:set(sara, Name, "sara") + CHECK(parent(bob) == alice) -- O(1) + + local count = 0 + for _, name in world:query(Name, ECS_PAIR(ChildOf, alice)) do + print(name) + count += 1 + end + CHECK(count == 2) + end end) -FINISH() \ No newline at end of file +FINISH() + From c0594c7e75b97a156d031a29f91cbc1cef63e3f8 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 27 May 2024 03:50:46 +0200 Subject: [PATCH 12/25] Fix docs --- docs/api-types.md | 2 +- docs/api/world.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 8 +- 3 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 docs/api/world.md diff --git a/docs/api-types.md b/docs/api-types.md index b446999..cce3856 100644 --- a/docs/api-types.md +++ b/docs/api-types.md @@ -30,7 +30,7 @@ end ### QueryIter.without QueryIter.without(iter: QueryIter - ...: [Entity](../api-types/Entity)): QueryIter + ...: [Entity](#Entity)): QueryIter Create a new Query Iterator from the filter diff --git a/docs/api/world.md b/docs/api/world.md new file mode 100644 index 0000000..06a8a66 --- /dev/null +++ b/docs/api/world.md @@ -0,0 +1,188 @@ +# World + +### World.new + +World.new(): [World](../api-types/World) + +Create a new world. + +#### Returns +A new world + +--- + +### World.entity + +World.entity(world: [World](../api-types/World)): [Entity](../api-types/Entity) + +Creates an entity in the world. + +#### Returns +A new entiity id + +--- + +### World.target + +World.target(world: [World](../api-types/World), + entity: [Entity](../api-types/Entity), + rel: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) + +Get the target of a relationship. + +This will return a target (second element of a pair) of the entity for the specified relationship. + +#### Parameters + world The world. + entity The entity. + rel The relationship between the entity and the target. + +#### Returns + +The first target for the relationship + +--- + +### World.add + +World.add(world: [World](../api-types/World), + entity: [Entity](../api-types/Entity), + id: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) + +Add a (component) id to an entity. + +This operation adds a single (component) id to an entity. +If the entity already has the id, this operation will have no side effects. + +#### Parameters + world The world. + entity The entity. + id The id to add. + +--- + +### World.remove + +World.remove(world: [World](../api-types/World), + entity: [Entity](../api-types/Entity), + id: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) + +Remove a (component) id to an entity. + +This operation removes a single (component) id to an entity. +If the entity already has the id, this operation will have no side effects. + +#### Parameters + world The world. + entity The entity. + id The id to add. + +--- + +### World.get + +World.get(world: [World](../api-types/World), + entity: [Entity](../api-types/Entity), + id: [Entity](../api-types/Entity)): any + +Gets the component data. + +#### Parameters + world The world. + entity The entity. + id The id of component to get. + +#### Returns +The component data, nil if the entity does not have the componnet. + +--- + +### World.set + +World.set(world: [World](../api-types/World), + entity: [Entity](../api-types/Entity), + id: [Entity](../api-types/Entity) + data: any) + +Set the value of a component. + +#### Parameters + world The world. + entity The entity. + id The id of the componment set. + data The data to the component. + +--- + +### World.query + +World.query(world: [World](../api-types/World), + ...: [Entity](../api-types/Entity)): [QueryIter](../api-types/QueryIter) + +Create a QueryIter from the list of filters. + +#### Parameters + world The world. + ... The collection of components to match entities against. + +#### Returns + +The query iterator. + +--- + +# Pair + +### pair + +pair(first: [Entity](../api-types/Entity), + second: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) + +Creates a composite key. + +#### Parameters + first The first element. + second The second element. + +#### Returns + +The pair of the two elements + +--- + +### IS_PAIR + +jecs.IS_PAIR(id: [Entity](../api-types/Entity)): boolean + +Creates a composite key. + +#### Parameters + id The id to check. + +#### Returns + +If id is a pair. + +--- + +# Constants + +### OnAdd + +--- + +### OnRemove + +--- + +### Rest + +--- + +### OnSet + +--- + +### Wildcard + +Matches any id, returns all matches. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 5029861..1a47333 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,7 +1,7 @@ -site_name: Fusion -site_url: https://elttob.uk/Fusion/ -repo_name: dphfox/Fusion -repo_url: https://github.com/dphfox/Fusion +site_name: Jecs +site_url: jecs.github.io/jecs +repo_name: ukendio/jecs +repo_url: https://github.com/ukendio/jecs extra: version: From 3d689ebdd84e9aa115115ca33215a49eada3fd4d Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 27 May 2024 11:33:59 +0200 Subject: [PATCH 13/25] Fix links --- docs/api/world.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/api/world.md b/docs/api/world.md index 06a8a66..39097bc 100644 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -2,7 +2,7 @@ ### World.new -World.new(): [World](../api-types/World) +World.new(): [World](../api-types#World) Create a new world. @@ -13,7 +13,7 @@ A new world ### World.entity -World.entity(world: [World](../api-types/World)): [Entity](../api-types/Entity) +World.entity(world: [World](../api-types#World)): [Entity](../api-types#Entity) Creates an entity in the world. @@ -24,9 +24,9 @@ A new entiity id ### World.target -World.target(world: [World](../api-types/World), - entity: [Entity](../api-types/Entity), - rel: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) +World.target(world: [World](../api-types#World), + entity: [Entity](../api-types#Entity), + rel: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) Get the target of a relationship. @@ -45,9 +45,9 @@ The first target for the relationship ### World.add -World.add(world: [World](../api-types/World), - entity: [Entity](../api-types/Entity), - id: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) +World.add(world: [World](../api-types#World), + entity: [Entity](../api-types#Entity), + id: [Entity](../api-types#Entity)): [Entity](..#api-types/Entity) Add a (component) id to an entity. @@ -63,9 +63,9 @@ If the entity already has the id, this operation will have no side effects. ### World.remove -World.remove(world: [World](../api-types/World), - entity: [Entity](../api-types/Entity), - id: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) +World.remove(world: [World](../api-types#World), + entity: [Entity](../api-types#Entity), + id: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) Remove a (component) id to an entity. @@ -135,8 +135,8 @@ The query iterator. ### pair -pair(first: [Entity](../api-types/Entity), - second: [Entity](../api-types/Entity)): [Entity](../api-types/Entity) +pair(first: [Entity](../api-types#Entity), + second: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) Creates a composite key. @@ -152,7 +152,7 @@ The pair of the two elements ### IS_PAIR -jecs.IS_PAIR(id: [Entity](../api-types/Entity)): boolean +jecs.IS_PAIR(id: [Entity](../api-types#Entity)): boolean Creates a composite key. @@ -185,4 +185,4 @@ If id is a pair. ### Wildcard -Matches any id, returns all matches. \ No newline at end of file +Matches any id, returns all matches. From 94cc3ee8ea8f87bba1e96b5b82a80bc6f6742a80 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 27 May 2024 11:45:41 +0200 Subject: [PATCH 14/25] Fix links --- docs/api/world.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/api/world.md b/docs/api/world.md index 39097bc..fa52403 100644 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -2,7 +2,7 @@ ### World.new -World.new(): [World](../api-types#World) +World.new(): [World](../api-typesk.md#World) Create a new world. @@ -13,7 +13,7 @@ A new world ### World.entity -World.entity(world: [World](../api-types#World)): [Entity](../api-types#Entity) +World.entity(world: [World](../api-types.md#World)): [Entity](../api-types.md#Entity) Creates an entity in the world. @@ -24,9 +24,9 @@ A new entiity id ### World.target -World.target(world: [World](../api-types#World), - entity: [Entity](../api-types#Entity), - rel: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) +World.target(world: [World](../api-types.md#World), + entity: [Entity](../api-types.md#Entity), + rel: [Entity](../api-types.md#Entity)): [Entity](../api-types.md#Entity) Get the target of a relationship. @@ -45,9 +45,9 @@ The first target for the relationship ### World.add -World.add(world: [World](../api-types#World), - entity: [Entity](../api-types#Entity), - id: [Entity](../api-types#Entity)): [Entity](..#api-types/Entity) +World.add(world: [World](../api-types.md#World), + entity: [Entity](../api-types.md#Entity), + id: [Entity](../api-types.md#Entity)): [Entity](..#api-types.md#Entity) Add a (component) id to an entity. @@ -81,9 +81,9 @@ If the entity already has the id, this operation will have no side effects. ### World.get -World.get(world: [World](../api-types/World), - entity: [Entity](../api-types/Entity), - id: [Entity](../api-types/Entity)): any +World.get(world: [World](../api-types.md#World), + entity: [Entity](../api-types.md#Entity), + id: [Entity](../api-types.md#Entity)): any Gets the component data. @@ -99,9 +99,9 @@ The component data, nil if the entity does not have the componnet. ### World.set -World.set(world: [World](../api-types/World), - entity: [Entity](../api-types/Entity), - id: [Entity](../api-types/Entity) +World.set(world: [World](../api-types.md#World), + entity: [Entity](../api-types.md#Entity), + id: [Entity](../api-types.md#Entity) data: any) Set the value of a component. @@ -116,8 +116,8 @@ Set the value of a component. ### World.query -World.query(world: [World](../api-types/World), - ...: [Entity](../api-types/Entity)): [QueryIter](../api-types/QueryIter) +World.query(world: [World](../api-types.md#World), + ...: [Entity](../api-types.mdEntity)): [QueryIter](../api-types.md#QueryIter) Create a QueryIter from the list of filters. From 1498a28c3fd665b4a7215789031eb6e9a6d550e0 Mon Sep 17 00:00:00 2001 From: Marcus Date: Mon, 27 May 2024 11:46:42 +0200 Subject: [PATCH 15/25] Update world.md --- docs/api/world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/world.md b/docs/api/world.md index fa52403..93f4e7c 100644 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -2,7 +2,7 @@ ### World.new -World.new(): [World](../api-typesk.md#World) +World.new(): [World](../api-types.md#World) Create a new world. From 05d61bd6da988b079ebb0fb210773f9d7b044d7e Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 27 May 2024 20:09:22 +0200 Subject: [PATCH 16/25] Fix doc --- docs/api/world.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/world.md b/docs/api/world.md index fa52403..c7f251b 100644 --- a/docs/api/world.md +++ b/docs/api/world.md @@ -135,8 +135,7 @@ The query iterator. ### pair -pair(first: [Entity](../api-types#Entity), - second: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) +pair(first: [Entity](../api-types#Entity), second: [Entity](../api-types#Entity)): [Entity](../api-types#Entity) Creates a composite key. From 63f40a22c00dbdfe20b32573438c647178c3ab7c Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 5 Jun 2024 23:34:24 +0200 Subject: [PATCH 17/25] Add Types (#21) * Add Types * Fix return type of without * implement types * make types more specific * reduce query to 10 generics * Remove second type pack --------- Co-authored-by: alice <166900055+alicesaidhi@users.noreply.github.com> --- lib/init.lua | 83 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index 9ef0b10..ebcfd0e 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -845,14 +845,79 @@ function World.__iter(world: World): () -> (number?, unknown?) end end -return table.freeze({ - World = World, +-- __nominal_type_dont_use could not be any or T as it causes a type error +-- or produces a union +export type Component = T & {__nominal_type_dont_use: never} +export type Entity = Component +export type Relationship = Component +type ctype = Component - OnAdd = ON_ADD, - OnRemove = ON_REMOVE, - OnSet = ON_SET, - Wildcard = WILDCARD, - w = WILDCARD, +export type QueryShim = typeof(setmetatable( + {} :: { + --- Excludes the given selection from the query + without: (QueryShim, U...) -> QueryShim + }, + {} :: { + __iter: (QueryShim) -> () -> (Entity, T...) + } +)) + +export type WorldShim = typeof(setmetatable( + {} :: { + + --- Creates a new entity + entity: (WorldShim) -> Entity, + --- Creates a new entity located in the first 256 ids. + --- These should be used for static components for fast access. + component: (WorldShim) -> Component, + --- Gets the target of an relationship. For example, when a user calls + --- `world:target(id, ChildOf(parent))`, you will obtain the parent entity. + target: (WorldShim, id: Entity, relation: Relationship) -> Entity?, + --- Deletes an entity and all it's related components and relationships. + delete: (WorldShim, id: Entity) -> (), + + --- Adds a component to the entity with no value + add: (WorldShim, id: Entity, component: Component) -> (), + --- Assigns a value to a component on the given entity + set: (WorldShim, id: Entity, component: Component, data: T) -> (), + --- Removes a component from the given entity + remove: (WorldShim, id: Entity, component: Component) -> (), + --- Retrieves the value of up to 4 components. These values may be nil. + get: + ((WorldShim, id: any, ctype) -> A) + & ((WorldShim, id: Entity, ctype, ctype) -> (A, B)) + & ((WorldShim, id: Entity, ctype, ctype, ctype) -> (A, B, C)) + & (WorldShim, id: Entity, ctype, ctype, ctype, ctype) -> (A, B, C, D), + + --- Searches the world for entities that match a given query + query: + ((WorldShim, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) + & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ...ctype) -> QueryShim) + + }, + {} :: { + __iter: (world: WorldShim) -> () -> (number, {[unknown]: unknown?}) + } + +)) + +return table.freeze({ + World = (World :: any) :: {new: () -> WorldShim}, + + OnAdd = (ON_ADD :: any) :: Component, + OnRemove = (ON_REMOVE :: any) :: Component, + OnSet = (ON_SET :: any) :: Component, + Wildcard = (WILDCARD :: any) :: Component, + w = (WILDCARD :: any) :: Component, Rest = REST, IS_PAIR = ECS_IS_PAIR, @@ -863,6 +928,6 @@ return table.freeze({ ECS_PAIR_RELATION = ECS_PAIR_RELATION, ECS_PAIR_OBJECT = ECS_PAIR_OBJECT, - pair = ECS_PAIR, - getAlive = getAlive, + pair = (ECS_PAIR :: any) :: (pred: Entity, obj: Entity) -> Relationship, + getAlive = getAlive }) From 818dfdd216b45e4a5070ef0679416cdbd3f6fb32 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Thu, 6 Jun 2024 00:38:27 +0200 Subject: [PATCH 18/25] Low foot print ids --- lib/init.lua | 56 +++++++++++++++++++++++++++---------------------- tests/world.lua | 2 +- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index ebcfd0e..b83d988 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -100,49 +100,52 @@ local function ECS_COMBINE(source: number, target: number): i53 end local function ECS_IS_PAIR(e: number) - return (e % 2 ^ 4) // FLAGS_PAIR ~= 0 + if e > ECS_ENTITY_MASK then + return (e % 2 ^ 4) // FLAGS_PAIR ~= 0 + end + return false end -- HIGH 24 bits LOW 24 bits local function ECS_GENERATION(e: i53) - e = e // 0x10 - return e % ECS_GENERATION_MASK + if e > ECS_ENTITY_MASK then + e = e // 0x10 + return e % ECS_GENERATION_MASK + end + return 0 end local function ECS_GENERATION_INC(e: i53) - local flags = e // 0x10 - local id = flags // ECS_ENTITY_MASK - local generation = flags % ECS_GENERATION_MASK + if e > ECS_ENTITY_MASK then + local flags = e // 0x10 + local id = flags // ECS_ENTITY_MASK + local generation = flags % ECS_GENERATION_MASK - return ECS_COMBINE(id, generation + 1) + flags + return ECS_COMBINE(id, generation + 1) + flags + end + return ECS_COMBINE(e, 1) end -- FIRST gets the high ID local function ECS_ENTITY_T_HI(e: i53): i24 - e = e // 0x10 - return e % ECS_ENTITY_MASK + if e > ECS_ENTITY_MASK then + e = e // 0x10 + return e % ECS_ENTITY_MASK + end + return e end -- SECOND local function ECS_ENTITY_T_LO(e: i53) - e = e // 0x10 - return e // ECS_ENTITY_MASK + if e > ECS_ENTITY_MASK then + e = e // 0x10 + return e // ECS_ENTITY_MASK + end + return e end local function ECS_PAIR(pred: i53, obj: i53): i53 - local first - local second: number = WILDCARD - - if pred == WILDCARD then - first = obj - elseif obj == WILDCARD then - first = pred - else - first = obj - second = ECS_ENTITY_T_LO(pred) - end - - return ECS_COMBINE(ECS_ENTITY_T_LO(first), second) + addFlags(--[[isPair]] true) + return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) end local function getAlive(entityIndex: EntityIndex, id: i24) @@ -161,7 +164,8 @@ local function ECS_PAIR_OBJECT(entityIndex, e) end local function nextEntityId(entityIndex, index: i24): i53 - local id = ECS_COMBINE(index, 0) + --local id = ECS_COMBINE(index, 0) + local id = index entityIndex.sparse[id] = { dense = index, } :: Record @@ -372,6 +376,7 @@ function World.target(world: World, entity: i53, relation: i24): i24? if not archetype then return nil end + local componentRecord = world.componentIndex[ECS_PAIR(relation, WILDCARD)] if not componentRecord then return nil @@ -822,6 +827,7 @@ function World.__iter(world: World): () -> (number?, unknown?) if not lastEntity then return end + last = lastEntity local record = sparse[entityId] diff --git a/tests/world.lua b/tests/world.lua index 79a2686..7ca883c 100644 --- a/tests/world.lua +++ b/tests/world.lua @@ -208,6 +208,7 @@ TEST("world", function() local pair = ECS_PAIR(e2, e3) CHECK(IS_PAIR(pair) == true) + CHECK(ECS_PAIR_RELATION(world.entityIndex, pair) == e2) CHECK(ECS_PAIR_OBJECT(world.entityIndex, pair) == e3) end @@ -350,4 +351,3 @@ TEST("world", function() end) FINISH() - From a62cb370cff5fbed0a38d9fa34470518068b13b1 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sun, 9 Jun 2024 23:58:01 +0200 Subject: [PATCH 19/25] Everything are entities! --- lib/init.lua | 81 ++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index b83d988..402473c 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -853,77 +853,72 @@ end -- __nominal_type_dont_use could not be any or T as it causes a type error -- or produces a union -export type Component = T & {__nominal_type_dont_use: never} -export type Entity = Component -export type Relationship = Component -type ctype = Component - -export type QueryShim = typeof(setmetatable( - {} :: { - --- Excludes the given selection from the query - without: (QueryShim, U...) -> QueryShim - }, - {} :: { - __iter: (QueryShim) -> () -> (Entity, T...) - } -)) +export type Entity = number & {__nominal_type_dont_use: T} +export type Pair = number +export type QueryShim = typeof(setmetatable({ + without = function(...): QueryShim + return nil :: any + end +}, { + __iter = function(): () -> (number, T...) + return nil :: any + end +})) export type WorldShim = typeof(setmetatable( {} :: { --- Creates a new entity - entity: (WorldShim) -> Entity, + entity: (WorldShim) -> Entity, --- Creates a new entity located in the first 256 ids. --- These should be used for static components for fast access. - component: (WorldShim) -> Component, + component: (WorldShim) -> Entity, --- Gets the target of an relationship. For example, when a user calls --- `world:target(id, ChildOf(parent))`, you will obtain the parent entity. - target: (WorldShim, id: Entity, relation: Relationship) -> Entity?, + target: (WorldShim, id: Entity, relation: Entity) -> Entity?, --- Deletes an entity and all it's related components and relationships. delete: (WorldShim, id: Entity) -> (), --- Adds a component to the entity with no value - add: (WorldShim, id: Entity, component: Component) -> (), + add: (WorldShim, id: Entity, component: Entity) -> (), --- Assigns a value to a component on the given entity - set: (WorldShim, id: Entity, component: Component, data: T) -> (), + set: (WorldShim, id: Entity, component: Entity, data: T) -> (), --- Removes a component from the given entity - remove: (WorldShim, id: Entity, component: Component) -> (), + remove: (WorldShim, id: Entity, component: Entity) -> (), --- Retrieves the value of up to 4 components. These values may be nil. get: - ((WorldShim, id: any, ctype) -> A) - & ((WorldShim, id: Entity, ctype, ctype) -> (A, B)) - & ((WorldShim, id: Entity, ctype, ctype, ctype) -> (A, B, C)) - & (WorldShim, id: Entity, ctype, ctype, ctype, ctype) -> (A, B, C, D), + ((WorldShim, id: any, Entity) -> A) + & ((WorldShim, id: Entity, Entity, Entity) -> (A, B)) + & ((WorldShim, id: Entity, Entity, Entity, Entity) -> (A, B, C)) + & (WorldShim, id: Entity, Entity, Entity, Entity, Entity) -> (A, B, C, D), --- Searches the world for entities that match a given query - query: - ((WorldShim, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype) -> QueryShim) - & ((WorldShim, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ctype, ...ctype) -> QueryShim) + query: ((WorldShim, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) + & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, ...Entity) -> QueryShim) }, {} :: { __iter: (world: WorldShim) -> () -> (number, {[unknown]: unknown?}) } - )) return table.freeze({ World = (World :: any) :: {new: () -> WorldShim}, - OnAdd = (ON_ADD :: any) :: Component, - OnRemove = (ON_REMOVE :: any) :: Component, - OnSet = (ON_SET :: any) :: Component, - Wildcard = (WILDCARD :: any) :: Component, - w = (WILDCARD :: any) :: Component, + OnAdd = (ON_ADD :: any) :: Entity, + OnRemove = (ON_REMOVE :: any) :: Entity, + OnSet = (ON_SET :: any) :: Entity, + Wildcard = (WILDCARD :: any) :: Entity, + w = (WILDCARD :: any) :: Entity, Rest = REST, IS_PAIR = ECS_IS_PAIR, @@ -934,6 +929,6 @@ return table.freeze({ ECS_PAIR_RELATION = ECS_PAIR_RELATION, ECS_PAIR_OBJECT = ECS_PAIR_OBJECT, - pair = (ECS_PAIR :: any) :: (pred: Entity, obj: Entity) -> Relationship, + pair = (ECS_PAIR :: any) :: (pred: Entity, obj: Entity) -> number, getAlive = getAlive }) From 1553362014da14d27e1959f129425241b2428ecb Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 11 Jun 2024 01:39:43 +0200 Subject: [PATCH 20/25] Please the type solver overlord --- lib/init.lua | 146 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 108 insertions(+), 38 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index 402473c..ae3c1e1 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -23,7 +23,7 @@ type Archetype = { type: string | number, entities: { number }, columns: { Column }, - records: {}, + records: { [number]: number }, } type Record = { @@ -47,7 +47,7 @@ TODO: ]] type ArchetypeMap = { - cache: { [number]: ArchetypeRecord }, + cache: { ArchetypeRecord }, first: ArchetypeMap, second: ArchetypeMap, parent: ArchetypeMap, @@ -136,7 +136,7 @@ local function ECS_ENTITY_T_HI(e: i53): i24 end -- SECOND -local function ECS_ENTITY_T_LO(e: i53) +local function ECS_ENTITY_T_LO(e: i53): i24 if e > ECS_ENTITY_MASK then e = e // 0x10 return e // ECS_ENTITY_MASK @@ -145,7 +145,7 @@ local function ECS_ENTITY_T_LO(e: i53) end local function ECS_PAIR(pred: i53, obj: i53): i53 - return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) + return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) :: i53 end local function getAlive(entityIndex: EntityIndex, id: i24) @@ -163,7 +163,7 @@ local function ECS_PAIR_OBJECT(entityIndex, e) return getAlive(entityIndex, ECS_ENTITY_T_LO(e)) end -local function nextEntityId(entityIndex, index: i24): i53 +local function nextEntityId(entityIndex: EntityIndex, index: i24): i53 --local id = ECS_COMBINE(index, 0) local id = index entityIndex.sparse[id] = { @@ -219,7 +219,7 @@ local function transitionArchetype( sourceEntities[sourceRow] = e2 end - sourceEntities[movedAway] = nil + sourceEntities[movedAway] = nil :: any destinationEntities[destinationRow] = e1 local record1 = sparse[e1] @@ -265,7 +265,7 @@ local function ensureComponentRecord( local archetypesMap = componentIndex[componentId] if not archetypesMap then - archetypesMap = { size = 0, cache = {}, first = {}, second = {} } :: ArchetypeMap + archetypesMap = { size = 0, cache = {} } componentIndex[componentId] = archetypesMap end @@ -289,7 +289,7 @@ local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archet world.nextArchetypeId = id local length = #types - local columns = table.create(length) + local columns = (table.create(length) :: any) :: { Column } local componentIndex = world.componentIndex local records = {} @@ -311,7 +311,7 @@ local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archet columns[i] = {} end - local archetype = { + local archetype: Archetype = { columns = columns, edges = {}, entities = {}, @@ -320,6 +320,7 @@ local function archetypeOf(world: any, types: { i24 }, prev: Archetype?): Archet type = ty, types = types, } + world.archetypeIndex[ty] = archetype world.archetypes[id] = archetype @@ -330,12 +331,12 @@ local World = {} World.__index = World function World.new() local self = setmetatable({ - archetypeIndex = {}, + archetypeIndex = {} :: { [string]: Archetype }, archetypes = {} :: Archetypes, componentIndex = {} :: ComponentIndex, entityIndex = { - dense = {}, - sparse = {}, + dense = {} :: { [i24]: i53 }, + sparse = {} :: { [i53]: Record }, } :: EntityIndex, hooks = { [ON_ADD] = {}, @@ -349,6 +350,8 @@ function World.new() return self end +export type World = typeof(World.new()) + function World.component(world: World) local componentId = world.nextComponentId + 1 if componentId > HI_COMPONENT_ID then @@ -391,7 +394,7 @@ function World.target(world: World, entity: i53, relation: i24): i24? end -- should reuse this logic in World.set instead of swap removing in transition archetype -local function destructColumns(columns, count, row) +local function destructColumns(columns: { Column }, count: number, row: number) if row == count then for _, column in columns do column[count] = nil @@ -415,7 +418,7 @@ local function archetypeDelete(world: World, id: i53) end end - componentIndex[id] = nil + componentIndex[id] = nil :: any end end @@ -444,20 +447,18 @@ function World.delete(world: World, entityId: i53) sparse[entityToMove] = record end - entities[row], entities[last] = entities[last], nil + entities[row], entities[last] = entities[last], nil :: any local columns = archetype.columns destructColumns(columns, last, row) end - sparse[entityId] = nil - dense[#dense] = nil + sparse[entityId] = nil :: any + dense[#dense] = nil :: any end -export type World = typeof(World.new()) - -local function ensureArchetype(world: World, types, prev) +local function ensureArchetype(world: World, types, prev): Archetype if #types < 1 then return world.ROOT_ARCHETYPE end @@ -853,17 +854,17 @@ end -- __nominal_type_dont_use could not be any or T as it causes a type error -- or produces a union -export type Entity = number & {__nominal_type_dont_use: T} +export type Entity = number & { __nominal_type_dont_use: T } export type Pair = number export type QueryShim = typeof(setmetatable({ without = function(...): QueryShim return nil :: any - end + end, }, { __iter = function(): () -> (number, T...) return nil :: any - end + end, })) export type WorldShim = typeof(setmetatable( {} :: { @@ -878,7 +879,7 @@ export type WorldShim = typeof(setmetatable( target: (WorldShim, id: Entity, relation: Entity) -> Entity?, --- Deletes an entity and all it's related components and relationships. delete: (WorldShim, id: Entity) -> (), - + --- Adds a component to the entity with no value add: (WorldShim, id: Entity, component: Entity) -> (), --- Assigns a value to a component on the given entity @@ -886,33 +887,102 @@ export type WorldShim = typeof(setmetatable( --- Removes a component from the given entity remove: (WorldShim, id: Entity, component: Entity) -> (), --- Retrieves the value of up to 4 components. These values may be nil. - get: - ((WorldShim, id: any, Entity) -> A) + get: ((WorldShim, id: any, Entity) -> A) & ((WorldShim, id: Entity, Entity, Entity) -> (A, B)) & ((WorldShim, id: Entity, Entity, Entity, Entity) -> (A, B, C)) & (WorldShim, id: Entity, Entity, Entity, Entity, Entity) -> (A, B, C, D), - + --- Searches the world for entities that match a given query query: ((WorldShim, Entity) -> QueryShim) & ((WorldShim, Entity, Entity) -> QueryShim) & ((WorldShim, Entity, Entity, Entity) -> QueryShim) & ((WorldShim, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity) -> QueryShim) - & ((WorldShim, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, Entity, ...Entity) -> QueryShim) - + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity + ) -> QueryShim) + & (( + WorldShim, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + Entity, + ...Entity + ) -> QueryShim), }, {} :: { - __iter: (world: WorldShim) -> () -> (number, {[unknown]: unknown?}) + __iter: (world: WorldShim) -> () -> (number, { [unknown]: unknown? }), } )) return table.freeze({ - World = (World :: any) :: {new: () -> WorldShim}, + World = (World :: any) :: { new: () -> WorldShim }, OnAdd = (ON_ADD :: any) :: Entity, OnRemove = (ON_REMOVE :: any) :: Entity, @@ -930,5 +1000,5 @@ return table.freeze({ ECS_PAIR_OBJECT = ECS_PAIR_OBJECT, pair = (ECS_PAIR :: any) :: (pred: Entity, obj: Entity) -> number, - getAlive = getAlive + getAlive = getAlive, }) From c3d745a88b9f7750498583a5b5c0c66efaefa200 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 11 Jun 2024 21:06:27 +0200 Subject: [PATCH 21/25] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..605eef8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 jecs authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 1344398ffe57d95c339254cfa6639c70122a57df Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 15 Jun 2024 20:03:12 +0200 Subject: [PATCH 22/25] Fix types --- lib/init.lua | 92 +++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index ae3c1e1..bc86403 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -252,7 +252,7 @@ local function moveEntity(entityIndex: EntityIndex, entityId: i53, record: Recor record.row = destinationRow end -local function hash(arr): string | number +local function hash(arr): string return table.concat(arr, "_") end @@ -262,10 +262,10 @@ local function ensureComponentRecord( componentId: number, i: number ): ArchetypeMap - local archetypesMap = componentIndex[componentId] + local archetypesMap = componentIndex[componentId] if not archetypesMap then - archetypesMap = { size = 0, cache = {} } + archetypesMap = ({ size = 0, cache = {} } :: any) :: ArchetypeMap componentIndex[componentId] = archetypesMap end @@ -490,7 +490,7 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53 -- them each time would be expensive. Instead this insertion sort can find the insertion -- point in the types array. - local destinationType = table.clone(node.types) + local destinationType = table.clone(node.types) :: { i53 } 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 @@ -575,7 +575,7 @@ local function archetypeTraverseRemove(world: World, componentId: i53, from: Arc local remove = edge.remove if not remove then - local to = table.clone(from.types) + local to = table.clone(from.types) :: { i53 } local at = table.find(to, componentId) if not at then return from @@ -615,7 +615,7 @@ local function get(record: Record, componentId: i24) return archetype.columns[archetypeRecord][record.row] end -function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?) +function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): any local id = entityId local record = world.entityIndex.sparse[id] if not record then @@ -658,14 +658,15 @@ function World.query(world: World, ...): Query error("Missing components") end - local compatibleArchetypes = {} + type CompatibleArchetype = { archetype: Archetype, indices: { number } } + local compatibleArchetypes = {} :: { CompatibleArchetype } local length = 0 local components = { ... } local archetypes = world.archetypes local queryLength = #components - local firstArchetypeMap + local firstArchetypeMap: ArchetypeMap local componentIndex = world.componentIndex for _, componentId in components do @@ -707,7 +708,8 @@ function World.query(world: World, ...): Query } end - local lastArchetype, compatibleArchetype = next(compatibleArchetypes) + local lastArchetype, compatibleArchetype: CompatibleArchetype = next(compatibleArchetypes :: any) + if not lastArchetype then return EmptyQuery end @@ -715,51 +717,24 @@ function World.query(world: World, ...): Query local preparedQuery = {} preparedQuery.__index = preparedQuery - function preparedQuery:without(...) - local withoutComponents = { ... } - for i = #compatibleArchetypes, 1, -1 do - local archetype = compatibleArchetypes[i].archetype - 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() + return function() local archetype = compatibleArchetype.archetype - local row: number = next(archetype.entities, lastRow) :: number + local row: number = next(archetype.entities, lastRow) while row == nil do lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) if lastArchetype == nil then return end archetype = compatibleArchetype.archetype - row = next(archetype.entities, row) :: number + row = next(archetype.entities, row) end lastRow = row - local entityId = archetype.entities[row :: number] + local entityId = archetype.entities[row] local columns = archetype.columns local tr = compatibleArchetype.indices @@ -811,22 +786,49 @@ function World.query(world: World, ...): Query queryOutput[i] = columns[tr[i]][row] end - return entityId, unpack(queryOutput, 1, queryLength) + return entityId, unpack(queryOutput :: any, 1, queryLength) end end + function preparedQuery:without(...) + local withoutComponents = { ... } + for i = #compatibleArchetypes, 1, -1 do + local archetype = compatibleArchetypes[i].archetype + 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 :: any) + if not lastArchetype then + return EmptyQuery + end + + return self + end + return setmetatable({}, preparedQuery) :: any end -function World.__iter(world: World): () -> (number?, unknown?) +function World.__iter(world: World): () -> any local dense = world.entityIndex.dense local sparse = world.entityIndex.sparse local last return function() - local lastEntity, entityId = next(dense, last) - if not lastEntity then - return + local lastEntity: number?, entityId: number = next(dense, last) + if not lastEntity then + return end last = lastEntity From ea12df96a495b875a48fd615be7c6965805dafac Mon Sep 17 00:00:00 2001 From: EncodedVenom <32179912+EncodedVenom@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:03:04 -0400 Subject: [PATCH 23/25] Add Typescript Types (#51) * Add package.json, tsconfig.json, and change gitignore * Typescript Types * Make World a single class, refactor of all query types * Fix InferComponents types due to typo / misunderstanding * Bump dependencies * Fix get type to include undefined and add documentation * Remove non-user facing types * Rename PossiblyUndefinedPack -> Nullable * Some changes to how EntityIndex is handled * Consistent formatting * Remove EntityIndex related type definitions * Match API * Doc comments --- .gitignore | 4 + lib/index.d.ts | 153 ++++ package-lock.json | 2199 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 43 + tsconfig.json | 26 + 5 files changed, 2425 insertions(+) create mode 100644 lib/index.d.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index a43fa5f..f3d15ef 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ Packages wally.lock WallyPatches +# Typescript +/node_modules +/include + # Misc roblox.toml sourcemap.json diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..66ae62d --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,153 @@ +type Query = { + without: (...components: Entity[]) => Query; +} & IterableFunction>; + +// Utility Types +export type Entity = number & { __nominal_type_dont_use: T }; +export type EntityType = T extends Entity ? A : never; +export type InferComponents = { + [K in keyof A]: EntityType; +}; +type Nullable = { + [K in keyof T]: T[K] | undefined; +}; + +export class World { + /** + * Creates a new World + */ + constructor(); + + /** + * Creates a new entity + * @returns Entity + */ + entity(): Entity; + + /** + * Creates a new entity located in the first 256 ids. + * These should be used for static components for fast access. + * @returns Entity + */ + component(): Entity; + + /** + * Gets the target of a relationship. For example, when a user calls + * `world.target(id, ChildOf(parent))`, you will obtain the parent entity. + * @param id Entity + * @param relation The Relationship + * @returns The Parent Entity if it exists + */ + target(id: Entity, relation: Entity): Entity | undefined; + + /** + * Deletes an entity and all its related components and relationships. + * @param id Entity to be destroyed + */ + delete(id: Entity): void; + + /** + * Adds a component to the entity with no value + * @param id Target Entity + * @param component Component + */ + add(id: Entity, component: Entity): void; + + /** + * Assigns a value to a component on the given entity + * @param id Target Entity + * @param component Target Component + * @param data Component Data + */ + set(id: Entity, component: Entity, data: T): void; + + /** + * Removes a component from the given entity + * @param id Target Entity + * @param component Target Component + */ + remove(id: Entity, component: Entity): void; + + // Manually typed out get since there is a hard limit. + + /** + * Retrieves the value of one component. This value may be undefined. + * @param id Target Entity + * @param component Target Component + * @returns Data associated with the component if it exists + */ + get(id: number, component: Entity): A | undefined; + + /** + * Retrieves the value of two components. This value may be undefined. + * @param id Target Entity + * @param component Target Component 1 + * @param component2 Target Component 2 + * @returns Data associated with the components if it exists + */ + get( + id: number, + component: Entity, + component2: Entity + ): LuaTuple>; + + /** + * Retrieves the value of three components. This value may be undefined. + * @param id Target Entity + * @param component Target Component 1 + * @param component2 Target Component 2 + * @param component3 Target Component 3 + * @returns Data associated with the components if it exists + */ + get( + id: number, + component: Entity, + component2: Entity, + component3: Entity + ): LuaTuple>; + + /** + * Retrieves the value of four components. This value may be undefined. + * @param id Target Entity + * @param component Target Component 1 + * @param component2 Target Component 2 + * @param component3 Target Component 3 + * @param component4 Target Component 4 + * @returns Data associated with the components if it exists + */ + get( + id: number, + component: Entity, + component2: Entity, + component3: Entity, + component4: Entity + ): LuaTuple>; + + /** + * Searches the world for entities that match a given query + * @param components Queried Components + * @returns Iterable function + */ + query(...components: T): Query>; +} + +/** + * Creates a composite key. + * @param pred The first entity + * @param obj The second entity + * @returns The composite key + */ +export const pair: (pred: Entity, obj: Entity) => Entity; + +/** + * Checks if the entity is a composite key + * @param e The entity to check + * @returns If the entity is a pair + */ +export const IS_PAIR: (e: Entity) => boolean; + +export const OnAdd: Entity; +export const OnRemove: Entity; +export const OnSet: Entity; +export const Wildcard: Entity; +export const Rest: Entity; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..964ddec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2199 @@ +{ + "name": "@rbxts/jecs", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@rbxts/jecs", + "version": "0.1.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": "^2.3.0", + "typescript": "^5.4.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "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.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "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/@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.1", + "resolved": "https://registry.npmjs.org/@rbxts/compiler-types/-/compiler-types-2.3.0-types.1.tgz", + "integrity": "sha512-NZWNo+fC4icfJte+NiDSDdWJo1KwzD0MDQ2iBi70YhNYlkA7+Xc+B1udbVqxLJuBur2JxG5bbKrpMAfHqfCtbw==", + "dev": true + }, + "node_modules/@rbxts/types": { + "version": "1.0.781", + "resolved": "https://registry.npmjs.org/@rbxts/types/-/types-1.0.781.tgz", + "integrity": "sha512-q8NwgHqyKiyhl3q22tSxCD8S796T88hh/itUJim+XYC010f7GZv2jNKzJcIjp+2d+iKVxgRFvx9go8nHBDjwDA==", + "dev": true + }, + "node_modules/@roblox-ts/luau-ast": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@roblox-ts/luau-ast/-/luau-ast-1.0.11.tgz", + "integrity": "sha512-+maoLYpqY0HK8ugLFHS3qz0phMyDaN3i21jjW75T2ZaqJg84heKDUo98iXClvnx3mUDhW10IxqH+cYJ2iftYhQ==", + "dev": true + }, + "node_modules/@roblox-ts/path-translator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@roblox-ts/path-translator/-/path-translator-1.0.0.tgz", + "integrity": "sha512-Lp6qVUqjmXIrICy2KPKRiX8IkJ+lNqn6RqoUplLiTr+4JehIN+mJv0tTnE72XRyIfcx0VWl5nKrRwUuqcOj1yg==", + "dev": true, + "dependencies": { + "ajv": "^8.12.0", + "fs-extra": "^11.2.0" + } + }, + "node_modules/@roblox-ts/path-translator/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "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.0.6", + "resolved": "https://registry.npmjs.org/@roblox-ts/rojo-resolver/-/rojo-resolver-1.0.6.tgz", + "integrity": "sha512-+heTECMo6BdH3a3h4DCj+8kJvwKuxWqBevcW/m2BzQaVtmo1GtLa4V4bJCMvDuAMeEqYKQZUB7546nN2dcqqAA==", + "dev": true, + "dependencies": { + "ajv": "^8.12.0", + "fs-extra": "^11.1.1" + } + }, + "node_modules/@roblox-ts/rojo-resolver/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "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/@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/node": { + "version": "16.18.98", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.98.tgz", + "integrity": "sha512-fpiC20NvLpTLAzo3oVBKIqBGR6Fx/8oAK/SSf7G+fydnXMY1x4x9RZ6sBXhqKlCU21g2QapUsbLlhv3+a7wS+Q==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "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.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "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/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/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/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/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/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/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "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/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/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "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.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "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.0", + "@humanwhocodes/config-array": "^0.11.14", + "@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.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "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/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.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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.4" + }, + "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/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "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.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "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/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "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.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "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/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/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/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "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/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "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/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/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/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/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/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.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "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.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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": "2.3.0", + "resolved": "https://registry.npmjs.org/roblox-ts/-/roblox-ts-2.3.0.tgz", + "integrity": "sha512-swz+3sxHcB1ww5iUkwxzPFqrbWYmjD9uDriLhta5MAShvRFW4Vdku/aBSU4KiLqtVWYvYo32G+5bXg1Pw2yvIA==", + "dev": true, + "dependencies": { + "@roblox-ts/luau-ast": "^1.0.11", + "@roblox-ts/path-translator": "^1.0.0", + "@roblox-ts/rojo-resolver": "^1.0.6", + "chokidar": "^3.6.0", + "fs-extra": "^11.2.0", + "kleur": "^4.1.5", + "resolve": "^1.22.6", + "typescript": "=5.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "rbxtsc": "out/CLI/cli.js" + } + }, + "node_modules/roblox-ts/node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "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/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/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/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/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/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/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.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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/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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ce2b802 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "@rbxts/jecs", + "version": "0.1.0", + "description": "Stupidly fast Entity Component System", + "main": "lib/init.lua", + "repository": { + "type": "git", + "url": "https://github.com/ukendio/jecs.git" + }, + "scripts": { + "build": "rbxtsc", + "watch": "rbxtsc -w", + "prepublishOnly": "npm run build" + }, + "keywords": [], + "author": "Ukendio", + "contributors": [ + "Ukendio", + "EncodedVenom" + ], + "homepage": "https://github.com/ukendio/jecs", + "license": "MIT", + "types": "lib/index.d.ts", + "files": [ + "lib/" + ], + "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": "^2.3.0", + "typescript": "^5.4.2" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e0c819c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // required + "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"], + + // configurable + "rootDir": "lib", + "outDir": "out", + "baseUrl": "lib", + "incremental": true, + "tsBuildInfoFile": "out/tsconfig.tsbuildinfo", + + "moduleDetection": "force" + } + } \ No newline at end of file From 5bd43bddd4786df4aabf12fb2f3515cd18a0bbe2 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 22 Jun 2024 00:15:03 +0200 Subject: [PATCH 24/25] Add queryNext --- lib/init.lua | 157 ++++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index bc86403..cfde17f 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -652,14 +652,15 @@ setmetatable(EmptyQuery, EmptyQuery) export type Query = typeof(EmptyQuery) +type CompatibleArchetype = { archetype: Archetype, indices: { number } } + function World.query(world: World, ...): Query -- breaking? if (...) == nil then error("Missing components") end - type CompatibleArchetype = { archetype: Archetype, indices: { number } } - local compatibleArchetypes = {} :: { CompatibleArchetype } + local compatibleArchetypes: { CompatibleArchetype } = {} local length = 0 local components = { ... } @@ -708,86 +709,98 @@ function World.query(world: World, ...): Query } end - local lastArchetype, compatibleArchetype: CompatibleArchetype = next(compatibleArchetypes :: any) + local lastArchetype = 1 + local compatibleArchetype: CompatibleArchetype = compatibleArchetypes[lastArchetype] - if not lastArchetype then + if not compatibleArchetype then return EmptyQuery end local preparedQuery = {} preparedQuery.__index = preparedQuery - - local lastRow + local queryOutput = {} - function preparedQuery:__iter() - return function() - local archetype = compatibleArchetype.archetype - local row: number = next(archetype.entities, lastRow) - while row == nil do - lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) - if lastArchetype == nil then - return - end - archetype = compatibleArchetype.archetype - row = next(archetype.entities, row) + local i = 1 + + local function queryNext() + local archetype = compatibleArchetype.archetype + local entityId = archetype.entities[i] + + while entityId == nil do + lastArchetype += 1 + if lastArchetype > #compatibleArchetypes then + return end - lastRow = row - - local entityId = archetype.entities[row] - local columns = archetype.columns - local tr = compatibleArchetype.indices - - 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 :: any, 1, queryLength) + compatibleArchetype = compatibleArchetypes[lastArchetype] + archetype = compatibleArchetype.archetype + i = 1 + entityId = archetype.entities[i] end + + local row = i + i+=1 + + local columns = archetype.columns + local tr = compatibleArchetype.indices + + 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 :: any, 1, queryLength) + end + + function preparedQuery:__iter() + return queryNext + end + + function preparedQuery:next() + return queryNext() end function preparedQuery:without(...) From eae51988a9e3ca45e39ebcfdbea0f9f8706bd3cd Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 22 Jun 2024 01:08:51 +0200 Subject: [PATCH 25/25] Update benchmarks image --- benches/visual/query.bench.lua | 13 ++++++++++--- image-3.png | Bin 123556 -> 77539 bytes 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/benches/visual/query.bench.lua b/benches/visual/query.bench.lua index e8f948a..57eddaa 100644 --- a/benches/visual/query.bench.lua +++ b/benches/visual/query.bench.lua @@ -3,11 +3,11 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local rgb = require(ReplicatedStorage.rgb) -local Matter = require(ReplicatedStorage.DevPackages.Matter) -local ecr = require(ReplicatedStorage.DevPackages.ecr) +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 jecs = require(ReplicatedStorage.Shim) local mirror = require(ReplicatedStorage.mirror) local mcs = mirror.World.new() local ecs = jecs.World.new() @@ -177,6 +177,13 @@ return { end end, + Matter = function() + local matched = 0 + for entityId, firstComponent in newWorld:query(A1, A4, A6, A8) do + matched += 1 + end + end, + ECR = function() local matched = 0 for entityId, firstComponent in registry2:view(B1, B4, B6, B8) do diff --git a/image-3.png b/image-3.png index 9db2297237b2aee1f769651c577029a3e7885bde..103914224c9f3f8c55fe2254891c254dae3c56ae 100644 GIT binary patch literal 77539 zcmbSz1yodBzc;p^sE>$98+3<&GzdzE!~jEybcup=3xW#L-O@2b&(NSC-7zyXDBax* ze0#+AxzGE(>)!jV@2urwz%w&v_SyTtfAybF%8F8i1Y`twczA>|(vm88c$W|cyudI-Hnto#6Mm&tnsB4bVIdu(_!Gh7U1PH* zw#nD~^t%$83p}ugvFE{s;0}hj7+uH22xbk%t_1!^9xKC$M}fGD1c#SONNg;ldLAS) zO)6NH-;(<5&v+@m8JU@%>+0&X=>K&p-p%{}{X;MR$Kgjw73@o%!r#O7uYp%#ps3db zKa6_UH%Xo8Oi3B`c=AjrFMvNO-@a;i4iE3;5G!@fZd`R{yZ*0dF7MKd?|}cqz+83~ zWy0wcU~rvErC?%zPdF5zSBwVdzv=uOmPz?&X8VxRR)w!&QM#F2VkX3}YRH{yy*H^Z z!%)f`iq$wU@L#p2+as)2PhE6~s1>e~ZVpmt^xV5?d;djpivO0VR0rnPX{N1){@9wE zN3A6foUM+EjlN7|KYM#QY{%_%Y}3d7r-c^Ay=TVKJZ$Ho-C6wngW)mf@YH znKx?e6@tfChTd{5@#_jm&-+CdZ(|X~BchO1^RS0YJfj11Lbo1mc*fGN!8%>KNJVAj zuk+|X+HOW|VvgsQFpibz0()24VDj!3Kh#>wk?5h^vakY8BY|H-YMadQ(VA&Z)Y$Fu z4aFS8rx14!(@&rD8Mnp{m|RB)qSIEFXhY+p+v@g=g%64+0=BkxlE8IOLNg7Pf+z*2 zKal8!=vFz%sHsJ0C36{nwi>HM2`sfGG-wXv?&GDTTu)I+|#h>_e+YL=tYxoQjd z>&gyWPiiHIb5u~hN)BbqqYm}3#3$}eLQGo;W8~BA%M!M~d=bB9kF6@26%z#>nf`M8 zJS6lMn$RuuRyKXZ{etl%cU|-i^;~JAbo={D2jmp`Jt{j$?>-6M7Ez?JjsAjb>!}-f zdz}%a)zCvQjlxLu-FnqpYd71NH>ZL{p;7@s9CyJ9YzZNW8xOybX^YRS2E#&lU9}eB zJu1xFuH!7lb_iIP3nldma<^(gSwQl2$QRp^(YeuMG}{F3*5&j27J(lO*-&ot(d^F& z{O(T`mjk1@32k`Qi-($XKN+GDkGpfwup#1PRff<|l^OZ!oD|`&1x23`6B%Qhy`sq0 zX#Hln$aouW{kkV-uc^xlE#(k?MX#7(#$=f6dE#90(~lgM_Uvr7$Vn_1xVxRkPE4?b zZg{qyIGuiCH(ro3P48VR2_{?~)O=VF@zO-t6wzS7UafcqM<{ z@{PsnKG56n;iEA{u` zglE9=|MSobw|uzZ_J5u$h9B_hQ>cXhjTfZ2!N(m*Vt>CS)xSMl^5Nn(-}-afK3Tmc zb%r+#S88y_OV8i84fk;JoCl~XZK_b`pY@kyWMmj4Kg)@VisBOxgsrc?u~f?MJFcvq zx7}+S6YiVCJpmkr(J?Xi_}|Sdk8?mSyzun&{1h1YIGC1ISXdX#RAZ_{iRai#Qc}{} zw{D50nDu_+VP#cli)7ZgA42=Ds_EwcD6q3XQ;bM!f5ce3}$4T8{<^#Q|sR#ikZViNd($dl&5$Z^O|H6yK zL$e*RkAD9AiH2+hQVBzI)$>fOBSqY6x`npnyV&o2ZtE`~IeXbg)p-s`gGgdFv#_wR zX|Ma&uU}1i?dJ?>`ExKD`tG#J?z^#C4$FfNa&mGucQE>LP-vX}b2e~$EaA(xwFR{H z!J*7go(w{_04h&SMi>SOhBY1mb07-`JOdA@aM|QX% zscSugjJ(SVmF~|}$Z$W}y-h^)=&!&2>Pr#7U_My*^|8&k4jGRHO(c_g7?_-~yyp)R zgsAcwegzSxI-*cnIAVAX{D9TkqtXh%uy9D1e{a`0-n0>xz4Rqebb|3uVbSu>W?(9iyZmB5R{6SoCyl?p?SsOjP5{#&XN z{m1TKbU#dMs0?71R(YP(@AFN#^_NQZ>_zw$lc9NEj}9iJ)minft9$l-ol2iL^1g7T zgY#_i-%`BD zNC?E@QYoa^A_I2$y$dD0YhV&3-XKAEohJn4k$<$i!X0qJD|U1WMiT>TZf9dhSJCU=XTR_BPIFxdsVb;!%1bKS|^Ib`-yo{$sln&(_jrM*-)Y7tV?Jq(rb zyb8(rHkF&z^Tx@B&%jIl(%oc%Ga%NvG0GH#!+etL;AH$ zTp9dni>ZmjEXn5??Fc}JUkDYlg(@|FFZRvE#(+I-c$bJ8)80cgvYNd zX6|kkIVL)_$3mn9pqJ;}Q7@H`A_e-_6-6uag`cfeZ*?V{RO6VAy`L-DuEkA#!d83O zV;a`3=H0PghpRyrV-qL4WppnGFnfnf1@RU-@Idw92BhoWYUY=xR#q^WZWVf2zwC8l zERCG&ugWx3x+NMGN}}f^PUU(0_F&EP)RK~s!clyGCf}8(023PoSG>1-G;yk@=eokP z#e-=4*tZuj{)o^pGN>ph2NH8OwI&z$5nw6P!v*aZ)x2M>+CKhKv5*q}0b{Vx=0|*JFqN=Htsg{o(Z~es z2|ROVG4MGv&AjhW+s^PV6@H=r`*Ph^M{fJLP zZD9Z#wxk7%SkQqgMSgboje{Qr*5vYDBHJ`KPG-{}I94uB@LA7OAN)KUma1L?_ymtA zH?`aPcOLQ|Oo`?4BY)lcaRz51+>;c3DnHcuP#EOih;L?pJAbY2bbR{?tF?eP>iA$w zp$!6^l}8` zejWz3w1jz^Ibv>Ze(|H?w%)vOt7kz()sfl&_Uv>#A$p~5P5XZ#O+2MMAs(IUH*c2h zPBV2DECsMR7O?46n*}YYNxsZx^l_y5A%Iv*x_k?g#FOA6(F!v#!@Vd);qI;Dm@g zv8elhK6)X09Rv;yos4`BmfptRUK#Q;J%F}-eSLt4ld$RB0{ECwBR|2pdi2+1by8T^ zt9&`9fMQ9{rX6<@Fc{;H8vmlAdJR7t+lYiSuzbtF%DhKx{#7Trkz@Bl*j<&OVeQA~ zWK1)DE^^*>nQlJs4SN)bnvwN}`55Z@XzJd)c8$CHQrqXi!0R&d@{j9`%HJ)7spUaB z1yr5@YVwu?=>Ty9dg4(NAh!9<@&ID z6YO?#U6J@ibkb?hzrbpSwy^?le@QN23X&00=>WKKfjJw?V;P03$HQmLA^#a0v!O(r zMp*NR8;kde0h(&>K>leP)yaJ8@2FC4ytdnf`Uo;4sAsmIZpZHYx>%g0n8<}H z0yv+}7lj95?nDUGc%GhYqP1;eKkZR4spryw`c;Z7PS&rmH3k=<%Xn?#Y(*r^7At%g z{-PH;`$Cw8n_HrbmO4_tZdCP{3rq8`+;9ING8|4X8g_Fkxh&}0Pg`q(dZpD6*HqQ% zaMiE2ywymf%NG|-GvR5MK)ragI?7LU@}!LZ4Jub`#xq^vTwCR_HNoR|s0@I3!*=Ep zhxYoFL|f}lb_x>qLYd^_&#&MM8nxU42-_A#Fa&UxY=S6ZO{sF`4(Ic$=%f znM-t(ZTw6;Q?obD-F-T?=3$y^Bq)ODwmPcZsQCrwxg%9oI0_M)FvhZgrKA&3Kg@$b zOWv*<0ZU74p*QON_@tTYn;{NfTGkgqdiz7e@p;dk+Mf!R1Aqz+*8!6~ z%-8p#Wu8^J&OPm=dA`ktaK5(175W*}mn@?K&(icoN&R!;Xi{fgwm_5@&uQ9L>Za)J zrc@ZHGnC>mzm!8eY6=RgAX<9*_^ok|qm=|PB!_{O$5hjo@C>==I4_86i@*`sMrZt~ zJwhB-hFepg1$5MgK9Xt!SbkyeWv%61jEdi?O%jfZcTFcR=#~n!DLbEr^LEw}6C9@aC|zng+LbP0i7w*u{R- zxp^}UK_}Q+w~92Lp~d`v@(jmEl>K%aOOJ?1Kipur-QRTfR<1x0K5P z=p_7WtnQv>k(MuCrVKeqH4NkU@5PF;ahJS-d|hocOv~`_$6~T+rCD}f-OIyv{ga2L zT?wIAsNH(p;9zdIj5$uQ0w5ElBc!38i_WI`8p)=qv8$k8ftM{ zmQURvdNVKOwIc*~I%*a@c>oGUN&ZXrXn3G-@rxV6Bj1PHb_@3VH;+xFjO@fNv|jHZ zkEqNGL>*0K&)dP4eorhF(tu(%&-nNCO!CGsDh!jSwZuALbS=rJ$EN*P(6NACpuS36 zaR$^>{^lHxzZ}Qj)h$31`%LsH*SucE2-UKqpzXwJB4w3)Gb*o{KlsyE-XO>ctPxOa zx+{tDs^Os8{jP}{0d_DPcN|%=16e7n`Idc(3Q25&{L{G5GZ*!43}?QaC?MZrNnS~2 zRM6&q9QiU)&=J|iB9hpV2~Z$i0wuReAU?zE@}HOOSh%?QE;0jAoHx?M=gQ5fnfB;* zm{v(TAk4EAMkPx|x@Dqw*wV&fbUb96+V}TGu*OYG1$AsnLL0u$9n?Q^3CVeK z_Dp-L#0y%w7ZpQq}MdF)BIqF99sSFD|#i_n(EhkBqd^aQf>D59FY$RLP=+r~M zy%fd2x*fi%B-zr__=3yHA$|0CWHh$<4}{a7;=%@8H-8DAsf>sk>t1aL771zazZq{o zv$n?WbD59}P`o0;<{SM{VnUDo9dk?!}St*l5f-S@T-zJUJYyFyV5?KF*jX<@rc~FX&*PTVGtg^ zfMhts8Di$lx0>`wdLgOx1b#&~wq`rSgGCNohTAg56BUu_6lANcTE&u7PLt>QuR-Yd z2yU<)HUmi7s%Ji;eVZ8-S(jYEYRaU~9y{YBpJDZ);%KmNOX_pLQSYn77vuaPsJFe1 z{2x(v1f2^bc@qN4Z3h9PLv`?&zj?<`aZ)|vOlC|eJpg1TS2|}5?0&4emG44<2&U;OIS$B2 zfBxi%O&wxaXXO3+_tRAdw07*no!0D(jLytY?YT;Tv=;kJ!lXH^>KQ*3qEQU6&d$Rb z-wz*Jfuv)`6o5L|n!85%0}`|wO;>hNZnTaqPFB-ee0_GyG~K`|WYpF(2A1Fkxv^Gl zl*nqUTEJ0q>Z66fDW@~O#DbwdlBFV74=$w|EogHnMEGo@O-E7;H}0GZ6itOY9EO56);$>SPRyebARL z6?5^!p$tOcTqgVSICaTh*|EaMA^okx<&fN05q0>6803>K2WIuRygr7eRK4t(62|aF=WTuL@ z&oq3eTq^!Nrh12yc=*npA5!HwAb{Jc{vKlS`1gI^{~p;V!*^~qMcpPe!eR?n4R;n6 z7B0Qle0J|zxzxQSDg&71_+c=oftC32GZc7*f5#QRMHxPOez-02SLX(UL-$!3#={fq zeL<>C0ItUAW@ORo3(}>yG7oN9V=Hkr!rwb-;0N5M0=8ADx6SX{2LEv}%B0$#ONGCq zKy^>f!;}w0{z&L}%ioRfk*m~(1uhoqC9UjF5l1gQ9ENtkFUiAyMJfA>?CfU!K5Zm- zxaRgeH63Xz>89o8*Yg3Z8uaX|Q9g6LvG)6ytu;bILxnxWw9-UJf}$uKqGDrnPNxnU zWAmnBwf5Ld-wxmW{aUiV?Hs&ZrQXiR2m7K%y?eq0ETYN#S^OSM8zfsF>$j>RSWZZ~ zq1s7VSSunTB7CD4v-91hCss?{Pkuk&i?Sl&+fvf=0_m-6s<7DD2XkS;vYGm;j2^Kb zxBt?SI6iJ`MTb+j`fqe=OXYX;W+P$OfJw6E=Wb~|L+oMeKT4GNFgbxBvg9X%mixYP zyE=f+i1-mbU=P9IRvO&i(S0)2Ud3HqL{91js;cK}IK4G$?t{g)KVI#bpPlmU5FMvD ziD-rr9gMZ-dpb8<)YI_CuO@h9djYr9aNBoN1MZeGtZH(2Yb;FG7*K;7<76j9Q891a zFc?uP$?x*h)Y2xO>?bQN!+n^vft94BuYAIj7foNtO>;4A#qA%85J4V~M1`EwVB;Pt zdSgzqyN6P8xx0RZErc=&k&t^!IX(EJHcEEK#oipq`}LY&T2dR<55C_I$o&soan(@n z${HbVhBd}d{k&?Zi)4H==)PN5Y`Zj7{A$UNKj5_u|Ase(^dL>EIX*g8!vQb}Qhr;b z*qg5k7`Rna>wTtsk%(#0(}GVzcf;=!cT?V}o3_(xHlzGAJ0jGh2@MefyEiHM+Dj-s zJG7{33_iFyzf7twF;0PvaCZu9wUxTqZTxEFUjp7lH~_i&v)#$;s+sbk^YdnQEF2ul z&jas00Di|~_0rJLP!ms_A04To7wWt-$UB(m`pS)zMdG zTiU_o-ea+I;fboV&tu^;S?0qU{n=?F6mVCi@V!=_Z&piWL{v2nX~h71%8GZZqeB8E z^N)>C7DE6dK#?{RY&eVPNW0YR!O6)9m)rguErdlR4ImOB2!OJ<44bZ#;&7Ig4Gw43 z`jwNg>pecVI#eV}&OhRPm%%Zf(!OJCvv(fh|ZZf{7-No+uigb?mlDjqw=Mj)Isks2N zE6Mj>_zi*r?`k}M{=Dm=DwI{wypLw6!Y=DN#S`V>Qgg%I0{uF@%h&IPl(-#lG@I0Q zw;qZp=}Cu*+6b^X)>cT|rOai({|h5y@lB_ntb+X4TYVA?JfNRJCoB(8J*4rc`^a^p0IU`Gke}kKV^Y92%LA?G40%9Xz zNZckPgJxUXNMFrn0I2Xmx0Q6*eqm-9nVv4qeMi5o;-}1s)UL~%q`X!!2zqV*8)}nIbuaw1)ygF{{2LCZp&n9^@PoN9cC>XeN}(>iHKen=TwOhGGpM{rfB!pnbiOZP0g6?`Q8S#FLnFB0y3M6FOLr! zYK=GtQt+!lV44QGt&xx*!Q2)wNK8PT2jy9E{RC9?_573#cZf)ZUQZfk8(@PWhKjxW%ZxP3&tHAd9xzB>FR zASWZk>1}I3GUKG?LcQtlPP+ndSR~-4GGHNgRQ4^JU@M1*pVDhvj+PhU-^K|{7&QsY zm|@^$4wb)6!=JWyvwwvgd0nLdWFL#eD}tq#m@eNUu*A5(!!Cb;zQ`5jcCgDhT3M}~ zXQ!js)-x+Gs>vb)<@9J}b0%89?Yqwv1-YA8(q7lz5>uru?)*8yd3L#Nia8DlvjrG4 zCS1S>IEb>tWj5?m!Bjj;0#t`_d&@(HdeN&(6LodYi2_4OiZQ09hr^MV2cLcQL`Uo% z)jz0KxfG4UX0TSPX*|1EK0Z#nSojld;OD-CU<_StdqTe8#YzD6w8nBO3+)R8VH&Sx9LMG?3 z9*@O9AyU!uF8UcZ z1#+HCfxH(GbU!S8Y>AeC_~=n*ytJqIdLxPc9W&j@YolW%ricC?P98oT;WvvvPxy+b z&>mfBZ%LG5ans}q{0TZm-DmuGdwjutz>D4 zw~;@1mgV#86PceRn(y!b^rwzvdbvUh^Uf#%JXLxGY27KciwJ`hpn%qR9)8U&H18<2 z4zHK@QUQo)<=nzp7|gXP!QY_vW`W}g7an??G6t;Hir@ot9yrooT2?k}f8U8)W*nG{ zMwoov%%^J1xC^)TuuDTB$W8<_=+8vD6tkGxx;thqs5d(?c?7CE6)s+@(AAiGm?TF;%u-xvyHKTVZ@Uq0dPf5m@fTL4mR{Tb(aLp5 z4XhY@a=UsSiOMeVok#(qq7Qop&|HsYBr(~2Uh_W5MKqil_`hHay{g z<=af95oxHC=HW-T-8Q@=DYaN-njK?3ax2YtQm1;yQYnIEMJ=d)6C5EzHDKzzv^I9d z?qC#97NA^pG%xHJ?gPIz6MtU~tmYYT-=k_=_u^`c06K_xAmJBdI2^gAf-b$(TQrbVgxgp< zYh70>=(}kQZ0fK`U>=`xE(`=eq;9gwgo@rFAd}hKW)XE|3rM53$v9 z60d6fM4FvnaSGa&s$@gpan39(*jm{w_n$E6N}Sfz%z^$rNlbfU%JSg&dr#g=SI{MB z$uQMDsO3W}-5fl@A!c|l1u;8PsxBoh{gWQT!uUU{i(g;}nB|W~ZOd}#-@=fTynJ9Q z(y(gb)S4mv8kNEy z-Afi|R8KzPbmRmA>^~=SO^d|`J8ZU2GB;y4%$R#0cH?=99-N>AXFeRow{2wXwS4a7 zYrRnRL$mvq-AWg00EBc-W~R9I6O`D;Sh1jKv+Q-`Oj95>GnvJ!m|c}#e4v}g$*H$s zXl~&fir_dbGbwR)l5}l2Cn6I?|YBn_9 zPWFr-Z-PYzcGP1<-Q36LlfI@;vLwakq5K9m*U&0*3u9b;{d-ieSBKt*M8{xz#<0rl%Ra4OT02h2oJNp1#) zQQ>1!t1QcTkXI)Po&Sa*-$qdWFGzpMX2s#PDhCrRZD?<-RM2?Y{Y$6I>BK;!u;(J3 zed^QB-I<8Jy>PjOeAiSHBO}?0(Sr;Er@1_z+b30?vn>6hZ^j0WzSz6Y{2Z}}|2*bc zTe|Rr%frKV^Xcbf=YE5Vtxn3~dF5v^AIlZFd>$1C>GG1-naukd1(2{Kk!Qp2Rs7xf zP@_c0VJ4?!pyS=vei!D7(L`&2iLUt?I?I(Wtm<|4>y@`FBV(pBp05L=1`i`?xE@MV zmGhU?Ee-up%ee>Di))mW_L+Y}Iza3Ae2pEM$YqURYGY-VQuoe+igpI8>mS8jE5a}~ zA}QC|_F!|%`=Vt*4b4Th2ta~DOnGT!+%#2X2)iq7fFaadY`8d(Q*1RLW8_SvFDZboN~EOB8*5+{Uwux|>73BQxv`!)<-1%ZQ+l6atAf7fwrM^gOaZ zENk^BBrOk%)k~a*r8(1Prnh$=b+`IYP$PB+m0o33ZtiU%X5?p09&K+t~o=+>oI|nRXH|CeTw?} z`g8zlxd0h-2C2IY)htDo92MHK-xL@GmZEX4*k@wqjz7}Vw<-ePB2cl7r0Yemv23xw ze%ze3YiSNW_e6VtO}Njd%Ml&r>Y|6G$4Hm_mE7VVzj-;cpt?*V_vW$kYsx+S*s@s? z=aUsy^k1Q#HX>{*N!VaXQ_3|#_q2qX5R3DpxxpSU6CeE2@9I`VSp*NA9{ut{U_46M ztz68EE%TOTm6X_TW%B}%iVIF&xj_*k?7G`BUgMhacB*M}KvhYm!H-aZ2M%e;1wQ=3 zcuiGDJYTn(!*eN9rQum+goQdTWd|fiSvK8D8W7{WO-jlLY{!zWb;{M%fRc!aI~7hF zsRP+BBve#YJ4I@mS9edh`;|6Nj@II*;`wZ%Qc~`#vM#0I@65of}il z?YJaIA^h*K>p2a@K#(rX*mmn1VYUKl;nO7EZ{9&&%e0zDQBx|UEIG$c<_9Ve91Pbh9 zqH-!h2YKKPGIN`Ba5Pi={29Y(Xaqh==CA&th5jssk2ybI0uY`7qKTXT1oD7uZ1ze~ z#}-0I{&G8*iO$?ajd_^Dwaa)P6kHzd`i&v_UeJRK%?u21Uq* zg@<=~oE3pPx}gn&BYOKbNv)-eLMKHOr-=D;or?-mGma&72xNW|1iff^aC9hvrdn%d zAYlLz3OM{%zvVo_?aO6fxhNLS@AF_y(*Zjx3PcN_Qg#Oi2Nl**DVY8BhMNk;S&%Ol zFBh!9eeFY9Yj5=)1AjWs(%jzq1bwNt1?Aq`rU6dg+bZc62?sQ=^WymGuAtea4T+n= zG|U?LrMuI81W@axkcbGHjg5_B53Gx7VwEL^jVSxd#fvSgV^z4`hQsXz!>8i_SZa2k zg%HGE5c3B%M$)R8G1`m7+NyiQL^KM%Vr=ia_13c9qbNPU1+*)^2NO8<*pn4*2LiN^ z)YMd1$q!gEzg^R?tx`eOxr~w@KO|#?oQrb?2ku_R#i4ot^1d@$Yi6J42Zz!gcL}bw zfb3ufaKm2jF%uRdig6?$9A^W$uTB@l*^h342uQ3)si5QPV|UXr1-7%R^I~S6yY@4w zfFHWz`Gfsu^=Kh1t2BP3Rx{x2Dtn4P3lt9z7qi2CS3t}*C*KM<_zTZ+-IaM5toqVF z<%GfY>uLvbRGWR>WSL(A#lw1-=*|ZbDN&08_4osAl6?Zly^N(qlk}zM6>@mnxQ`a{l4#1l2kK`MWm9 z31g-8%m;7}D?L%a)7f~NI4>pp<-r$J9{UDF!xj^oi5wO!ArEF_kdJzv?alC+_1w3k zOstj_O%k=8md*is#qmM6XIBNXjCj_gs*;_o$)X*!emH$sJAb-cR>xCgXU~G*Tb?*Q zaop0;II^D*-WxSs4epT+C2!R?k%nJ;5Hu zhwS%!m6+wnsX%jsk!g|dA%-L$JoGaTYTw*dukyPXx#xB*Y;niVQ|7Ik()Y!UloS_q z+{t^R0t8*(T4Z%({|(h6s#3IDJY>V)cY62fUW8^=0crGq8FUZ+gBeU&GXxKJ8d4qMO4}nU&&9<(jhU9X zba$*kQE!y>O1CxFzj9=Fdpdqap%>xDo>00t#I3iMGzm?Kf>D^~HTPC$GKTK4Imsp~1y8u+V zs$zwfmbP)Y4RV{v>6|eokrVNci+cIadESD!htrpFP76+p3G~Nl?k`4*UY;-bhv)Ky zZXNjM@nKO6QtZc8E1l|(sxvNL@&zpHZ3PDs-D@-LPj3@}8c}M?BUftGJ2jI*jTin7 zxB!8_efcD*0lUl6%uWt6j1L3tJGDl0l)@2XlLKFWKh~9G`->v_hnh4@R8^@pb zA2U5=W-b$9V@8TZmCWx(5mC2z`znMbM7>P>^t8PrB76P5bE!4!LHDWU!{A05=|;ON zjqC?H=%Qu4>Oc3XEeUWMDCLoVQ2fkLkmxK+@Oa_VS|JXl@gXi{^h&G=z}nDDydW!} zRsQB{AUU5RkdPFf&Ed-WP0`~*9M;nFI55u9F3TU90NMRm5%)^4F)QH$W%(N|kAkH^ z{-o0dCl9ZVR`3%iD~V!Dapg(K5lGXvr`SE*?rPi?!FZdJ(z>fC+*Yfg3exy8wm+}v z7_Mzx!D(2xK!0XUW0mS;e6OvN zFhHm02gZORUY_^T?>su8OLwB6>G|Q6kuo_T7_>vS6x&ysQPt(4B)1l4B zva4;@)*fiCh4#4CHgJe(fv_9voW{72KpErwzoHT$I6=jp5W*u32Z4>DS_-&Y;$!o$ z<g}&IfsZ*|N#hWvsL0XT%xR?K%@PygWS7BE~)|xLqQOEw1ck?~sCf!M_ zB_^G1L@gl2{M%b77X{uzCT9H$-?-VXx&AAx*7h@K4R+8lxD_anFyTuT0#ZIpAQ+{5 z7l>LeEP_zm@{yaaJxm^~(|g;$I#3((8|}J(CsfdOUeN_@D+)tPt$ECYUwBjZXp2Y zp%YUlYZA08O?WdGrTv=oYwa%aDk?8}D68hfnf>rZQPHtqflo_2ze0eXiAfeT8O_9+ zCUHI}L+A?;iuXb45< zYo&^BIyQWEU?0V0Q`6JaHMNIs-@e^Z)BjAH;|bLmYuiS4seXatM+kJaVvb5iL4n8J zHBxan^IR>&!6KvltryzSC^HTf5D1DMs{k93(6>jJVx@PSNt8BN^*km&h zeXh!JYu8LeIZ-96@HysRmIK{{)4{7U2(;~`E2uJ@EV!#_`S8@{^U|rj{EBN2E-QCl z%2R9F{D0O2E0WXYKL?ck(+LO$$z^USNN9Y-qEvgL&!E01R(;%Q>dV6`MK`)0JZQU2 zxKt_t(Z)B6$HyAvK@qw5hDu37=82K+dNXa23W(CnaHvckai6S&B;obuKvKiQb91LL zHM=)nqhYa<&!5ZwJD+&N+JnEk9ZfbI;M^KU%kQ06g}vdjwk={2aFuZX%1tVk_GorK z11C_VI{9%eqHxK_PxOJEx`4X=dv19BVzf18$(Q%}L46sk-S?D9+x0X3{RLR5$CRgA zJ{P1xFwu*3$+W#Rv?sXTX0*K5w}|XkEeLkaR!?A89c?GSojU+sS)iXvkMltagnn_a zsWq!A1O~jc)(jx z_O8pclqxD$SU;d-9z@q&SoPCTAE2GN;kM?YDjC*Sfd|I~>VGI8aiFs`_-~4`z!2BRT=Zt;V$fCkIn7_J;J`KF1vKh zcQ*m0S4+0Ixc_r_isDBUFrX0sXqTngD`~v4v{%Px=jix0cn8t*|p;=W7*HGu_?o!+4r&{_w=!UT*Z2 zp&C<`&0ij_cZChdm|u4f#zwczdeR+&QbSdP;{KRabg5UV{Xfz@+yBgYUJ?FRv+p;b zY|!$c)^vDxwagS`d-P;9nnY9s(In&R(08SXD3ZvcU5WoKHhLwp=&b(YO^Sow;Yq^S zSStMlzAYpa6ZrI2X>ci{QzGvr5ue~UL;rz5c`yAVvGk^t5!b!39k{~^bnVU9 zq$1xUDv5hbg8yqeMf~rm(>e&X{P`s7c;F#JI-ufV+OuR z&~upyJxfFA#2-UJk>eBgTA`((i4l_`5f!aVka7&L6=G8ATrHqpSnW;o4)CvQ`G)~>d==6At&J6W7 zp4&7CmuPXVb6A0D)wm$+9gw(oP)f(q2i&Gz5iK!`Xua;-q5FeSBK}ReVnh@(&Af}H z0u7^aG=h$6g1raYZaH{@MaR{2!d080?Rgs2j;2f2BzH~VpWLEj%y5k~I@B_i-W@wYMkGt2)D?qUdmK!_x?iUO2!+a^U{RYv#GjB5W2wveR1ktJRj| zJ=d*HJ~@Efj28MbOtdF_1U!II9Ny9*O)BhC+L0uz4aUkeNCOU4PjcVYDp8ssB5S%F zR5!KZRtHg6$0_-DFd+1T{~~f-7r)%Xb$=|6*NhC`-YUtjd($I2aFa@i0cdIb289se zU{dbqAZ8>?bk^+VWl3VdBKxe$VP$$Q*)s}6pkfG~67GOb-WZ~eMF$Y-kf${OV575n z9nq&M!7S_mfLF{`YFe7YV7B)p=%4Lq{sx$r*;CMEiwG++>5Q`-u`a{mFb14#f56%x z$6oVpXz7p%>*NOH&KYpac}Adwd2oD+$QXT(gN&Rg`%L0^(3{BV{gs;#0=lcMlk!mL z{|Lepwqvna<3G6syVD1B$08obAhy zkbgC$s(}s!1!ZJH+h2U{IjvnFI$rRbG#pZc(&|zOtdzhQkR}H>e0BC2WZCgNU zDys-m3`!Tz+kJ}COT#@)x z%xFkBRJP#3iJC@-?Xy{X4j`kG8K=t3k15b+$LZjp7{)5UXqL$n-SOC6=x_F|4!Vnn z5vZZ>+IyP!YKMf7?Bh*RE=dr3vE~PPkWmnS$$YvC?p$ZiaP3x?Al&q~cH?D92<~5k zpAvK@SJ;rpt#4AjL5on{{x&0sXgOS(aNGK_bDdLs((Bv>+bGa&J}3|-muJK?|2ZIl zTUdo>8WlWZ$&mRy8q#55yj-w}VYOFO--PeN3N)da#rD4Aq7{1`%;usUQkYtg^i=6QQZPnHk7t zgO3#qB2~4Ai_9f;g&0I0=|));+*|mBaGX23a~E z=!yevbFZGR(<#JhN}cUu%4)tv3~!;a3s)s0A@IfY=VZ&~Gnq)1p5S4@VFEMuL0Y+} zmJ2t9ULHn{+hcSy`7Auht}aTd0oAn<-;I(RWvQeE|8?A-d}74(*8V(G;&xskW``G> zv~6#xuLFOFvWHVSyh(b;a@st_a~~zEmFOjx_W{x9r8uarLLDsn_LuKMRi1h>**C>v2c_r-C*DLQllu4hS-GQ5RtF^Kn3m z`%^S!1meirF{lvQFrV5pIixzJhA`p5zfMq!6v}#z#YJW3la^~6y56_*6#}Bej(r9H z)FIoB-D(($F>HqMxW>O$@14W!O^R=2yN3e;+EGd0DB#{%4#VKBeYvD#_Ig*qgrhy9Tag}(g$avj`V67u@{p=_w4nCI@Y7U)-fM$7qMk$^}N zBB!C7;pG1rx5lrQ{jZem*DcDGv8reeG)bi7e@Jzzqm;_!Y<@y1Wl9X)0^+*8nWHORipES25@NWJ-=m3b)Dlw6X=e35_ zQ~*(e4MaKHOXjimWfR{gCiLlmUIsu|$?l)a1TDQUnAmj6?*l4^L`pJ2U<_yRH1I`I z^pB+e4{u)qRn@xviyTnVgN2BqfMTGuNVh1Wlz?nHM5H^VK@e0_N?O20Yz&#Hjc3;YTvk z$tql2V272&>ne7%M;xD`yq&{;&>4RT)m&<9={JAlO30#7MxY1E!pBqsy}T)i`{gBe zVr#d|9ZN2#vyjzD`FIV5VL%zHLh7)_FuwFVQ);up-6Md7I4pGNn2)!gH@Kd?x{_7o zw*3 zj7d)q2PMJ;2BIwqa=O<*)G#EfC|&WUWFR6UVzHUNsaWigw`AiC<8)F6W6G&yX+MN1 zZaPZKWpm_`RXRFrP+kl{SGd!194KSg*T}QMm8;%sH^y-7`gM+#8M-=01c2&UG5?}$ z-tu~%<%XwW7x5o#4z0}BF9{H@Fp>@E-TVfv=2aOPzqkXfF3qHTfPz{L6@pw&vBaq; z?raZ)rVzROfsdbfp@;6F!Lu{qvT@I+l5`IoMo;0z?{q6t@`$9 z=8H=Z+Ld)$dkrvUFhUZOvzi`B9qQ`?YOqCLkY=vgvke}vS|}pO3|p{QDT#MW=WJV? zQ2h?~Xi9Oymw-9SZph3h&D8cP;T#FP!+7A11SqDy{U4t%?JD7TrCfMSOogtTb*R|s`($dm27|ydp1-b0&j`( zDM->Nl*Ik*2PGE9ufhdrDJ3EJf-JU>USc^5{FNt}3a4Fp*S~$9fj*&)`_}bNrJGyx zY)c4cr7$`wN>L=E>ky}IEuMT5e)mL)5_C#FXdZ&7l~e5Oa@b^50}9+771ZF0)>wp7 z-G6N8St`RsNj%ltHeomJEzZru8f6$A5(&{b72Ml$>qW7) z3f917u>wE&{9mk%Lbr}88Ys3s_#L;}yLNf5DN-a3!lq2#_f~|dh34@l$0&$}tr>H^ zFdoC%bfyxHQ#cFw{XCi5Y?qdqGJc+gzaEQ$rL{Z!fus>s)LA4d#Q(#Bre)??FtG&< zF;6-V8f%ZW!$-R&*<%D1sljy3zMzSp|83NmgAh8paT7bqw7cHEfqc`UN2ASu5GPiHV4Oiyc>LfEJNpPS!918?qIfjWQ_UKWPLh zs<)@*zOtvlu>@ziO4gSXU~&v(J`{E7k2g@6h6qxDet8Z9RFG9vWr5Y0c3By+i~wd| zx2|kbgbB0xM3m7B=KrO)RO>ezR10m=I$}0e92X)G|l@Enj?r>6L65Q^+jINn7X??_vDqwKJL58YI4m!L;h9smaItdqx=2) zAu8_hxr8niBBg_O4c%JcDr7AfV*TZYz|vk@{*XihFZN^jx)kbZ&s;SELcbeGju}AI zl^d&~1af1phX=s>Bqk)}`X?o?LI9JxF~3(|!5x#a#9DMrZ%;adon}3G@@?l+c~jF= zqHGjCl95E34u(T=a(y9%i6mu^%EZaW`GxY@Hit&e6@qeFS-E4E#okQ)HmLO;d#M;v z)SV{onZzC6?`y<})!FJ=-d}&qKYaZ9+jh`kRlnUUK#!6lh;Amx#@$)~dq7Auw>2E| z(GEN%5LN;$^NUlbPhWFzC`?o@>6LYv?98gYR|>gp*q}*Jev1Zs#O~#r^U>qz_wvY6 z(n!Q)j>25=6l7?@wEa6MZA!Z@to6>!&d$kXSV z$Iq%R@caynN_l@qPyj%A6JKUWQDvvL;M*~9K(wlefSHy0SMdjJRS(~Lr=qQiCRi<7 zoB(&9q#V8%o;9%-$_HZsC<?waivYny9hg$v)s@BG#qgEUV4J2TqKa zmtRDLph2yLK5Nxlu!!>}<3h-uiM=c%W6`Ctb?R4oDH<%Wzl|?!OhIUl^vW57(?v1= zW(XlON&2Y&GoxUc$n=H4!O%kY8pUWe$so(Exz`z7D0&eks%)b9Rb$kpgh0$Wi-$oAF5oYTTO_2z= zOO$@ARQs>y4=@Vq-N0Qw9^UVnm0O(cU;i}xC5bVUj zg(mLE2sog~Y!_D22LSkc4gg3EP_D0~BfC2;`$KduQY%93(wV3pGA?QES6XCJNEM70 z526NW0A!Oe#IZHzau#%NJ;A0{Fl>A}#Hex>Dh+DTYM|+=%V&hv8GD3~aK{d1gSIgE zBhg=IPm2^*ajP$&tlMN8^SUSGfu!=89d4`z23ZR=UZ(wmV^F zR!|bSQmtP@YYss`Bh-xmI4HXfd2+j^reXZri~wm}I7Ro-fy?ggA3m|yFSL1{jTS7e-14SE~-0EvPH9ww*Y|j8t zuGNN^6rV#=-!mq4H6($^^P9O%k0RdBIkrr#v-y~J;HMP!P1v?7_G5~Z4!SOd=+!jlaV&iPZ?g7>@rVY7E3lYU> zCpxvX*yn75-wMEGuoQ+yl5U)hVvf7vn5HfCTtSGp>z-di!i!S2sD1_4cO5 z@U{`s7_I;=pqA4{PivGDp_xdMqZilud3SxW`i{3@v%~HEgr^Yso)t6VDES#0RL7Jeh3H?{}@;4;x zdrEU%sj<-Rd!^woO-oBl2P$MMeXTT0Y>d2d2&q$XqQ107zbD7v60UuHeE{C`l>fX31;BfEnv}ESFv%kzLkrLY= zoHsw1Sq?JJ+M?eq^F}1TC;A8{SUuAbkVaiTJch4fsCF-H;q8C*64_O#MP^b%Zkv|x zy7*XQ_WENSP}RQwVK>{AgzZGPq~%5$*(W$QA7~d<-x}OIhUcmPUQA9{ zh%Ud(@YLMtC05Os9HSGsc6aco_QYOa7Z>tRQ|cYuTL9cMqkCypn&l7n&%D31_Zo~T zSv1d4K8>WtwSEY_-ByTfxael2RAE#$OI9**ipz>)4mJ;`pMi`!aEKG?i2vA`l z)4m=iDA#5c4k0bOPM-W+cyD{3A@-7+vESo5Wt7GKQhODdAz z9V6s=>vVq2!G{l(=zaaK3=CSWWH>~0n+l>}GB|gn!Cg`jIOa7VHR;E_O5vQP@BZNn*)McBtG#tD$ zkj0D4$fr?s7&!T`<9B}4_08MfuOxi<5q{_&9kH=zGY+ynu*wZwO}gi9={N9!!7t1< zYGL~kMX~7H+R<7j1y>okpp6E&6d^0!NwgB~NpsW{Ym=rY7D(?Cyg=CK60 zM$vdC5>-;A+moN=u)Z{`jZ2DDDS#5|cM>@h5*0m3%JmiO&M(o;1_rKbEhH!SJj>@A zb8b1!8k-cCNzB+M&KN~erL;#U9Pb&6QOEv6ht2?L=?9=}pr~yWaep#}!RqJP3UhnHUCZFg{AY zvxD$3`uTiICfLz?{M_?wQQheznW!>6E+^|(Tq$;gWP_{k*v!ul7KF~LGZkujjoedN zT8#6FV2_@-=;GX(XfXLKW4P~G=G;@`#>%W=loOZ+Q_N6Ws$?kV34|#H7%Z!`(6+%O ze(m<{*C-0|3Eou%ihLC;=){{RiEO^F2W$)^Zc9tWW(Bl;URi+VJv-G{)qo*I?Q33N zBuD1%-Qct=rki0ZfG@trBh^|WiOsisDqGnAPs%QI*yii&e8r7Eu*^r^ODnFL)S&N| zh&oa+vOEYD*%(QaqjNjyf?8i9}4*q zGL}wi~>Af_NX1EBAcIRafDP*bMmCJ*otFSQHLkAQqOIMq^bJ69? zmjg#TP@Eee0~TkNmC|oNbO0169bEX;Rqu-{!qmm>wG7N0yP0&VGx~^QYCG(3m6s8~ zvk0HeUC@bSN3*>F4d}pDffUu(k4LH6GhZUTYrx2?1=8FDQ-j{6xC49T{q@$-7+bv? z{caFKlRI3^PWX;=zoM|KD{o(P1a@AMPxDURmm^0livTlxmR<{YMxL=0WCQm=JQ64o zf*=0ozsBa2mzP(8i!1hf0zKaK?-81KXq!h-g>Hc>h*1r8pH-gI>1y)BU0JE|PR*3_ zfF49^FyE!GAzur^h8wLN!5xpOC(+FHBiY3Zg|*GwT0fOpdn*Z` z$nnRXDpR=bE(ybC@aN~m_tFUWR}2XsHW2u)={>Pa-yFCUk|_Qojf1NXDE)L>>E}#L z&Kjhg%}`n}Y$#f*ZvM0BY`c%tgSF5cdsqskL*l!#GVW(wOzd;Rs>Y0nG^It++vLr+ z5cua&_PWleN&j~)xWT&CqL%*rzdGOUOfN1zhZu@rSPp}J^-~lyKI&G21~>|#0=^-} z`PH2@hYlQ|hKJD~id)D&%pXF;QW(IPY%Qzm3RmwxGJz!IhrktI)I?bp#n3=hfmW<8 zKgG^~l#?Qeo>{i}L;m4je>ToKW$HM1=cwQ?^5*l^*sT@z;ggVf;=0cSI_8s33P9|Q5QRx)#x*oe9LQPZ{MDV zIC>-wdGS$MrH4spat63LkXrR|PzsX9lhrh>8EBt?T8dEn8P} z+zpzHzK?L=o{9KinhF$lGiX1MCSg}5<5{}y9v&Sq@Jl6J%^7TUz`)MqbCW$?p^^SMoufB@4Q9Z#TT-mgS)haoLuEDY+W#6xCD7vN((-3f%f7v; z1GI?`92hl7k#Us6n6<5Wv~x9ScTN22q9mO=ny(b|%%eI0#!FZQbS>R}(Kutm(<8GJ zd*a+Vv}Xn-07lPPqof6D_N|#}(}2}^LM7MqjU7Ag>GJesK168&)aye}&-CmcwkkS4 zHV4aqz`(Q6#2f$$Oc7aj!V1FtrGr)U9099R`Tq^PTdp;S|dKZ-Op z%b<;Yg2+=SJd!G_ExsN;v4iT*yqeL4-jT)l35rZ%H@TfWx>nPmkElz(U439_Yka~s zl^Gz+drJ}kF$!CMYlLo%Bd72xO*TBu)h&V;Iu@%>G!vTfW8{AN{qYypy~){XWHa>z zZh*;c?(!WqHZ@D%Pt8}Ie!JX3`&c0;W^ z5FOlbSs!K{uEnh|FX7>~cnn>e1doO?cZZ+k``3@QTY2GxY7;9*P%y*GOtTWc+NL(S zOQSe~|KNVbyedrp>6h5~JoVY>d!w7|0jjPRn=&d(p7Ixf7@|XI@ z!38~HqP7i5as5YNYXY2KXt01`;&Wq9Df33n?`y79g?l-wuZU2i3%X3w3)#p!Tb@ zQM_OH@R*w@^&`_ZzkrB6*C` zd4LxXwo2euv^vtYxE{f&|2-3l9?qpFS@O4`;}H&C<7Z`+1rZ?^0Aya>APm=5hw%jk zG$pPCqogWLZ@x2tkYy|!9GL9wao$*CxihH}#H``Us{f3KL=RB&#}qtr2xbF|qRzq< z-1RfTNE5en&z=CF$ZODp|BCg`@gG@6@U~yr7T-MkLQ7j)$soDMymmAH35^QSV+A>) zAVz>*f~?eM!3b0(!O?Tuh!p<%EC`$?XE)RpYJ=%4*gnbs-9L5u`3G>|0 z;Z`0dO*L{!c2<==MF}@zyojP8mF`y*PZ9hI;)g@PXNrzR?>*%0k|FH%1cfLD6_aIG z^ovJ$ngTq+amf@M#-VwYXSXf)PFH{}#Ow|ZP|c;Kkvvtklqdo`A1CoNShOJwCCd|7 zt}yKqr-K{NkHKvCvs~24-e~|s$T5hwW4Zg?2K?Xah2w`&1*5KrZHB-t62%*Qh&_SA z%oZ=bl{4y#XidjCf$>z_x)4%0Ggw=nJCCYH%PZmC5VFo;vm>0wFM$rqvzdm zEN#K@$HEWA#KfM0j4AN=G?HinA1?;7fuzVh)?=1!UV^tDvSyVA8jDkk=K%{*IZt3yoJn z2~b{9q14zAJ*=m^BPsBSxA$|j^`xc?iQxqKjvp7ap6X>ln{T#K*8W;wPD?8P=>Q_$ zGXp#}(-B0=mgdQDmxHC=UuZ;hj>4gcN58$oY&XNpy-wWbYhA3|!5 z^B58(Dt?FdV=Xr^WW-Wg?st2s?#xm0SZw-tt6yJxdK(OG(K}LS%QbM%>si?F^o%p< zUTe@sR(|?Lx9!nnaSNQSf~lZ5h!)L;Av(G*&f`CrbMw(=8NgO0cH`e!l(ZiN(MYej zi*+T%oxowe+8s&8t@h494TjWUfvzP>2v5!y)a@KJxpTu zzV}3N9)BLW#ZQ~ECdp>13Igb$9(6jUKG2P{cnOYF4g#La1n0tqE_W+{S!I^0}HsnGfww-Gdks$mE ziUb{BCN zADbj;yezFbsm-<6*0YNSlwQ}&WTG)IG; zjlM-}S1DF(KUIa4^~K=n6_?4LolGV9+54#5sV&|H}zTV{8jg;v=; zqUKoX$?-r)P4a=D*v+hIB;i!^MI?{j!pi&iQnp~*x(xuYIpmTBJ0gw(?AjTV2EPw1 z&$oZr%l|Ec9+5rQ!%-Mxy4QotA|kc0QA!J`sT{sdhbnICd9noz7P>JXI-Lxeg3YNK zmq_*{gIXRU3~!cQCME(n+W`JsdIeY{d|6U3a8pvSSXC_6<_-oRJ&q*OtATW!WhLKo zgc{nu3uDDYKgJl^v%gr{G!}rxHk^DB9v;pMd!J^&g;O0X9XSAOC);qrQQYK-XVW`n z*L#8@c?@ojpFC{LB6+57lM6~$F38M7`{@3|HUe8vdW)NK0=jnNL;}d3UZbE%lU`nw zI1;yRDfd5xRxl{Ni9eCR11hBFKof^dVM-dIum)NcrTmUk-O=oicry-*BE&h9TeFq; z5;V_msU5zLD9o4~$j85+j>E9T^NHP_F|9koA%b{#xCstw@GyXFaH@M_(&)<@6ha5VvNg8G9 zHVLh+tx2r7fn*DTG#xlF*QIP_zRkkiVHOEw3XxQp;9jR!|J@ywKHz;4v$M;$Vtwx6 zG1>Wjra22UCAj$9RDK<5bmK41D=DdhN9m&CtGgfszl7+IGsjx>ZxSc6={l}V_571-|>PwQvsmIfic)SRKQscgpfD+zTaFx zBM-!!2$$32|5{EmGn$YRe0&~&RCAbiNDgs-S%&^Bkzp}Y>#j#q!`$aZH0r#3oz1L} zp)GM2OQ_|$J%*gqAlD9^@hdUg;u*=G>5JhP?VYSBrPwrft-@=GB3)3vXIzz4nB`V{ zk?8og?4!MiT4qx>U`I^jTpqA2yfoi4u^cm0)g<$#wPDhgH~;n zwvoJ*2+^V+0kIV3a3?}MF>7H*ah@$UOk#fWn?$)(FoAW>)mw3F7miBW6U<@4xM|i+ z^`gR^^P5@>A-7A!2eSN2(dGqyVikGFs?=U<^TdKhx;FEq)Uw$U8e+jtTW76-vN{n# zlQG$yFQ|O%+b-e=$ePeTQ`5|hX=9VMe!r^~xwWKZX_lI2dmR^5Ns^O240yaaBD?;VTMd^{t8eR`SWUk%)tcH7SBc>aS)fhf+m`Neq&S1VCjiH$Ref<P-2U=UPU){NxN2j%i^`6iT#35iF-95Eo}fP`|VQhgEV z6-+UjKscycf{pRooQBkXnTFO+{)=fSv8C_gXY-9co(XS%L`i=M75MusguAQ1XqJEb zEm0xZyTtnmpIIU40Ex}uI|8+ZxBoS>;IgoAHN@%=X*x<@yZ6^*vi8EAniKc`YXHId z^XJXKyg#wIQx#pL3(dc%HNRA#S05~=dR0+AFrcm%a^GhCHlhqfEtX%N^tqfg5@4$`udDOug2bZDg>Z*Qx{Sx)kNF^UXhlgRCquN5556&32G9h{Gg| zXwTZ}>T8uea~9i~+j($;-3E=a3>1J7Y*Gl(;rT*JKD)qSgiTk&8*UMQ1))s~1vs-0 zEw=3a)I5s8K_vbJ@Jo8%i3w$Jg#J@v61FLk!wFmT0=z&(hV#9nYt5e_@qnYX4u-Px z!+I_}Kb=4*(&0zs4+V^y-#tF;cO$mDk&l81;XF-X>jbyeXcY`lo4L%I-@vxzb6&F| zP?MwE)QyXYn_CI(i^6{c?9^2_xR*GKJv_({d)&V-1}M%T#JE011pRs2 zC8z9y*%A=BS}^tNXS2ypK}^fG-sGA5u(fdp0w%;jG!g|5sX6EtLO;`jRDj^Y^Md4*=3NTJ zsT2$lY&OnRvPun=fxT-|7q+DJ2HBZ_o1O+%4j!PQVj62p;qcEy!2X*oucvDAQ##@N zj>wm24jZ_}I&d%gSz4%Wx#$#%ZxdMm>K(1+vOR^oLH}mH>Gkz1~U zBG+YAGez{Bluo1dJ^;Hu1vx~=y+WlU@!MX?-PM1WC|3bavI20_X@}hi99#AS{$+qj zEvs39?%q7VftCSGEhu6P6{Z&2vWsn5>FpH6?_P{#Z_g#SydKPY4I;wzl4~#D#9iAv z5maBceFR$K1&Is)%6LXJpCG6`Bp@q84a8g$Y|+hf+wD|V3iO4e<4Cm zDul73#fBdfcR}6|Y5KC+3W^?so36D$wvca)c_~B~#AOk^w0=w2{H-r!yI_J=6Nc|_h0c&nh^*d@K=hYub+h4bKKvB>HD^x$0nn+M0}QpA`N z-*`XoNMQD#XT=lnj5Je`8kSA}8DNBbS`@8i!k`u>YPlm%BiN!p1jwS&4|5Pw9n^GP zJ&+yqlwqWD)~YF@;p-P;X)lo|jW9_EjGXSbgw_;t&e6&-E7t_jL3W9M(%PXl;nej4 zv~Si&gkQdy^{=?rXRxtSvOx#!GfN4hvK5fupC6E!|P?<&|>h|oq^-sa%l z3@P1xR&Ut?A-zbUw4~TlJ&;N0u|RE|`RFjWWh}~YV>jtFbRt6RZ?LFYlFT}al%U&u ztz8VNr<@jf6gfZgmnnQJ2A;hZ5S_uVl~f!T+W|u#gl-Ef9WkI=(hGfaEo_BCB3#*) z(fkZ_xax#Eo&hDzg`y`}eyVR3)+V}uOsGG(`H<&k7a{=S+c)R;(WPWf(WN=2C4332 zT?bZKsuldCAZl3PSq|4+6F{aBs<2LrlePH+(phN7)^8)ShT$)}6wtCj|FmLvn2n

!J)_mr7VcwUhvdSup0txZRUfL0P!iFFm!S(Gt=9_C?iVi0 z)w3FjJN8#B?^%DP|F?YCvR~I{e0kju)0q}N6W-ZG)N|^$p?MdqJ_CSO)f3&q02v!r zGYh-MQU3M-Shd-TZX?p@tJ>rZoPLD&`lIyBosVnaE7D3d0pwUtPp%^1*?Uh?u>BvW zh0l!n<8S_f2$tWHo!ALw|AQW;%pjAufO(hwmfYl~4yta@miVGBNx8!^dTBH0=f#&V zun-^sWO^ABk{XS(Rxr9VKNW%^EQWRgOw^D4MU`1BTJzK5b)gHfXfSmTD$aOsDNlGG zA7bhL*0a1rQw!jVvoIyZ^Zrrw5{HVwxu3s`1G9;G$;}5Xvbf645 zMAU(lz6R|jM$(Q??Dt`5A0UO3_r#$nr*Vlq2k@l^xV$>?bzH%jri5a+i+C3tA0Z^A zfP<+)@1c$Lr&KSrF>6>x1MlGQ&*-QZ7W5mopB)ESE!E@u_Xoufcg-4XIEAAT&wWX{79c-SM^(4ZsJTG&3_h^Q<@DD&ZKHIZ}k^ zc~L2;Z|-@>^>v`!%Rt*P07nfrAdnV| z)9PD>%T;=Kcm(g0`=cFD?O>@aXWTBrYiJO*PVVo&NpcTT3}UchD&Wcj;+2_U-iH4+y{}i)*5yESOj1Qu(ijK{MG9O19O) zkVc^vW}B&?po>+s{b{o&%>%G|Jt!y$)*L`jD~Vb_S|_9k>L_t8S>n&!`@Jp7Zm1KZ z)6Syq$aBAYNn_GYO@3to%*-ez%cS#q1i_bSJB=nPy2?+ZHen>@3t9}DPXW?-z51Zr z9`o|$c3bi}QaR|iiB6?lZnPM(3ELh-zMagYz`({Y+v345Okz#+qD&VN@AvsTbr~uG z3Yct>5v2LjT!a1YBGY-us{6jwNNTLE$ML=XAPmaY@Q>=Pe#v4)^hYNoii|w8%R5AP zS|WkNN(MqQ^{+ucgI3Bvu__PC++hfIfY$(lpl#_bRB~?{zk|32uG7<|EyjXfd%9sD zAF}QI!x{z|E)pwPp;exFQX$_mjw?A+2dD6V)q>smJ!>&6Ynl6(CKjXQ|KI#%LO&Gu z;3q5j?k9@{9INMEh7xfBf@`NlGvAfAW**`Mr|{!O3Ei|kn23ED=#SsS9abfV9VaDE0Bu5 z+wD?fR$&;gc{wN~1zHpJk_P`DEoBVmJlS+!KlJ!#C6oxP2Y7#~FHZh6jv>hf?l>v^ z9^d5u||E(t8uO;wbEnz5({TR39gZ~$!?{7l;sI!nOa?l6DOI+>)apuXfUcFqy z^=QmlCH$jg@9YGUH-Q4f2rRm2KK#LbPXF%t{YGG9jtg7D`^ExCZ~AX`qCq8hnYj~zub zwk&Wuc}(P%aGoopbQ7XK|D>PW()jmtp<1RCWD6Z)C|))IjOifU$Vi^E$uaH`XB!3& zB#JWD-T<3AEm~!$2$@l7s~`6|GY`X&(>~rZ4`e9HS&?WRM)_{GFzY@Cw;I@fK{ijm zKAJ^=wzkY3!?YXN)O;6xfIFZHCWzCpE2FSt6{LXm^s5bM-${aeN&x&QQn^7&TUny| zNU(d+4)SrY!{>z%MY;*6Zehdk0C|UK}>@Bb9zOYdU(SNJ^atJOpT_t;SP~itf%>PA$&sI6t3+uOK0~l5Hg>DdMQ81#b{l(U0 zwD<3}F4gaaHaeST$LZFH>|B`+4!skQ_a*hj3{!S13{D3I4b zN9p7DBr{}RMl}HTr_f)2gb&9f1A_fWjFeK;_()1=ld3)}5Og-OLSF0Kd4@en{eKb> zomY=RyDCEZRx7w~2`F)W4;c|R*l~ZWIGko>{UBSWU%R770Ibf;i(26R*4{ulbqk=M z={m+^Hy7Jwa0mk`m#FWyt_A&-tv%ug_X1Ai?^LHsr#>sMrk@-0R1}3sqB3{mNJ1vr zC~Pa$


^sH|$(=q!^Mux)w%AB@NXgRhQBzJrO`e{+9^YUR6b(b z`IF!Bb)|-FEOX9QMmEPw4;Q~WtBSTE%w<^_f0jCoDp0lEZfr&>p+WELsXC?F zF*B;5QiBsDHcNm0|KbI4SN+?SZfa{zUbvCIKELWWpJj2Vl0WA3TRpR09K|BKT?2=i z;^EogY0x`(NXlmalHNIue8OfB&5{e|zr<2B1&lp*g$x7LfsUzKzu)lN+ZH(jfCnN6 zFk0vW=TJxx9$TW*^qU=8D4D@=*%GaTw6bQrGyG*82=Smbv?eZE!a}EY3gUTCh7fqh9nfPJjNVZtC-^6{X%bEwXs`MkHS z2KLjcH;-+&M&A#)hkK=LvWg;_O28x<9al%MUR(%YK_x*lLQtp*!q01Z+}73>-Kui; z?8v{2nC$UMg66>d>nCBv3yb4%0~vgSAt96W-egOJJy^4b)Q0XvSt#Wg8@aYYL2Ehl z5luJyec^TmnOR1^i4W*7@JT+A=JgenDH6(Vz*U4;WX@pw`uZ|u6(E1@i2CMtu$gDX zdu7E3I&TaDE6d6uGAP^}Ql}I|Ec7^SfS{P&zYJMbz4z`zJxKEsN|4sPbqfih*(S6Pt_Y86H^OXR&gui?_TC+ zq(VzsOYW9xq{5(v>2hYvl`M|;&=BQS zYQ!*->+6>$orrhycx5*QnI23?_?^YUBtNW6It~iafgiE)C>c^C#VvWP?T$$0hakI!#i@G4|j*^bN0vq>ek!5!kwy+NcF3={a7G#fL zl)e`+&G^|Ak%Hv^Jt1RsyU6 z?mbp9Kqf^c=_Ob{TZQtNZ6~tY#9Lo4?8PjfU)&-F#c#&EM6!HF5D&3kjBI9>qw#@Z z{CNi>-JHED(xmi@x?xn?9%gh;T!*;W#jgmBdHQnXo^mJUO;o9-KM&9CysdTZr4tC9{?`Ka7j&H>h%+8!4ljnZq$ohU#R*?Z><)55dB_ z%nb1zyn8)V(N7ru2SBDOKr~{LMXa_{> z(Gw-W(rC7Us{ID_y@%R<&Pql+LjO8K=`ZeO?hWq--^*IRp6$3J432pShUE1pF!}X2a{LEttH?ik&7gH)91%n4TtafVp zwjqEKwaulaf(F+adtxf*LKD8C2=Sf7{xyR&*L=dmSI?Zf%uPV3hz_-`b7Spm8X8#2 zh(uGqCRshE1`^9W?y&ik7Qcc%3cgRfaXQ1^L*O=oGY@)Kwcz)aAZQ1=X@(#@KY3*? zFzm)*vvcQWHekC!?QWfhK*y{<@L#J+`Gj7({``4Em_LnV*t00`O1D*459Kxwuknv@ z*{QVF*x`~h@%qQ$x-0wE7dXJmBpxP+g>~LwkuzhXSMTLrr#p9TK_&mx!=RuUTWo&9 zC|go6=6-W?9S&i~>#v0$7P)onR+!)ds5h7&S?o&y6Puxwl;lR6va4G?f92lpL4NMU z#uYKFMOcFDwd|Pa8iCzQ^IKme4?r^88txjxG^yxGUQP@9P{)$k309J5+X{q)2J&6- zh-PP>+6!GeU8pkY{fgh@!$^x?K+riS#mc9QEg4&y(rMCrX{Octs{n+=(hC#c^0&|$ zF9QRa2>^yOmqa~fXBVGWLwcWU;@ZYhN=DU7LUm^MTSi7^w9M04&#CvS>^LjNGDr63 zGSBwCOhJ+xUrOB7HZM@{;qk}r%H+lCgG|on#iZTZYnLDbcV_Al*`UJ}kJq=gYvik| zXEdwBAL^0lwLsuC_!V?Gk+<;MbC^_oUdBjP_b0AB zYJBl@AkoolxL))s^RS-PxJ%;a#xQ<{5$!F@V?X%Qe&8Md@wcTdEc5D(8=A$bS-Hz1 zEEZ(L&fV3;erx~K^DdBo27vF#kt5hcQrTaSuk(5I@4X({x{9V%WCcTc>{)GVPMf#F zbm9b9c?bvyuHL#u4Tha#TZSDbiC=c30!%#unW^HyH>x>&6-I3#kx|bC4tZ`ngJgqE zGL4nc;a$x$AFO+Yaly&h`2Pn6+L zv`AHVKwuwLHb~y}l9aw%zPKg-JuUWo8j?3O5hy_hm<^=8a3_9YI*)dDeA9BHd8=Rr z>zt}!6Nw~PNVM?q!2{Dgff1Q_ z+pw5cgP;|v&>{h{KR3!QDJdB!E}kFJ0a?t8(I&VldpBkt0dMGR^d5X*C^2>;y^oO| zirNz6`6eZJw!dvm^z67PtChd;^**`Kl<Y(YhaY~EM&>$`o zmLpEKLtHV>4;VEr4Am{Hr+W%&5?&?ap4b=~fa@NB@fA5p8xRBrNnw_=@ISI4 zU$3J%O04HyKFTXn1}9&A7$4859qwJRgHnrMGi9`E&H=_I13v4YhRliU56gOTekeDkJ1>fZ1$U z<}w?C+Bh!}UvmGXZX+E# zJ7Jz%TYUdbde_rTm2T{WRhVUk1q4cf%x)3;+Mutv2m})~0Q%_xF|dFQCZ^=&RQ?!b zXT}d?^g^>3Q{L{A*P31l_h80Io|Fwk+}R?#cHQBlpo+Hw!)2FwAt=o{z>T2IXE!&E z4TKlCl@j;@vYUGkGavsfj8a>Z&0E6JZMl7jyj+x;Fzvvy%{=d%10LR+D~BJcpk2N^ zV4E$9vfM=kf{RIf5%<_#2T3DD(^6^QM2ciEw(~&5(h{3dn0DYBIhF$KhR2WPS+Sg(acM<)nq9pwU-8C5ezD8mRAu@X* zppGyOMjng13CY|k-kl}WWxI_-7@xf^orbe03*xtrEjF#ecb;!5|M8|TweWZiT}mbp zc9d=T$ECqyQQ`h7{Vo=8Xmv<=uh{>4Ho`z$@FtVE#?2TqIT zTJPhgijO6Zv51+C#Z$R!4E>4W>bI|x>@cXBt)O*$Sy!Z1+yHt~7OnZ}cm-G1eAu*9 zY0sQ`EF31SdpOB_7QoAeH#?|uy$2G40(Pwogt#H>j!Gt%t(37cvKe!|{E~SN zmQG~@B-TP0{*lZt|C1-(IE-0V@y4 zHS&wpE~{4TMgL5+9T8CGym&3uuj(4cFzfZK`7^3^=37tb!(-Tx%XvsrVtLV#QaoSX z*)w(RYA2acq_F&y4 zOqLfw_{t|0zL^D(j^+=-3foGE#<>N+W&drOKwKdI~;Q z#@c794O1iHgJ+Y~iaOnW^iB2=bnx}%e+lZ`gYwOK$t_-5*aR-ao1A}q*>yK#6=%$@ zmzW~p2*dNW`Vi~~x~;k|=Z-pgSxtV>T!5j|qBcGqb2ibMDE@J)0vI7$7r@++|Cu0& z>afnRtLdw$BAZo}t9LUkl<2uE!Ut?(d*<35lLPG?{4LC7Zot9hJgTyJFxA*w1kEcI zU_*9if7J;asXraPCgP=F-r1N)FD{YAk!_OGx?d@1V26qxI~={}?16JQ;0A>^`BM)& zi=dgyX>HU;n4Aw_cXku@f?1}OGab{h5X9?+5FInKZ zexC*NFQaTC`2FD3tObAiKz1B{HXuvMw|RIhk2-!(!-RZH+I_%1UXD~LfgIBq19%J= zH0$)6J&GHjt>JP+7Q&0eC!Yd4E!h#8;p(NbITvwd0Z$00YV>0CxH!->+ zfwHKh6^uy+AXBP;!PADJFj&Pae@;~{8kyNv)m7s^8?gE2J@jBc#3gxm6w=|k-NO&= z(N{YWhu;R|V_w2~!l=yJ^?5V9rpm!q5)9^LhZ&@z7mO+q8L37b+VSucUE<}f7%jyyCGk%#f)++0PU%C5 zPcUuBng#Lksk1(tAkV2Yod-`vT-LkcVhM#3N|0sdxhjGD6t#)^K_-WLbU(h1s>G{L zYUC!f6X@flC2|~YJI9lXORfqWYmPp?BDtUgh2rwrV z(eHNbd5wC^0gv~`Vck4 z?$F#%Z_7pLq(!e`h)<1c9jG5anY1LGJHJQoN{lW)^5MV}h$%YpV1P-l zB=IDSSiH=XZ{8>n?EdTv`@4Iu%`vlw;&%`)u6HPx#eK@6sPc4@oOMn?088e%Yuz;Mf6J+_V=;mI}cTyL_ev)%Tl}7KkX&e zbNPJ*pOdbu_-nr(%&AeRPI%-!DDv~dGtXVU3I9PIkLW#iLXq8vQU`8*b^e;tGp^8| z`|0^XlgYqX4MX>{8it)rzN;?G?3^}{Xx=Mpzzq*oxcub&n`UDpYb^tXpYExeTFGj*j4CbGM_9jK+ht%5?d7x*PjaDb-* zrDdbcf|l;^fuD^X^5hZWIc5fF^TXo9iY=t_D*LVYHeY_cnLQfx=E7;W-X+2sdTR*X z%fEJz60B@JgkHO(?dQIH#FuSaf9a(y{e%X9%VJRN)Kc1ypD$SGH0a5PAWF!jbQ~HY zxdx&TLm-T-!KhOZu^b&@T*Jx4uj(=Zv6kro)>fdkw@CD?yaCXdR_JG>Cq#ke;DPhX zr{wH$C-eQ5!s?6Np#f0VgKJ-6f@kz=8b0H>v>b*DdQY-9&1N3&JOhI+Y1a9f1!CCiab%(F+M(wF@ zl%Fy-+hIQQ&(>XFJSHzZ39f-|1wd>+pb3S{@R86*FsA0~cY}+)osS=Pi$j5}9^fRj zIW79kG-;>oHv4+Ew%mD;G-ZLhqQF+#M+p+8$#QAWeh`^fht=^p46x^c&;x;_3<|0J zx!#iyh1}|1uxrPogXeC^tJy|Lyx^YC#S%Vz1++QiK@8hhz$Tqkd{klfgyTQl-l_BY z#9>*6u^U=uAdeXb@)ug=N7-tYla`dEE_7cez$o$rVyK@e10CJX4+L=*bboo-JHh>t z4SCl&l(;KT6O)k0f^3<671YCMO+BIKu07!(m>z>gAg#a&cG*2HE7zUaY55qORHcrQ z@qg=iM1SddWG4uE9cCrS9O9cfp1FBHWW?8HHgw+06M@ zqgq*3QtlI<-aP`=MK4(&?R{dDVp7RXWdLkk^f{vuI4`q699BR^9Lnw3HfAr-d!y5t zCHg_vCH~{#wvM-hOneUmJMw^}=>CJsgN-Y@W(e?h@{&^0PQelBb>q=pu)gH`n1FJI z@k`6QJm?80g)WB=CNKr8Nu|DkSu#RR_*oZ-UPffXb+M_!P1g12XHvq%tKlZXdOn@MSUpppYZFa#Me*5 z1qcm~y?VrGJ}lNKGHeKrXSsOUbM~-LISEdPWF^cPX>+^`hBW2nnptawV3E#SVP&cW zXfYH4m^TEmIOAFDw{1%ja;+us9X}wRio-k3zIipCnB8~b0u)grCo41tC3@WGsg2Ap;bC83oGhdKl&w+PvhrZxnf5D_mKZX>F zmT3%_SW3{QB12{Y7~xcer(wCU>btb-+l*cx!f(P>ijtAcGQEz@-npKK14soINWneI z|Hs;)1B;=D2p#o5F?lZ?n}Hkhqf<<^ZyJ_7@q!)MeQi1Hdc^t?~_ z<;2S`4zY|C+huV)FBxc6IL`8;$R(WDi-oEl91=b=Wd59yIpGnOdP-|pCvsVNuPtHf2OH1 zPEgjaa`CV|Z*T1O-Jka#dn}yFOc^w2b-31sK*YL&TIq1eRj={uS+3P1Mhh}z$>|d& z4!S$!P0VXAmi~$XKx)tCud3|=5By(WVMDXEtr(sU+m=%2KoDb?|L_fVN)i6of`{;K zT|B%SkJIQ&k~6gg`Z#z| z!w{11eFs~aG|SpQf4#!?vuk|Je5m*-#iIcWavKZ1%D|#>7y(8}C>MrU0L6tVE_Gng z=9>onkNsH3$)cj8^YKB>gjcF}iHeXY{8aODcbJfHXEN0C<7a$(ua?_4JD0V+W7$@! zClqeTB=dUb588SlI#R^D6D;jSn)`W|#olc71>SUc?@3KQ^SK8czuZolA831mB}ds6 zzKi3M)N9lYPp)kcso&w2ToQgge5jlVPjVY7fW#c#H^I=64#L&C2JSI>lO#oOof^UR zaCTTE1+_RU2WvRp|E43qUj}v&Ou!JD`CQGex`Au>SWYq|@ms@co4lpVvawPp?8nn8 z6G6Vz8~j4o|>r@~hfCmt%9vlROH}C=<3e@lvH<31YGZ+n9WFrLcK2B*m z^R-0OSiPqN03_>Hj6Cd>g_Ubq072}5rKE@26=pYzJCR^T9=62Uc?E3{fuX>(s}GKX zJHqySRPGNDiFc>`v(@N_YC<2F^)d@4^J@uxAw4Ajj{}!4}!yA#+G@Rs0Fvv@^uB!;v2Ggn|c)G;?>n?yjCmS*Nb1 z>49yp4MdJQz9LVH@1;D_6DnM~n0SgUUT@I2phg*g-(FN>c3s6#B*@|K(jYxV%L|v0 zX!L(5dk?54vn^~`9T^qIE~0|K=-5EO1_($M8%igDgrf8sdN+V5Hku7kX;P#l6ln<% zLKLM40@6YWMMP={NDCzdzWsvE+1pkFLF0qoYg!$fH==try(E_Pw3uj7yLOrjulsy4O>Hv*JxSi4Cjnn6RSD~G^ zuoAv@r-KRV$B8Hn{15b6&!e$2v6nc$;s|3u3!Z|f4bhVmew}VPH$TDvQ%B_9(2@bX}?W)x9(jM=@SSEa* zVnKJ1M**kN1K4#>iBU!KfbMu-ZAec%oj;hF#!&|Rlwl!>ma?y4qb!%@u9&EimO)qp zo6xl-9nwki0f6*HH^Iq)r;U{0S7PK{2jzu(QF%*JJ&@8N3$YRep!a%qehJP(TFiID zN8L4-Mtimj$`uCat>awaC~>nd*rw6bh;%!Sep9?WF}Rt4g|aX}a%C-z-XV$rPt@BH z)B+$p9dPV@3)Go83QFQ^nYeYAR1J8t5X|IIAcl81(OB_u>=YKn6%a2s(|Mr~hC%&t zcYF$Pjcq)|>S$Y^hK4)QSx3KPK4r1a7BSlZ}aqOH8!##uV z;v%e_5~2$9NXAZ`P0nkxKir@C9;oRaEkNH~V6TRQuK%GaQdp3WxxxYRaDH)}v3JWm zcFbc|Oe}NHf!=k-RaaNr$fvx0n#mU}TJsu~)0~FN-FLLS;#c=_EwWo)dY(V=U^z(B zxn$)YQWE_tE^|`!D=OLNkT4COcSt;=H*)Gj{piTphZ3ppXZ*r9AP0p1TCuXW_0Kon z9EIe(-atj{-6b*q`Cz;*cTnC!TZ!CZHt_0-2Ooe+`>QWgsYg4 zOZ_*=lScIy%i7Lzy`zk%Gp!C5S$qr1dAa_K=0geomLqcCYAr7AiT0h7FX`|XP~m<1t5NKm;mgJ)*UsW5CzTs7<1P*Y|3*tWk^yIjeqB4zRU16i{>d()NasfKLF<-XcOqU^P4H(VBG zPW)5;ZJ1SRDpBKrU9o}Zhsq5ppl#3Zzd1Uw(d-~`rW$Wj0l}C4@X+&{{#v}PWo^$Y z>+c^~bNlNj<7=7v_`DLSvbLD#@gLfpIL#$D?;jW8*qW`4Oq3M&M8Eq9m#93bZo9J~ zQ=h+Nu9a((nSUsFfrxp8L5(eDL6Ed!f|j_X=rbnq6!Skyx1y8$^M^xfe>=jv=D=XA z+aB7fGa*K!wY@sAYBw`7kgA!B|L3+AsAxRBvgWYt-+l#QeLeF!cyw(QdiBlOrZ93O z>hew}=||If+wNXj4$b#t6^5VU$SvNpJ76x+%6=XETB-VE&61L=>|br<(Qk`?YWnF< zHoEVPMb+#(b=EB4z3j}3n;k_@Lph58aSvMcccp8#S9f_t91_P3N1o5tjoqju;Fu`O zwAMYMU*9%oQhHThU^*m1a4|{l5Tjtkyu0u45wH5T*S`xLk>OX=JE&5RhA*ZIyEj4z zl?yn%ChWtBf$q1*c7;qmXZC~~Yh+$a%-qUdtPXoEAWi9x{+0X_r>7?d?h8t6AC`0| zIl`7~TxfDov9FA~GUTfAeA60J6d(aIeIciTli&+J;r7`jlaIJX44Tn*F1y76QW0f? zj0#FJE1!#?sfhCp9rVe>!wi9a99$g;dx;l3y+ik$EQ1iO$2p}?<6+oHP9=ae1T@U# zx2alh142eXry|U)ngCyl2AL}ewm-u+kXsl!NCK&ImmB!*&;VPpXO5Z)Y@eMOZEvcR zoi%hk7OT<;w~!q$mnq214O2XPn8!~NCL@3sIlurc9O8IC0ZE+EwaTqp;|<{=MM{76 z!i0GgpyGUpc`0|j7%81YZy)#JR&#+)fZJ zO!njDG4@J8N(p`tKcMq0LuElXd!1kM0aV16aB=4$#{KA>(Wfk`3l&f;*&r15>0o{R z?06SyJsTiRyY_k=<-ntfm;&fLNWo<@K#)YVXE5>02M^3b?5;>2A)PA3y9VGMk3oUU z<_p7TF)FN=4-4hi>|7)0k)XF;{UiR)eK|{x99qv}6?&cNwtbHV8<6V4^wkcdv3(_Serzf=zjE6rnz*f(>r40gBZG2$dapJkaQZj#-QtHg z&kUSGWBbgy`R?LpHy}s=TnL{;>L&*p!zP-LX_63S$-ddk_W{5WBV)tyVVe(hYa=(n zeYp!+F(B{!em3X^&W9bpJTK6CNPYJ!m6FL@dIMueljR z?%VJ>y9N%5&i0Ie8Rb81y;?FNxxbDB^OWCz(V3?LH>rya_&FGr1-SDq5N5UaMn4EN z-ixQML1tcQKXfLulhz!CizeBdQ#? zaZquiZsSH~0A+UP4n=hGTxRTTx{3Q|K|Z& z0_dT*JOhQJ8M@qb_`6-ws0~P>Z&uk^LobVrifax#7OV9OWHs#8g+`TnPQMU^@~?Hr zO;E#O;$N?h3J%ubDIr>Web22DF~Yn;IU;g$5_X(M@PyZ=B=psY)oscNk!LubJ?p$R zQidY1L$@-%j;LbxPCSVkcSceWpn*#|wOdLZ^%jXUC7Ng1x&z*rrQ`uVF7qt!@^aL@(rs*}ki83D9 zf6{~VD0lP7I%}L|92hO1a*94avCm(9w7TtbVCT4me05FyX3D9^6}ZeYC0GYJr?A?1j`t84 zG>Y71H$){a?BO+exUC{3c}QS0GCJxRX~T-favuwE=&wB%0`u0FY_g9#JZ(KYE8?E& z{y8fp%s8n$=?NX&pR11-ph)ol?tQa5h`ejio>%RvsDr_x+NW%J-uaUus(;NV)G8S- z*?&n{GhYE7c1H2dNTd-GI|>{TWhKLzRdMP^wY7qiTO4+~{4GmZ=g5i{u;4UDr9Ui5 zQrMkC%J)XiMo4gMdLY5^FlcJON&c}1VG@fYa!KZtmzy9L59gTL^zaHj(%Kuv8)j)xFH>a{sU^9Z0T`H*v zJRu=bB+vhFu|CgidEfyLOvDhL&sPmh^S}rXz1%zoN2-R|Yx=m~A1ol3t=t z#cl+a{CR{O2+9#5O9ka1$JeusX08`%94#@_i2Y?5RP;op+vD`bkAP zP-Jm=4{PU5do?LpKZ$S48(u%_UFH|lO3IC(cS#+5yqUa5_rSrB(ETvI{7HO*=_Qz- zNXf$R=16ys@sjk@o4g9 znG*4b2jAe@N3v`Ni^7Np6#1@+SYCLl6&9_&J|Uc_prR;aF!~@`eHTX)|HAg{ZGvuc z+y9z0YH(;e4#{1W2u$>eey1)qn0z7r+A^ z?>Tr11-b#5DRe+wbkHyh@Fn`eA$EN%zGwK>)rUI{b++mQIwjfaFM4ZhOxL>+hifH* zx{=GoLx$UggE$2y2Ts)=3A`3gbo$0-Pg~z~Jgho&>%h;K|1Yl_UjD%+0wkxgsV#$Y z<9|)|$!js+il0jgz%ET37S>9BK$Iwj0iPWZSyEKjy4=U@L=(U_s){hI!p>Bv3ecVsvti!G4WZ@4wXNpbCTG zi|BmoC;P$lGq%+(tEd8IQafi$n#*n?pdeh^cA!0F;rcJL@CMgSWw-it8noiUz5~)% zVSe}ys;I%R?K(cAJHA)9VQhMm9@>8B?_L!XZW|QQ3x;1?6V7jNylUB@F7QF%oYz}=ERw6rEALkl$|uzbwuavr5d|@4 zFREz=oCA8D-uneWysYk~JW2beuQ&`1fFXS?s&coOaVPLy`xfXzH#Tz@`={yG-4U|q z;ubQ8EUmXI+$xnv=y2^RyO!K;!*9omI-^$}eZkvlj`rJN45^nksnv@wI~U}~$;#!N z)Onw-LIoxu38%&`5Zu9-g+if-t)nXK&~XN01tSnyJIA{quJ}TmD{6cPQ->GRgyw)y z=l0$zpXRleh7FDq`h{q;~tDe zUB?M(&q~_2odDCk$LMSc4lqXaBx@sk&G`6n(jW;;8dlhaAm;j>c_lSqA>4m4i4B#; z=dZaJqpifvUNw=A_wZN-J4f&=l{r5zwoJKt(~plb15Az-#+b{SArKBvpCGPS=> zCsUsnzmo~c(OV4F3|D#}3cj7{T5=pgJM zyCfHcUEXLo$l>F$5g^Q{g%_-y>gSxHlIpXdndlEb zFi6_WclHb7;K!4;pwp7^RyQIJsg)3#tSSsn(rH-^oDL|}jNsULdLko!KW;aw1vzjt zq18vmo{p=+hBc;3t+_wv#IT?vnf5b`#FGP zdh#eDa$2aZ{xq%cv}T!m$|g3^JK@|ir_9$n+5^##35Z^MAO}}T=CDpV7BpmDSBKpu ziv5>aJyh`LZ=Kc84jRE`aQ(i}%;OEI)niH?$YDe-IQs<15>^XRNH0$=GD~D20#q`3S-_^=x$+ zf)1BSy=WpJ)F{_bA&764;(HeBx=(FTk7Z9I)PlL`ke{UajLgx6_Egd%&crjo4twhy!9o?ae*naJMqXy!#~l6Cg6>tyO20)_W%E$Imr#ZU3q&{aH4 zo_SHC3!bt0XDtSMusMsL`_IU)1lTGkbbs*qAPpXIzHsp&!>h4cyR?;s%cbVqXw%5P zMAC`Zq7|*+zUEkSQUE*s#P?a!FhaA0>#Pk1&zZ^(cOdhsc7U}p=kNXDynNz|{KZ?X zhOVJ7g26&D_%=Ahk_sirO$@3aZY?GSi^z$8Y=$m~Di0Ar`aABp6;Kl4UonlwftAlw4m>S?n4uF0D! z-<-R!e^fPDjk@anNI?>Nm2II#c`MGpa6uLZPvaXy z${!kfv|C{Q$lYNR4{`(p80vNM^3PmrG`Tk;j(6TsaS%!u$p(6~(3)vu1(bEM{&$d7 z3J^sa_gEBAPVBDKevy1K4W7!B8^<~pG@dY}8unZsqQ`{iT=C2u)ET^*eFd}``3To0 z_{#`BG70ven~7Y-!AX5(mh3jwq1ocP@H)NZSP;6Gq9NhNw8^6z18`aShr_M*K0;%;)d85*tLo5&qL?9`kY;Q`bsnE9Fybk z?CjD592`n92dGoPK}q3^&l4)|rFi2nH4(ytB5zA(a!b zy|q%V?e#33;qk}v={ugv+gEv+b>H;3i;1q;wk2tvM>!A2lzUniVly6zq+cF0rXY(<|JJom~wmdN~!mF>JIxXN_CG@KcCtYiGOe%L3k08P`3mQi0=impwXt(OC8+ zV!Z{Ex9MOV_TXap>mdQ`g7A=Qy{r{N>oW4{4H~O$08-pdBJp7_7@!Pkn5E{4X35ioK$GP`=QQ<#&BHnlJqH+=VM1HtlWi{ zHJKBFA+oB&wMVpX9R~JlYD{wYt094na<%jW>k|?g&$?zB_%^X>Vj^d*8VfjRi1A@F zl(^+!z{6x#QKq8?ksuF5>VqbEWD=QPfK}8?2kTPa&eZ4&@OXoa2`O)>fO;YWPrDy- zn9j4aJZj`CjYT2%a?W0?H{v<+CHg6d14xpG6L^k3wq^~nj2v&A84i6^ao#Z?F;x3Z z$fe@Z7|GD`jO%%w@{gc4aVm5Eqep&U#erF6-a9{9i>&uV_gTzfGyR>?wWjK1#`6Ka zrn0*y{Chqw$Q%`he-G#8flbTE1*0-JsZ5DdI=Q>jd4t1j6}N$wt4m(U{qy2AfvpV}1XI*vZJ~JnK{ZIp z`7tb(II`TQy)1z09OB7bGEH~Q##_h%#=BSb>r9j#w)sMfR=ay~XwcB+kSsnt<#+n# zCktjGCZ*?^yp(B|{4Y>*m9~Z6?>>{Bt)1^&oz>)UE-{w)Bd2gEF}PDdO+BTKvS4(9 zHwmht*5SFl?ZKC`(Y9hd51 z34VA+KTgWXcCgZ{v#r|QrI+lLS*2U|hg+{+&K{pftFz&(6F(FQ7mSZA=km*5_SnM< zGN}#5dP>CZtPh$(CTH15qXGMb|s!J>eQsTYPlSkOU8ThM~f)`Gw$r*7YpW!#% z)M;2l9><|e=cl#Q+>+r72j>gB@{yAvtXbg|DP<;~!_GOOA2#qT;C z*{rk%HrO!VBzC$4%K~W+b6iK{v38$ZacJeky6B1(s=DJg@!h{|*pd%akyi5cP>G8T z%=)Y*i{lW%+G+Q+E<)*0E!|YSfyz1~(STedcPVuidVc)T&ty}V#<#Oe8{|i(^CmzD zVVMy;EiHYo^e>KjM#oW%7SwLNcH>(h$ zgk8#SeQr(1?hD?@aK%vs5e?yOKJFZP$)kS!;h3=Qk<{8Fb}ER4Uh zn4Kv6;@*89DMoIMsHw_jM6bJz*qo%{l$|hTavXE-$(K|7qyep`k|($&&&HQ$M!B@Ir#Z05Nvg|>~+7OgKb$Vg^sfWIHRY^+Ah!b z2hVg4IoqrhUVVmu=|lfeFQd}kX!d+mS4()bo)N#=ia7Z+9-*L|E?XRT2~x_0bl<}8 z_Fy^FWrznZ^aeI`8mPQs(j;pMu22O)-7XSt_lGxG^fIu>o)|%jhXmv{jqk2RLbHK{ zY72`+fR57Oml7&z5u{KP%xVMZq>1UV0fDG^9D22yX_Ee@xA&n7U7mrd+h-0Or<)mf zO_5DUemeKROq|SsItL#C83@;Nex+nx`%-9+czR;<@;-+h%lI@c)O#z8mXjx#C3T$N zDr;_55t0Lwf_7+hJ1hH4d>P_Ibr>cYZp4ST3!C49un$D*=25ZNsx5wL`UuYelG`#i zkFbvCkuMP}owz?!nKQVPKJR7V8cl6Gupgix3(eNYKi`(!A5M?zWD!H1O&-<$=W zcF~kO;vnwE+J93QkZLZ`cGG>Nx*T^F$8^!|W=CD7Bg^$JmG6SXpHqRHL({{S2pI&7 zL>EecP+dC~ZMZgJ?j}rCQAQdI@Q8FE6O{5~u#Xm*otOu>tSDG3-Ql*-sq?>;{dlXv zgVUNx71%8_hbl-J)8@(Fhrk2zs6D)lJ+}DAJJlpYTD&#vI5=J>PDw$d+iLsgD}>I@ zk!`#E&ymw4pBY;g9D$v=V&D^lcTM6svH#L-2KaFjy{$qA~jq@QhY}LtdlKePc ztT88XtZ%<0ot9jGLH6Q<5O>Y6=u9&&*S$3-Wp$CYcz`j!@8`{r1dbG`u#HhK_jV*I z)Sa8U2Ix&}Ed+tXz@j%JjZ>SkDGpq9W>xoPCjL;~RW*G7>a6w~6>ie^r&miv9-dFY zcZfFwk9~xiN%twi?I_Sm*J46AQ1OUP(0$wUuis6>CmzJ9JNgY5n!pcdNTi>y;G*?U zK^1V2`uSH+-8H$05VQaYUdTvo6hhs*U%7Ekz`ZgGhP*^%?)^x=XyBFID1fi7EfhqF zsJDcW*=dpciz&dIZbNK>NPS^4qwu&+6V`{n0J2QF6ovG^ z;1@>LTlvyYKx56Cv#Ng^>nrg6_uo3L&0ha}C(n;{lO*NhfL0z^0TuLFTb||dow~0z z{v=}Pqd^i=4JSPYEEt*2lpd0M!|}N(8IK#Y#_#$zuG#=7-N^F;rX~t7HF0rYo`Y#e)N6TC8&kM@DbKec3NPbuq(laCo|B;9D00+MuSY*eBXGs^ca?Q3LD0iB5dRo z)W3@}SQz6G)+)Y>mV0vE7pN`6Wl^q;i<pN}_aOHE4XrPlD0%$M^liG9->?8HqUJ6xrC1`)@XHls%& z`II`=azArk+mluy=9lj-fTa`O^o(KcUU-lqyBWF{i&otn0@E}A{@|Y(cp|8!t!?zi zE(1WyeuP~HN+ickMWHbAlJ3z;RJK=4aRe<;k%Sst1Npg81v8Oh#BT!2Igc`VX(_|< zXohH18*{$Kp4}{vd5yhKw@^73W!u^u#xG|d+`TA|p6f72Pf{uNHCv}L44W1tJijZ` zr0s4qjK~d|EU*YP`|QlTWyhX?;XL$_ADw4T=Vkr3DBcy`J=lTTRpw?Qi^Dsa z4dl!LxfGLJOO%~da<9@oaXZ%P+sLyCQ=L&NqwA_>SWqeaGw~xb|{h?IJXJ zES7vMFg}|vR!-}CVcK0vYGlejCX6%Vc}Uo;_ZcMLVOtu>l+2}iXwgF^v9pe7*PTuM zi{ag}@F9ZG`)ruNxIa&IX~v4$8_A8u3n>!z%ZucO#@D^AIKQADynR-Go~hcTCXv1} zk`?qsdbTxb;))Sm1|0+AgY5$XgYA2x^4(kBCaAPXHASi~e6;MGieX$Yr82WRzB)X_ zOnFk;-pjHK1={OLU5>%V{`FeKvHUwErjPFnF6(FDG)vYCetsN7*(>>ZggqNUz zsFl(!(3Xjw8q#MCzmLlFnwgFxa|H7*^HiF#N^SE5_+mAUc#Fs1$NJj$x;hp;CU!(n zoasXBlpxd12(%SYJ)jdJp6(`aMUX*p*S+KMvJUxmL?&x;L*BtlnTCBnAVKog zb$BZvaO)HH9Hr$8Ez|ZsHA|Yh3IF{MhpXRAI!DT!!;4j}t45`$<}i~&BgXPHDLu3x ze*p}`1p5&+iBlrp=$+z5h}lNoypD#!hj5unbe^?5;oS;uS{pW+JoOj%}+J3y9`1R%V0aNHx7XMp`0 zB5mLWs2-6TbsOY?cW4JPyaPz4VC`?en;UNWagP`C773aF381A_ta~JyM`gAp>C#qD z6Bugb$6`GeB3N%AjLzeqZbr@R+V_~o85~+`{**4`n?&V~^Yz{0yhd|A7b`s$+{g^W zl17@TZod678pSV|YkPtEkV{d@<_ry&ry@Rm0%Hf+$D#@CV&T@f3uv>l)M>LC9ScS_EX3ryYWZvgyAWH%x^)1`k`Y{B$j+3Hj2oN z+zbIRkm|&j8t?;V1O<>{(tt3k2E-WELM;I)Os<8hLgh)oUpDtW}jlyW$yx$i)EtHM-0K_@&S_>xZ}w zn3y3HbY$Y%2C^_jE!w?15A2FMz*ChDcxL~Ls_PE%Lsss^9+Qz_>CGuQC~J2Dd#=hF zJvTzqWsXY2-!Ja+r~BmjwNKsjyqzW8J*j6?LyFAEk32vA7P)sNiJS8T1o$0jgU6Ye zm$H|(@a5N&I?Z}|ree@b{lk(BI zILj!$(&7D-zdK8p#qh?^7LJ{^@99tl+RfP8R{N2HJDtgq>x#Rm`7~_22Pw#gh>!Ci z;kp@Wk%3(zCbhW&j(aVImnOyV*cx%&41)`fEA`JBDLgWy3-_72yp27WSmc zt@IEng6cC`G~?O;!FPD}XQS@MlQ|`G{34IZQA01nhOwao$zj{6oPq>1b8_ny*{~C- z3uiwNFLlT-dh><$%9%=KnP+U38gQN+#Lirh)@{a?^jTt~r;E}mH$KS`t#;0H|HDz# zOe&>vCAFZTmi|Souc>@O7pOfzfZQCB^B^7(hkE%|t3{`~1vQzVTX;cy1!PHYUHdTMYs%{@Q}1qgTSpXT?3F%;d+x)rW(`*Y3D>p7Ah2&1l6$DtWyb4VLO16bSkjzvZ<6MJY5K@ zYs`6ME62V*2wEj+kinW6BY^rYWT^W>1_ULNrN~3NW=$$@nf}L6+<`$*@<8YBa`W3V zyJxvb<+Bs#ftjdvSi@y7T60mffYcgt=KM3I&j>SPMIx&&bW3*#C&2+vTMQPstsY<$ zRZQi;T|^y~h9;fI7O$lnQ7*suJ8DiA zWQ?@Jc$@|eA)=y16hw&85B`D3KRQ}QnI}iJ-;B`8kNj@q;9_t7d0jgSc{lG>oySG#O&$z>?; zUU`m^2|$qBf8Sx%OSS zISS-RMu%Otd(HfcbT&|BCa9%6e`O@?C6wP>$#UN=hT156H&)h{Kzbcn*C#^kG=VLMFN3drpsAY8Ol4D0`>sSv5t_b!98YM3YYDe$MyZEb&JpH zf`re={|3irFt(i_ti0E2zWY~~PumQwsQeCm+;owBVI~Kw%GtKKs`1Ovo4r)`75b;1 zaNPt~kE18*GMy2Ybc^4BsN?VpedAl zY)mihAVXm3Tv&yZKn|bYDe8T`sY2FN>B52i!Xt9s&UvM(3x>^RcdPVy`U~5o?@31G z@HLqs^+LeVfcuaY#Qk<_-HLUt*h>J&p{z|Al1TMoxX%3%>?{_38*B-d53Bs87X9fi z^|y=`vA;kFvY;?XS$(mH!T^|!Zn%h?Vtp!5Tn)GC3R1qzdI5>XXc>*F)Y==Y|4Zrm zQ;hqM8uh2+mSS|^dJmzS8G-t>QAah)y0%n_xhWRHK)6``6yG_o6Hc;bAA*)n{)Lwh zx$7A0-V@WUp2*;(`|T(ek(`|x{NRZ1zf4!6RHtV=)1M7>kF^Hs%$MvPbm<0{WcLJ< zL$~5Jq~={e{4IR}2HW4_)fFc&u$}-iL0kIW98NsYqpofXzIEj`V)$j~v@8Dvv*DF^ zgtfO_nfZ3N*ipi|BB#%3v~S)fE`gQ3#*DJrO-5mTgxBXHr+e1e` zW!y_NSNO;)_8z~)D!`ZgX}cAdlX@&jXMIjp_B1&b#j#R-uTd~Q5b@I|FaVq{Rtvo~ z|9^jbAxv0bs9lIM6M)+1N#Q;k_dQb_|D!AxgMG4KD_)W@*9B7gNVa z%lD_yeqY%KfgnkE`3F!xFeMWgg$?r4hy!~vn60vQL*{l{xEK)P(t zjwN>Ka`)j({azrsVPe^G3}$cBK)x9^*N@DWb6Eai<7$f|tsXhZy<(08#o&Rk2x2~Q zZ9%;E>Wer?nnmYK?IX=6yVetuSYaF|aB(6sh0!QJ*>^%@iMHx#QXS_m5&Qmo)A>p* zLi$g9tO|uXCe>%^@-s9f(^0<(%a??seB2*L6s6jA=D&}a;ZGy*~PBpY@t>bbc{6G5bhOw($_L~-v=HGgt|=|-nA zl}H{0j~+Q+-}Y0E^amga|P)sY2{Y{FMeuLGL4gJx_VoKN8Taj4l09ejUmrm-pU23j3VQyvK-BV6?|&8vCK zQgNL`*SSuD?6hS{e-dk}{>!CS+LF58Tai1J`cI@$nIC6(ye^3gw`bnr#u-w>?0XoQ zYj@BZjY#&(3Fk%4Ww)b})Ozwiab#WrB~IL(4UDYeCWhm7CF=KltWR^MU4Nrrsk=;2 zDeCDna^}m-w!F&S;1Df4(wviP!xGkUFe)t}&g?;z;fk4czi-Y6Al5~-NN`kA;w5Si zXBt@eFU(t!yet0jm1Ugtf5J2zdnMK6v;5me z4y(MmIZzTI;hLv8a$)T3s%^au4+~^Hb6Dn;%R7}$rzhOUwHvn_H|%$~q9@4ke=|O2 z`o_o2r6#C8W2n^fjdjg}We}%9y0M=Ao0GHp?_J(xXcU+wd?{7;%U4}yhcI5w2{>+k ze0C|%!{H*OO|L}vHxlT)O|J6DmbyO}m#0&U;&OgVQ74*sGIH^D&2QInlpU(EFjXI@)blPcf4IcL?Z1<~nRha@c|AE@fR(<(Kw{t9)J?^W<}|20;T);5E)jKT5x!8kjHkZyiM@b03SQg0d04nn?} zTbf}z+BJmc@io9bO{*Ne^E!!>JL6VEqAtbUCw`~~r(4iyQ=eemk?Yet!j~qb$12IO ze~FD}7B|29i7F@qs_>vD8&ShLs_=tkzB2~Z$7o_<|BOac8lnL@!Nt|-mLdl zVJ+P-=^mEoooA=MO}OCnSz(4;9bV^?UpJu^l(-?egf8DpHHuyOge__{IcirUfmt!6Ze$SyuHbUbgv89+;EXg^E z^nf~=iFR20R$e)y(!x=?t6I%Cr%c4E<+Sd5`dzT^Ye~Ub$L0a6>W64`!4Zbs*ut0$ zi#FMhDY9wL)fJa&U&^tKV94KUvxMGCNz*8~hG^L%M^sOaTzlQ2CJSX#`RbL&GNUGn zxGX(&jpp-JAw^@wMLj@J_)CemYZmtD4a8U;%n#k3E&O!r>&mkVM?L6OQcy_j7(qSO z4g29dp8*-LpHv1KFLm=u*jtB#iHw1@@^eCIeRFyxD!;oa<7F&5{b}^FHOz9%YxmLO zUfD)GduAjPD2&%5(kN^y!xi0ut&*vUqRH!Jn%S#DF=4H4li!i%^Ok?92{Q zE2#9c30|$DJWnM=L68WBt3ta(M#|dc>tbwpl4Czamqc!`n`PulMjg+wu*v?j1$rwq zy_HzJG_NAl{YA>vkYoIVYII8WlnqdhlO3lF#LR!x-534pUgO!*UTZVkBE|4df68nq za%J>Zgl1lUd5Zbz-ew6ohIgn#Qw5dL<2asSU?JSduRZ}c(IXs+(Q&%Ysd6HG(3nv& z|3^ZNbLZ~ol~0#=1yv;+qyD^wet@y*d!o%u4Rv&R&h_cfrPU$_lo*IuV72^NbrcIf z-MGXkmS*%YL7n{I#O?>8ASFptb3xxWJZstPFi|2V^}Y|tHh#P}kE{Lu%f99RQ!#=^ z>o0$n|F_ymwsuI#_)-|(L}b9?g&x{c`N(Sh8hGyn^Ne<((@h%5Q|$Kf$^BS$x-Yt? zZ}tBw>b!kM9 z+_vZGvuET(-^Ks+#s9aWv+iu)A84|V_)Yr*)elcd7$@w6N_=2B=k|%+IjchBf7b7B zkBpz?BWSd3v{!%3HEWy|fLIUusvM`%KH&!uIpAk9J#b3K=gd@%K)rcMK6j`(LasR+TqGr1*y`ks8PMiz>0L0A_rm3K2l|F3K+5X>@J~ zB7upBqc1YwUm-QL0!etqUienxL$^}p!8gDB-HsUoa~knVTk+bE887K$YFgwdlNb-f zeh%hW)WHcVs;GDOY(ZK8Siu(~EEWQ=3E(~Ac{2%7?kZ?O*Jg0P&Yh0HM$@ zgYNP%0og7hekEAc?3RJtP0|l71T}$Q-TV&^HO@Z;rw*&V*II!R5!8tI-@?{yhekdF z2Id%aKF!ko8b=Y5#L87$8M3zz=OBI&p)7#&Svf~{;x_ER3UiBA0Rsi)&O=z^}CL02-*e>KnFT{XRF*>fHU+|@q&`g!2Zkx z2@e9yMuUs4OeD_k0~&sq*gJq%b#Z(~upsx*wgynIG8|NAKWvq6E5))5>Gu(41Q?PT z_1mrdTwTTteu(XG{ACYoJAkkwPY}V32N4Si8x`k6&(MNswjsi60<(E#w*^=u3kb4g zBCzzmuMYgOL5t+zM4ulvn@4a5{O5o1K~t;u9r*lGbe9p?r-19?2QinFuVC;tRx+jL z3vi;5H5p8S(vZ!Zknexg|2TYR0oO+%u#c-|;z1n7t5)Zq!Yrt#jM>3nW>Q}`bV*do z%`YB~CjJm70^jCQtfW zS1i)BvPXb~$MH}Ng-1yCEos1CbS0x(tcbq``_%O#5ilr|@zl%;7AtM*QWFtK?2_mJ zZkpZQpTs^R@!SgT)UmJcWeM&yP`x`Gm&A_OzLD{=&UWZp3TCvEI?$_q?~YC2%oMY( z1z?sUu5$$t=UyxP7wW6DP2*pPU4YAnw@bL{aHWksJoGM@fO z<>Ll~N48XOy};yw@>*9+=rf_mbm|^kO@qH=Y_&BCCW{C+`~Dz(FUuidD-W=Sqj)l| z4f{kIW(11cG`DWquX2Spu4|`HNkz%`?U3wAdtXcNVeGo9g1a$zX1{&BeS$q+v9F&r3M=Jp#`yV*s zryt1An+9CQ&Vp5fVa;VBv$cYPI(xwuGMB!Ic| zY?k^WllGX9b$y|@xtSqhSLY;*$|V@Nb;oC_ykRn@I(h78<087dSGJp=F~k)J9u4C! zz(vq#DI$6HQ`GnjgBC7E)e|$G=||r~^0AMO=;Z`*gwI@|7MnYE4P>5Xd`ZCS;ww

3JZ=HwB@2)Ev{zP}~4?NLi{q0w5r!Tc@Qkl|R_q+o6h3# zh4#k&VS4_eYSpQg*KpR`@SX75{*N3b0gIyB#lE7lnKSU?MP$|EeJXbg>3p_aJo`~4 zB7=Wso$;$y2e;7=_q9v7yk_!WwPa6+*%QnLP0=N8fw1E%o<0*Mae1LKzEpE$p7y)- z4yU^mDxT(=L~G5Aep#C3*C8$9pW3f7Tq7s?HK_9);ywGnFtlEicfla~?N{DY)57k% z52S%50p1-VArac`aBW*Ar^c<^+i>??S#iYsUl+FDo~wVeQXc&F)9VhnLI3l&>%&AZ z!w@wayzy2(6qcX~GCT=K4X-4|u;Tmw@RHY~h#8j@%KmhdZ7yc^90+$_GTs^y zDI#8G2*itiHuj$bNuosFW!BLzV~{C<5bpo+SbO--HIMK-{hFgM1-2ErrgG8FaKjDJ z_jl&pE_PAs3jfZ3tV=v1H;Np#7f^ zmo?5KNl1hRLcfD}G8@k>_b6GQB2yu|I$GYp6hZZA!h1}mdZc+kSdbs@xA2MMO(VEQ zlSXj(P8aZ-$b^)qv2**STs}HPS+Dq@gMiNrT=XeM+weD-rL3{w;TI3p2GO}Y$AU{| zU08!bv;#(- zdGo_PaDR};n@~#eDX5@~qkKAr(qQhY*M1$8O~h`!1SJlh5=?t;Iihw|$_Jy2-$BgqH`2p+y1WK<9_c%WRyoTngHs4p0Xno~uEfN2F7j-Y{0k zfT~1vo8C-Dg*8a4m|#ItxXn-}=o}2zctLh23n6V|ovW9CpdIW~XU-9o=SO>SW-S-L zzOu`5rq5_D{+qcg2E1dDQ~_TY^jG!ailFBH$ z2eHt|fGNq?!HM2x=|4++sHtYRAPz4pSGy&j)8)Ksyj&NO&J8-L9E3?~mR_ujinqP) z1kytv-@{q|VUM;_f0R3??PMgqM#fM1kZ@8dQt5I{#2=ArxcP&r29V&+=+%~(f7-#5 zk1ziSjFn8c;YMnKogRp{JTKaYqe*N z*@vGQwmRviRp2%UwodTjvO(5tr^bQlkj?H8p;g?S#iEa9 zs%}J-U%ftdkqlQXMm{L(Rlev`@Cj*Onh5tVB>c{~rN#fe;@b}1^@=U)+>(g#jhb$e%WW^+F*>y^Vu6#${4!RHTIFX*QKd-j6NL5ulDnZ``MuU zZGrh~=uof!eIKd}Y*xBanmAK+#peqjgxnC)F1|v$KThZeVkD1A;%oz@VKH-kdjf6f z;0&(r4@aZNL%aSU*tOOLB>+hv2c7yQAL^j71J<_U+Z+U9e=ERh(82p$qGPH3jqV0u z!RCNLbg`{@=<3jWh1AHG%6SEqI})gadke|4iF9uKJNL%qD~M`d&q%6%!(Wi!AX<4v zSyAU^*?K(@VMqc1g;)kiOEFTqz|1JZXGMIWa@aaQKDtW*iTN|yc9m@;o~?0R=NSGg zs=g-sD09B0?g_I&8gVyPgQlWlHB4@ep!AB)(oWl{ z7%>Zjys#tw;Ip7$eDnWSC;W0c2m5V+j90b}*uEby+DL|?c-3bL{_t^hb(Gk+sHHmi zqpp4wSwmlkL|}-1jd}QOWLZ#z`YX^+yDLk+i0eK%cSyZ570(}O0>pw|W>K0k~ z=L*}P1#)sz%!MAc7QIeQ?EaSh&L+>Ea-kCr42w0)hRgNvFVha~)t=D0knxtd+ME2f zWl22T&>1waZ9tB-hnas~UY&FeSObAQ0m2VLB<1RAnYI>OmZaz%%yft^d4@$s(4}lCCmvY_vs*rA#+^e@A9cVd+%Bdnf42*WR zjkoYC0%OWkPrthKJi%vJltyr-Ai!de8*7?jfst>g3~F=~@7+spjG|mc8;?b8RWallU7oW-e~m)UE#mYAQ8R)_)C^#%dOZzEm&ip`b@I!%5EoJ8 zruTpYyZHZfcjoa>?{EJvr8Fukla^Cdr(%>=NwP#DEw)*NA)#d7WlbWHQi)bu2s75g zXlx}RWvPs9EQzv=rHpmPa$j%M_niAazu)=ye%$A;>R4w!@Aq|Guj{&=FK5hJ1#a&? z=_obiu?<@X*~rHO>K#*PU5dgZw7R~8;L&_w=D-P_{JqP8pchIx zzn~X;m8zSnp=?7uEF?!9ux+t|O>Gw=%bLWt{!sv&)1ctfhbM!~H=f%%S@~%H#jiy* zfa7imUb`v4?9t&Y( zXcTB`)?gdt0+!RGo=67Zc7$p)u?u^jzi;x~4*@9)4T%Vj0t~c2k7b#;r=IRiMQbGJ zzCb2AXz*raxXviIR19N9xTz112n-@b$yUN{OLJbyaV{iWALK+*STG-exL~Vy=$-{R z-m$dIa>`Nb$CC|^gH&Wi-GFpPk9{N7$bfpZdvh)4>%QKvEKBA%sWzRGrS7i(Nx808 znDk@AP-u%W zOo9Y)0eHtQo8l-3|AVo=3b8SbpM!tBRz2n^&oO@k&L;X`xr8+&k)>IZYWv|ua5C-$ z3C&=4qN3JL-k~Q#pRPgn_lW<Tbu7HkY1%g>o z(iZ*xHnG2!Z7*4dcCY!M2AlqwD8)6z9*^w-i72%LNt<;B|@t?`wPLB z-d6k&A@ExE89}eOiFezhoWdT`eHg&o>n-{K3;(&4YS1MiXBBKNu!ZVFPBKc^-_btS~yocdR z?8DZ4J}`97YBoZao#YLCgnmHv@EzUmH+#P7%w`$~7GkEr$*qGT%V1@~28!f|RfJby z_x(9*d)e~lVX87omAnG&aTW==``B66w&~vPYarT+?vrTx#(VEkC(+Jd5DlLB=1?SCaaz z$#4$l;(IA6`nGrdagv$voC*ka?e(YEu(GB?2o_8;lZjv zqW?=$I!i^jo*B=BpI>zOZ!5>Y{jm$+Os8x15#N8^#bwJECTzPM^>N?7_Xn;5Cq5lk z^vwKH?-X!C{VroA#jr;3`$NK2Ci!oji5#C<_{iUKyW;uAR=)h!3Ue>EXbztVdx_GS z*}sa`!w8K~+gy@+TNfT*%l`)3#H`+74t8+qdrV5|fAcfb*wUcdpG5X4ZP{Ue0dVTy zdIn>fGZe>mE<^=Wgjz%#BH(89*Q$8T9^`ptGBcc8Mh2`~XCqJ(=EMnQbB{2LX`t?Rz}HDI^!(1A&kUu8U^q0>?hWJR+pP z3av&Xgs%bI0R)3Uwj(Ir=u6Q2=n6SSBV=Mvw2mACybMkLll@gt7i2~N+nigCH?{=3 zL682Jp$TNg%uF}Fs$c6!Fta574L)wtFAqoQ0^E`>K)A?p28M)un@RP=zN*6}(B^P8 ziDAR1Dz67KY^pagDKl!HLH^6CR6Eo zUwruuUg0n+6@U}v`UKumdh{@0p`QnAbT2EvCvF0}i!^lJtnNb*o~yW<+P>%x?lQ8fg@?3~(1k@5t0Batc%t5N*)v*jNWDL2Ky6N$#Y5ScE zgz|7t2xsYMU-OW}sV=;&UZWAN{En|9TSW+=ocL=xVO9VmvxX2dQoKqd=Qar&jhTQ{ zt`~QnuojS^19XgL;J2J3(FG}#7B5)!v;yr4mLs48AS;Q7@f=aAgKno0HW_9=oxILs zdvSh@gL$wfiqb*p19urFe8PrOR5Pz#xHTk&M$iCWa&=nI&c6I{0I*X$vh=L7IN!tw z({>+EwZde#Myi}5*bW-Zqx#I?$m!ZO<%D*+o$F^9)J2cG25(5L9+JO*G-ss9UY zWZt%k8dAmL!J~P5eG!&D`;q+AG-BQZ&Ymn0U`ILMoMRtL6C(fOA$2Oh&b&N@Mwsiq zenI3axeHi7iAw}tOZfe)A^L6iT$-W%qb2V6s>iafsr(gC4|U9ydGJ#6@LPyc)lVo0 zVhJh8iI3NrQjCAk@4P?^;JVu0)Aoubf zgNgB~%PwKj^yqt%94!32jWbl$Kp4lR2lL;aTiDO$j}aNIlc|>PiKVKODyWV1_Zg%9 zJlceY9v%aVB=N?4aysj1$R;9KAv$=uJHOuVU+|YX)F%Zs+qB>M&8)jeUf*Oa_CV*$ zvyd*9B{6iDlOe6nKW&iTzmVe1cuN_wcO&=qfM>m{@K22IgiFEIN`X|YQs-8uNcD=buOL7G>gr!3l+(^Qh`B&6EGt?-nth<=y$*%? z2D4+yFLz{7Vc^X#1c|30m{PA6>Q{N9D{p#_&Af*npM4nu0^|R;3$BV_!?3U6mk}to zKbTOc`}6+fJEKnih=hQPp7sUp@25?a;Y#MNlkhU|**)^FUvFk=R`iH}Kx&NfT$J?r zvI&mBN8h^MIb+#*f(m{a<3jW2)D*!T5Zly_Sa!_{bMr)D@Qi)s=rU8-Cg`s)+gUp~ z`-}UCqN8-Dh;*5(>Dzz|N>X#aVr<2m5x@{kW}VppaLI=AT!r-1@Yq1-!lR^Yt24Q9 zQd2-wYI&3?Qt8W^0xd)2;)?ve`oPm*B-`}BZylcnf;UnG01jq`6ewD4GJIv5i0mvI zaNV2Lc6^!uKOZebU-WXbOI4&C+Of5Lm;68Ag7q*umoIhaE#2rTtKmgL1eDD!+H`2c zHTE~>HnXGlU%Wfq-^^s*J%j4+?3sZ5^alq})z2aSZo0N~?K$wxvb$pI;PBiTOx2DB zuW93P^v{Ru#hEr6pB4Mjwrb2KtGIDdzf4L`6KER8=Aj-X66hZbTVIA~yZ3t(>@nxI zU0dH9Ml!S~AYagm`qc$dMMnuYmV+hCIvDeT9=S73XKSL^7@@u^7uBiOf7Y55Ga(%I zQV7auFS_OOv={R9YIt!$9|h@&w&f!7R`6mDhkxomfU3{VX;|pAi5NjlK~!l&?hcjy z&KGFky>sBk@PybaR7n3a0HFfF2!X}nKc6lL#Q03b17bq8K*4_F^{3&G{g{#W_}dEA zr@MiGYjHds-R|$!297YHRtBTqdNj=B#Tm*#YZM_(t{`ADB`js^gD>fXWfXmeH0qM& zr|!d43^9ZpmWoJS`j>&W#{Ka|pxF^$#zoaN4o0_az(%BR3y>HxU{A#DME5xv1xn*i zeBy1;Z~j3DKXo?4FWn7WLn#85ppvY=2WI#=rWc)VtX;F!Mr2HB7v z++qgNkH`qqIoFCu;lBUZG|W1PW`hgrqIuKqbhY6E-4A4IH>cvS%C9D^_xxG8tmUPQHAzJ7d2Aq6c$wR4J4MTZ!6 z?nO9J#>+b19H%M9IYr_5pZ)WZLxTOwBA$+OjlNzEDgUPq( z--?@|?hasx0zx}+vww2N!Wp{?IBJ87;I+UkJF{f2*7Ye?1mgxiv)Omfrx z+j^#czJ-cWXWV3gac_6vzkT`)xX;Hk365mLqnR6Gp2K?hxn&45Mf!jR3$(AtZ+g|pX=qKyo6 zwV;cq7Xt{`!#yd=zBjUPBfr+t#a;8@{g&*|18s#?(hC&|4PD=$e&kDKXxWXxRlAA0nAbq~(3O^@q$k1QgTX1>;c9p^4c5ZrB$czkm2yyKQxZ?aw#0pgA+a z*Xv8=8OV~+xj+^IRIS)+lQ7M-)>;92mXCq0DW5-27;Skg8Jhe{Lsfaac4i5@VwZ1i zUNNFDy)_s6CktZ6vd2Ja0j>&h^oh%=yBT3u#xgCy2Ox2t>+619*Op?`24j~_T!)mHx^C}?;5I-wf;s~ft;t>|iPc`L?VdLnwS`vJ`T}EERVfxFlEE%0IvU8nY z>iJr)PANH;Q;@D^zv$?FyGLYnEX0PRNK$Qyc@oy z;G~JH)(oN(cBh%XyNhh=>7vL3dS>oHgXz6)$#k6im@g9dDqYMg{7*D0> zcpVbPC=jH$FmRj8ex^>$+V%sf0#HJA?%c4dI~!LzjiMr(_%>Nj0eS;W|GQ}BXvwN= zrhYHl&dFS`P>vxkwz#?*?I)HTiSA73OcNY=Q6=xlp1W2=ThzAPmJo6h_aq!l5Y%WR zo9|B8Z3VY+e>+=1Zs<1ua`0Lms4WDnXzC%N9Fi$n&0p8}`(tXL{u_|Dkw*>Qt#s7j zwSpdgJ#!x&0fDs?Dd|JVjt-TM!}LgOsI$s{5UU%YBbvruE&0isu!4#+ zZLXRNl`;Ij3=d+yiL%GUwHx1_bdU60`2@{k;Xw*nMYGF8J49>M48$8oKDK9t;_s{v zJj#Qykg_`H?~1`=KZxh>6JR`m1*TEl`Z0jQ8cPt9%lOJNPqjw(#JX>$&m)NKx)pqhmcFFhS-5-f^x2iHcGfE~2gIYy#c z1Gc(*@1BtS34|EfW_7G~Oe3Yi>)Nw`IxI|fx}$Fd;AomP6a$iqg!PLFRcKHSn2+Cz zsz3p1fSEUQ*l!JSXTsbqT1hsbE+C}$tO2_OyS~j6Hv*E>Q8UFAoAo#AaQ|2CPiD{Z zj1UnYl0qGn%Dby=Ook*PV3 z-p6uNc(iW@O1_G>*Sc{9(pL}KVQ0D~nmuh+X%OO2R4olZn!Kx=EkABEQ81nuJ0Ch1 zXng*N(XeH4j{k>>_QyZ7YyKDft0a_~Flk5+o3*2Y-@ri!xa-N_(EGbpjqLR1el@|x z|Cj$bHyRaU0l_T%(IX4hkVv~aD+$eLpRHTBo&lXHU#hf&F*%n!E1s4C&a^e$S=*PJ zV6UT25QMp|g729q)WRNjQ(g7h{b~Kb6k~_GA|;XOr>@|193=#?3U-W1zj7+~*Cu0= zUD0mhP96sd!Qt+)wiLd>9`D(Uf1arHlhwjM4mou2#r&#|565V94geSb!W&(Mp&h#E z(U141IlHNhrX<0%^zV0Q@F*OZFfpyookhC-5uEyIsOB!B4%y@VQTJ2k3Go4t^eq}0 z|NR`6=_Cl1;Jz%42om1aPtk-U+q130l04?UU75y~um!TM721afSR6e~Z-jdG?X)`} zw(DV2dLTQXWy(}!c1mg9yeG*z7!!gaL_{577j?MSl0C-=7fE0nIuB4lR3W(?#Or!<#oK-OIgYudh1SZUx)Z19zH!<9~2T?Ka34RTW zbRU+?%i$h7k7CASp?kQAA-J>3Q`od=1e%8X=mH*qg*I%X>0u*OcT*uCpeR++ATdQx z8hrnE72KC##J;!k&NJy&&(AOs$g)0_z#dCbn0!}qYXeJuc!M|<2hXg81@dvRf{lCy zNrhYQ$d%I&~wF7n~$<;KTrX*n6e!88IVL?C3u2c0hkO)}M_h2QiY1?tYhTyY* z1MYB5LfMxWi8H>Z#I-8 zd$o;HYk4^HNh&t`4^4pt)V(p}bRz6$S0jcS&e@p>HD?1%qd)kldSGJiP}?2Ox{lYS z6v>($pqq1GB5;N+@#qKQfbaaX3O0PX$R!}$5l|{;;E6^I^3Kd0r!b^b(DJI@bQtuSTqrO!V2d8$Ne zPLZK|_g2y}DzgvrMVJ@t-56R@nqWs9Z+7Kq#^~GdQ@m+p{MF zoY31Pi)#*g4Zhpn&lQy|z3F^7R<5NtIP~bD)^ds1h<5Y#*gte=ARSM&52|=oEcddN zD3(hq*DYL0Y^`=zd{ie8>8+S~>PW)O<9bn~4+g>zIp{=T z(a%;Ga9t9m+|{Sze;N8aPHfDekSFeeP*x3IGpW2jS**8)tv%mZEFujpSKQXbA5FB| zvr_JIbJf=S245><1cHWB&=sMhEGJ;W*T1}Y;?VeOF9kyt4VFsev`%Yzo zZ#jT)P4V**;^IuXIgJY8KZBi`dT;3@RU&FospU(BLZcbtEqxhQrdN(h>mjV_N>mBWg9?E@87=^D!3aZQV*c z{8JzmPo?h1^HGS4F}e5J@D%(zM13aB??x|KThy%>YZ1>MS8{L4Po;Ssz0w4}GX`6G zfc9gcmheTBM#6qa9dxhp(a*3*Lt2ugsqx%>c?@loe`@I9goYmVln>5yIM7;4B_TZP z@_x=(u2LBaD*g5W+EVAu!{bZW>dCBEuQS(P(E;e0KBY4N(hLbo&FJ{du4a3)QfN&w zpyr(^QT^d;#rxHL0;547uj}&+!g*s&)m=i?vo0NzwQ(tZC+uCI;dJZGD2ef+GAlA< zN~|raJ=9*?I63lmWL&{Ze0Zu`m=i0o>#-hF>Y^bl^Ay+IsQDPXG5oQUXPD4s0Ef;L zW!%}cQE{YuOD*P`kf%8NW9fv-!w{w2;b=@Q!ubjgaugLuYQ7|AjC@h!kj zeuFaOnYUrbz4k|IGK=ftj4^?+AN zAf6qgNHp$6@?y#Ex zY~OeKPNgfc^Wdi`wEW$tqb>>&jGr%O+beF>uEG3YwOJm#-g*Kce`rLLy?o?^W%IKc)c;NYcza)dF9OTcsz~9Lg??NCh7)9%EU{h@efE<=WMmpAg5s-V zz0Na+n}{LE5wWXB!FeI;YV}X7FV1Oce8{M!2yYZ+HA8g>F=I#%DXatILwUnZPD{Op z3`K@X3@YU`V;bLi3Y&1qw#OeHD~k`<+tAT`@6)?cy7p(V3p#(9>s_@cNvb?g$4s9dE$4IghNM@^!6Seg;}dmZhfJz!mtzJ%V>>`#!JT}%!g{P zqh6P=mnb9(POTOC0yIN85+P=-I!ri2TZ5nG+%ojX&?V3^%@dePY0Nm|6B>u#SR zXua5>#Za9LI~g!|l@NBA6=Z$rqWc&3-Rf_p0&6WlDJD&jT7`NRf4y%Ja4k;#$7D(A zO7Fr$m9W&%QLy6a$G~c%`A!CM@=@W#%M!2P7kmTh=)&QWAHOno-($l2bw0ME$)}Vf zn?Kek4Hq5v&ybI=!eNO5OZ+o%vc&qNLc{Cocc&pVH3ER%U=4N9bkh0NuIA-otBjNN zR+)NhrMu0Cf0%4xn$@v1<&w(RA8+dCEV_j6D4kR= zSeD>}-`X%GeWl!wQB8wZQtDp9q;QRZQX4fqD^1-;S>l`*#e!hh!(V9BtU5Dx03w22 ze7Qw3RU#_s6?9!)Owh)crEG@>w)-l*%QE@P%PdjQy%|jy$zG;ilWQ z{q-GPrxk~@%nrtv*K2alYraYG0zz?`PA!lk(FF-(p}PMn|4;?*_Yy~jY58GWs*V{-lr!ZbGyUS-L(Sb zNx=jAn;3L8=9|$a-)y)1BEH;yJh6MESp4DvF(*P Cx|s?9 literal 123556 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 W^YItB1|0(b$Vn+m=H7bv^#1{To)|*_