style: fix inconsistent project formatting

Closes #138
This commit is contained in:
christopher-buss 2024-10-12 20:28:41 +01:00
parent f82318c642
commit e81b572480
45 changed files with 2867 additions and 2855 deletions

View file

@ -17,4 +17,4 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
# CLI arguments
args: ./src
args: --check ./src

View file

@ -1,4 +1,3 @@
local jecs = require("@jecs")
local mirror = require("../mirror/init")
@ -39,36 +38,36 @@ do
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
ecs:set(entity, D2, { value = true })
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
ecs:set(entity, D3, { value = true })
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
ecs:set(entity, D4, { value = true })
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
ecs:set(entity, D5, { value = true })
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
ecs:set(entity, D6, { value = true })
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
ecs:set(entity, D7, { value = true })
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
ecs:set(entity, D8, { value = true })
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
ecs:set(entity, D1, { value = true })
end
archetypes[combination] = true
end
@ -117,36 +116,36 @@ do
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
ecs:set(entity, D2, { value = true })
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
ecs:set(entity, D3, { value = true })
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
ecs:set(entity, D4, { value = true })
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
ecs:set(entity, D5, { value = true })
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
ecs:set(entity, D6, { value = true })
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
ecs:set(entity, D7, { value = true })
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
ecs:set(entity, D8, { value = true })
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
ecs:set(entity, D1, { value = true })
end
archetypes[combination] = true
end

View file

@ -8,11 +8,12 @@ local function TITLE(s: string)
print(testkit.color.white(s))
end
local N = 2^17
local N = 2 ^ 17
local pair = jecs.pair
do TITLE "create"
do
TITLE("create")
local world = jecs.World.new()
BENCH("entity", function()
@ -29,10 +30,10 @@ do TITLE "create"
jecs.pair(A, B)
end
end)
end
do TITLE "set"
do
TITLE("set")
local world = jecs.World.new()
local A = world:entity()
@ -65,7 +66,8 @@ end
-- we have a separate benchmark for relationships.
-- this is due to that relationships have a very high id compared to normal
-- components, which cause them to get added into the hashmap portion.
do TITLE "set relationship"
do
TITLE("set relationship")
local world = jecs.World.new()
local A = world:entity()
@ -98,7 +100,8 @@ do TITLE "set relationship"
end)
end
do TITLE "get"
do
TITLE("get")
local world = jecs.World.new()
local A = world:component()
@ -140,7 +143,8 @@ do TITLE "get"
end)
end
do TITLE "target"
do
TITLE("target")
BENCH("1st target", function()
local world = jecs.World.new()
@ -164,14 +168,14 @@ do TITLE "target"
world:target(entities[i], A, 0)
end
end)
end
--- this benchmark is used to view how fragmentation affects query performance
--- we use this by determining how many entities should fit per arcehtype, instead
--- of creating x amount of archetypes. this would scale better with any amount of
--- entities.
do TITLE(`query {N} entities`)
do
TITLE(`query {N} entities`)
local function view_bench(n: number)
BENCH(`{n} entities per archetype`, function()
@ -194,7 +198,6 @@ do TITLE(`query {N} entities`)
end
end
START()
for id in world:query(A, B, C, D) do
end
@ -219,7 +222,6 @@ do TITLE(`query {N} entities`)
end
end
START()
for _, archetype in world:query(A, B, C, D):archetypes() do
local columns, records = archetype.columns, archetype.records
@ -235,7 +237,6 @@ do TITLE(`query {N} entities`)
end
for i = 13, 0, -1 do
view_bench(2^i)
view_bench(2 ^ i)
end
end

View file

@ -86,36 +86,36 @@ do
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
ecs:set(entity, D2, { value = true })
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
ecs:set(entity, D3, { value = true })
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
ecs:set(entity, D4, { value = true })
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
ecs:set(entity, D5, { value = true })
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
ecs:set(entity, D6, { value = true })
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
ecs:set(entity, D7, { value = true })
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
ecs:set(entity, D8, { value = true })
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
ecs:set(entity, D1, { value = true })
end
archetypes[combination] = true
end
@ -202,36 +202,36 @@ do
if flip() then
combination ..= "B"
ecs:set(entity, D2, {value = true})
ecs:set(entity, D2, { value = true })
end
if flip() then
combination ..= "C"
ecs:set(entity, D3, {value = true})
ecs:set(entity, D3, { value = true })
end
if flip() then
combination ..= "D"
ecs:set(entity, D4, {value = true})
ecs:set(entity, D4, { value = true })
end
if flip() then
combination ..= "E"
ecs:set(entity, D5, {value = true})
ecs:set(entity, D5, { value = true })
end
if flip() then
combination ..= "F"
ecs:set(entity, D6, {value = true})
ecs:set(entity, D6, { value = true })
end
if flip() then
combination ..= "G"
ecs:set(entity, D7, {value = true})
ecs:set(entity, D7, { value = true })
end
if flip() then
combination ..= "H"
ecs:set(entity, D8, {value = true})
ecs:set(entity, D8, { value = true })
end
if #combination == 7 then
added += 1
ecs:set(entity, D1, {value = true})
ecs:set(entity, D1, { value = true })
end
archetypes[combination] = true
end

View file

@ -12,14 +12,13 @@ local ecs = jecs.World.new()
local A, B = Matter.component(), Matter.component()
local C, D = ecs:component(), ecs:component()
return {
ParameterGenerator = function()
local matter_entities = {}
local jecs_entities = {}
local entities = {
matter = matter_entities,
jecs = jecs_entities
jecs = jecs_entities,
}
for i = 1, 1000 do
table.insert(matter_entities, newWorld:spawn(A(), B()))
@ -29,20 +28,19 @@ return {
table.insert(jecs_entities, e)
end
return entities
end;
end,
Functions = {
Matter = function(_, entities)
for _, entity in entities.matter do
newWorld:despawn(entity)
end
end;
end,
Jecs = function(_, entities)
for _, entity in entities.jecs do
ecs:delete(entity)
end
end;
};
end,
},
}

View file

@ -3,8 +3,8 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.Matter)
local jecs = require(ReplicatedStorage.Lib)
local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib)
local newWorld = Matter.World.new()
local ecs = jecs.World.new()
local mirror = require(ReplicatedStorage.mirror)
@ -45,7 +45,6 @@ local E6 = mcs:entity()
local E7 = mcs:entity()
local E8 = mcs:entity()
local registry2 = ecr.registry()
return {
ParameterGenerator = function()
@ -56,7 +55,8 @@ return {
Matter = function()
local e = newWorld:spawn()
for i = 1, 5000 do
newWorld:insert(e,
newWorld:insert(
e,
A1({ value = true }),
A2({ value = true }),
A3({ value = true }),
@ -69,34 +69,31 @@ return {
end
end,
ECR = function()
local e = registry2.create()
for i = 1, 5000 do
registry2:set(e, B1, {value = false})
registry2:set(e, B2, {value = false})
registry2:set(e, B3, {value = false})
registry2:set(e, B4, {value = false})
registry2:set(e, B5, {value = false})
registry2:set(e, B6, {value = false})
registry2:set(e, B7, {value = false})
registry2:set(e, B8, {value = false})
registry2:set(e, B1, { value = false })
registry2:set(e, B2, { value = false })
registry2:set(e, B3, { value = false })
registry2:set(e, B4, { value = false })
registry2:set(e, B5, { value = false })
registry2:set(e, B6, { value = false })
registry2:set(e, B7, { value = false })
registry2:set(e, B8, { value = false })
end
end,
Jecs = function()
local e = ecs:entity()
for i = 1, 5000 do
ecs:set(e, C1, {value = false})
ecs:set(e, C2, {value = false})
ecs:set(e, C3, {value = false})
ecs:set(e, C4, {value = false})
ecs:set(e, C5, {value = false})
ecs:set(e, C6, {value = false})
ecs:set(e, C7, {value = false})
ecs:set(e, C8, {value = false})
ecs:set(e, C1, { value = false })
ecs:set(e, C2, { value = false })
ecs:set(e, C3, { value = false })
ecs:set(e, C4, { value = false })
ecs:set(e, C5, { value = false })
ecs:set(e, C6, { value = false })
ecs:set(e, C7, { value = false })
ecs:set(e, C8, { value = false })
end
end,
},

View file

@ -2,9 +2,9 @@
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local rgb = require(ReplicatedStorage.rgb)
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 rgb = require(ReplicatedStorage.rgb)
local newWorld = Matter.World.new()
local jecs = require(ReplicatedStorage.Lib)
@ -39,7 +39,6 @@ local D6 = ecs:component()
local D7 = ecs:component()
local D8 = ecs:component()
local E1 = mcs:entity()
local E2 = mcs:entity()
local E3 = mcs:entity()
@ -49,14 +48,13 @@ local E6 = mcs:entity()
local E7 = mcs:entity()
local E8 = mcs:entity()
local registry2 = ecr.registry()
local function flip()
return math.random() >= 0.25
end
local N = 2^16-2
local N = 2 ^ 16 - 2
local archetypes = {}
local hm = 0
@ -68,66 +66,63 @@ for i = 1, N do
local m = mcs:entity()
if flip() then
registry2:set(id, B1, {value = true})
ecs:set(entity, D1, { value = true})
newWorld:insert(n, A1({value = true}))
mcs:set(m, E1, { value = 2})
registry2:set(id, B1, { value = true })
ecs:set(entity, D1, { value = true })
newWorld:insert(n, A1({ value = true }))
mcs:set(m, E1, { value = 2 })
end
if flip() then
combination ..= "B"
registry2:set(id, B2, {value = true})
ecs:set(entity, D2, { value = true})
mcs:set(m, E2, { value = 2})
newWorld:insert(n, A2({value = true}))
registry2:set(id, B2, { value = true })
ecs:set(entity, D2, { value = true })
mcs:set(m, E2, { value = 2 })
newWorld:insert(n, A2({ value = true }))
end
if flip() then
combination ..= "C"
registry2:set(id, B3, {value = true})
ecs:set(entity, D3, { value = true})
mcs:set(m, E3, { value = 2})
newWorld:insert(n, A3({value = true}))
registry2:set(id, B3, { value = true })
ecs:set(entity, D3, { value = true })
mcs:set(m, E3, { value = 2 })
newWorld:insert(n, A3({ value = true }))
end
if flip() then
combination ..= "D"
registry2:set(id, B4, {value = true})
ecs:set(entity, D4, { value = true})
mcs:set(m, E4, { value = 2})
registry2:set(id, B4, { value = true })
ecs:set(entity, D4, { value = true })
mcs:set(m, E4, { value = 2 })
newWorld:insert(n, A4({value = true}))
newWorld:insert(n, A4({ value = true }))
end
if flip() then
combination ..= "E"
registry2:set(id, B5, {value = true})
ecs:set(entity, D5, { value = true})
mcs:set(m, E5, { value = 2})
registry2:set(id, B5, { value = true })
ecs:set(entity, D5, { value = true })
mcs:set(m, E5, { value = 2 })
newWorld:insert(n, A5({value = true}))
newWorld:insert(n, A5({ value = true }))
end
if flip() then
combination ..= "F"
registry2:set(id, B6, {value = true})
ecs:set(entity, D6, { value = true})
mcs:set(m, E6, { value = 2})
newWorld:insert(n, A6({value = true}))
registry2:set(id, B6, { value = true })
ecs:set(entity, D6, { value = true })
mcs:set(m, E6, { value = 2 })
newWorld:insert(n, A6({ value = true }))
end
if flip() then
combination ..= "G"
registry2:set(id, B7, {value = true})
ecs:set(entity, D7, { value = true})
mcs:set(m, E7, { value = 2})
newWorld:insert(n, A7({value = true}))
registry2:set(id, B7, { value = true })
ecs:set(entity, D7, { value = true })
mcs:set(m, E7, { value = 2 })
newWorld:insert(n, A7({ value = true }))
end
if flip() then
combination ..= "H"
registry2:set(id, B8, {value = true})
newWorld:insert(n, A8({value = true}))
ecs:set(entity, D8, { value = true})
mcs:set(m, E8, { value = 2})
registry2:set(id, B8, { value = true })
newWorld:insert(n, A8({ value = true }))
ecs:set(entity, D8, { value = true })
mcs:set(m, E8, { value = 2 })
end
if combination:find("BCDF") then
if not archetypes[combination] then
print(combination)

View file

@ -14,25 +14,25 @@ return {
local registry2 = ecr.registry()
return registry2
end;
end,
Functions = {
Matter = function()
for i = 1, 1000 do
newWorld:spawn()
end
end;
end,
ECR = function(_, registry2)
for i = 1, 1000 do
registry2.create()
end
end;
end,
Jecs = function()
for i = 1, 1000 do
ecs:entity()
end
end;
};
end,
},
}

View file

@ -14,7 +14,8 @@ if not RunService:IsClient() then
end
local Reliable: RemoteEvent = ReplicatedStorage:WaitForChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
local Unreliable: UnreliableRemoteEvent =
ReplicatedStorage:WaitForChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
local Invocations = 0
@ -32,19 +33,19 @@ local RecieveInstanceCursor = 0
type Entry = {
value: any,
next: Entry?
next: Entry?,
}
type Queue = {
head: Entry?,
tail: Entry?
tail: Entry?,
}
type BufferSave = {
Size: number,
Cursor: number,
Buffer: buffer,
Instances: {Instance}
Instances: { Instance },
}
local function Read(Bytes: number)
@ -58,7 +59,7 @@ local function Save(): BufferSave
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances
Instances = SendInstances,
}
end
@ -111,7 +112,7 @@ end
local function CreateQueue(): Queue
return {
head = nil,
tail = nil
tail = nil,
}
end
@ -128,7 +129,7 @@ end
local function Push(queue: Queue, value: any)
local entry: Entry = {
value = value,
next = nil
next = nil,
}
if queue.tail ~= nil then
@ -147,12 +148,12 @@ local Calls = table.create(256)
local Events: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Unreliable = table.create(256),
}
local Queue: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Unreliable = table.create(256),
}
Queue.Unreliable[0] = CreateQueue()
@ -220,7 +221,6 @@ function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: numbe
buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3)
end
local function StepReplication()
if SendCursor <= 0 then
return
@ -246,13 +246,13 @@ RunService.Heartbeat:Connect(function(DeltaTime: number)
end
end)
Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
RecieveCursor = 0
RecieveBuffer = Buffer
RecieveInstances = Instances
RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do
while RecieveCursor < Size do
-- Read BLOCK: 1 bytes
local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -262,13 +262,13 @@ Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
end
end)
Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: { Instance })
RecieveCursor = 0
RecieveBuffer = Buffer
RecieveInstances = Instances
RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do
while RecieveCursor < Size do
-- Read BLOCK: 1 bytes
local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -285,7 +285,7 @@ return {
Iter = function(): () -> (number, number, CFrame)
local index = 0
local queue = Queue.Unreliable[0]
return function (): (number, number, CFrame)
return function(): (number, number, CFrame)
index += 1
local arguments = Pop(queue)
if arguments ~= nil then
@ -297,7 +297,7 @@ return {
Next = function(): () -> (number, number, CFrame)
local index = 0
local queue = Queue.Unreliable[0]
return function (): (number, number, CFrame)
return function(): (number, number, CFrame)
index += 1
local arguments = Pop(queue)
if arguments ~= nil then
@ -305,13 +305,13 @@ return {
end
return
end
end
end,
},
SpawnMob = {
Iter = function(): () -> (number, number, CFrame, number)
local index = 0
local queue = Queue.Reliable[0]
return function (): (number, number, CFrame, number)
return function(): (number, number, CFrame, number)
index += 1
local arguments = Pop(queue)
if arguments ~= nil then
@ -323,7 +323,7 @@ return {
Next = function(): () -> (number, number, CFrame, number)
local index = 0
local queue = Queue.Reliable[0]
return function (): (number, number, CFrame, number)
return function(): (number, number, CFrame, number)
index += 1
local arguments = Pop(queue)
if arguments ~= nil then
@ -331,7 +331,6 @@ return {
end
return
end
end
end,
},
}

View file

@ -22,7 +22,8 @@ if not Reliable then
Reliable = RemoteEvent
end
local Unreliable: UnreliableRemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
local Unreliable: UnreliableRemoteEvent =
ReplicatedStorage:FindFirstChild("BLINK_UNRELIABLE_REMOTE") :: UnreliableRemoteEvent
if not Unreliable then
local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"
@ -46,19 +47,19 @@ local RecieveInstanceCursor = 0
type Entry = {
value: any,
next: Entry?
next: Entry?,
}
type Queue = {
head: Entry?,
tail: Entry?
tail: Entry?,
}
type BufferSave = {
Size: number,
Cursor: number,
Buffer: buffer,
Instances: {Instance}
Instances: { Instance },
}
local function Read(Bytes: number)
@ -72,7 +73,7 @@ local function Save(): BufferSave
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances
Instances = SendInstances,
}
end
@ -125,7 +126,7 @@ end
local function CreateQueue(): Queue
return {
head = nil,
tail = nil
tail = nil,
}
end
@ -142,7 +143,7 @@ end
local function Push(queue: Queue, value: any)
local entry: Entry = {
value = value,
next = nil
next = nil,
}
if queue.tail ~= nil then
@ -161,15 +162,14 @@ local Calls = table.create(256)
local Events: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Unreliable = table.create(256),
}
local Queue: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Unreliable = table.create(256),
}
function Types.ReadEVENT_UpdateTransform(): (number, CFrame)
-- Read BLOCK: 32 bytes
local BLOCK_START = Read(32)
@ -232,8 +232,7 @@ function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: numbe
buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3)
end
local PlayersMap: {[Player]: BufferSave} = {}
local PlayersMap: { [Player]: BufferSave } = {}
Players.PlayerRemoving:Connect(function(Player)
PlayersMap[Player] = nil
@ -258,26 +257,26 @@ end
RunService.Heartbeat:Connect(StepReplication)
Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance})
Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: { Instance })
RecieveCursor = 0
RecieveBuffer = Buffer
RecieveInstances = Instances
RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do
while RecieveCursor < Size do
-- Read BLOCK: 1 bytes
local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
end
end)
Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: {Instance})
Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instances: { Instance })
RecieveCursor = 0
RecieveBuffer = Buffer
RecieveInstances = Instances
RecieveInstanceCursor = 0
local Size = buffer.len(RecieveBuffer)
while (RecieveCursor < Size) do
while RecieveCursor < Size do
-- Read BLOCK: 1 bytes
local BLOCK_START = Read(1)
local Index = buffer.readu8(RecieveBuffer, BLOCK_START + 0)
@ -302,7 +301,7 @@ return {
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
Unreliable:FireAllClients(Buffer, SendInstances)
end,
FireList = function(List: {Player}, Value1: number, Value2: CFrame): ()
FireList = function(List: { Player }, Value1: number, Value2: CFrame): ()
Load()
Types.WriteEVENT_UpdateTransform(Value1, Value2)
local Buffer = buffer.create(SendCursor)
@ -342,7 +341,7 @@ return {
PlayersMap[Player] = Save()
end
end,
FireList = function(List: {Player}, Value1: number, Value2: CFrame, Value3: number): ()
FireList = function(List: { Player }, Value1: number, Value2: CFrame, Value3: number): ()
Load()
Types.WriteEVENT_SpawnMob(Value1, Value2, Value3)
local Buffer, Size, Instances = SendBuffer, SendCursor, SendInstances
@ -370,5 +369,4 @@ return {
end
end,
},
}

View file

@ -1,6 +1,6 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jabby = require(ReplicatedStorage.Packages.jabby)
local std = require(ReplicatedStorage.std)
local Scheduler = std.Scheduler

View file

@ -34,7 +34,7 @@ end
local bt = {
SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK,
RUNNING = RUNNING
RUNNING = RUNNING,
}
return bt

View file

@ -1,11 +1,17 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.World
type Tracker<T> = { track: (world: World, fn: (changes: {
type Tracker<T> = {
track: (
world: World,
fn: (
changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
}
type Entity<T = any> = number & { __nominal_type_dont_use: T }

View file

@ -1,7 +1,7 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)
local world = require(script.Parent.world)
type Entity<T=nil> = jecs.Entity<T>
type Entity<T = nil> = jecs.Entity<T>
local components: {
Character: Entity<Model>,
Mob: Entity,
@ -11,7 +11,8 @@ local components: {
Transform: Entity<CFrame>,
Velocity: Entity<number>,
Previous: Entity,
} = {
} =
{
Character = world:component(),
Mob = world:component(),
Model = world:component(),
@ -20,6 +21,6 @@ local components: {
Transform = world:component(),
Velocity = world:component(),
Previous = world:component(),
}
}
return table.freeze(components)

View file

@ -1,5 +1,5 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local singleton = world:entity()

View file

@ -6,7 +6,7 @@ type Handle = {
get: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle,
id: (self: Handle?) -> jecs.Entity
id: (self: Handle?) -> jecs.Entity,
}
local handle: (e: jecs.Entity) -> Handle

View file

@ -9,7 +9,7 @@ local function create_cache(hook)
local column = {}
self[component] = column
return column
end
end,
})
return function(world, component, fn)
@ -26,7 +26,7 @@ end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove)
OnRemove = create_cache(jecs.OnRemove),
}
return hooks

View file

@ -19,7 +19,7 @@ local std = {
world = world :: World,
pair = jecs.pair,
__ = jecs.w,
hooks = require(script.hooks)
hooks = require(script.hooks),
}
return std

View file

@ -1,5 +1,5 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local refs = {}
local function fini(key)

View file

@ -7,7 +7,7 @@ local pair = jecs.pair
local Name = jecs.Name
type World = jecs.World
type Entity<T=nil> = jecs.Entity<T>
type Entity<T = nil> = jecs.Entity<T>
type System = {
callback: (world: World) -> (),
@ -16,10 +16,9 @@ type System = {
type Systems = { System }
type Events = {
RenderStepped: Systems,
Heartbeat: Systems
Heartbeat: Systems,
}
export type Scheduler = {
@ -27,17 +26,17 @@ export type Scheduler = {
Disabled: Entity,
System: Entity<System>,
Phase: Entity,
DependsOn: Entity
DependsOn: Entity,
},
collect: {
under_event: (event: Entity) -> Systems,
all: () -> Events
all: () -> Events,
},
systems: {
begin: (events: Events) -> { [Entity]: thread },
new: (callback: (dt: number) -> (), phase: Entity) -> Entity
new: (callback: (dt: number) -> (), phase: Entity) -> Entity,
},
phases: {
@ -73,7 +72,9 @@ do
local function run()
local id = sys.id
local system_data = scheduler.system_data[id]
if system_data.paused then return end
if system_data.paused then
return
end
scheduler:mark_system_frame_start(id)
sys.callback(dt)
@ -87,7 +88,9 @@ do
local function begin(events: { Systems })
local threads = {}
for event, systems in events do
if not event then continue end
if not event then
continue
end
local event_name = tostring(event)
threads[event] = task.spawn(function()
while true do
@ -97,7 +100,9 @@ do
for _, s in systems do
sys = s
local didNotYield, why = xpcall(function()
for _ in run do break end
for _ in run do
break
end
end, debug.traceback)
if didNotYield then
@ -105,7 +110,8 @@ do
end
if string.find(why, "thread is not yieldable") then
panic("Not allowed to yield in the systems."
panic(
"Not allowed to yield in the systems."
.. "\n"
.. "System: "
.. debug.info(s.callback, "n")
@ -128,9 +134,9 @@ do
table.insert(systems, {
id = scheduler:register_system({
name = s.name,
phase = phase_name
phase = phase_name,
}),
callback = s.callback
callback = s.callback,
})
end
for after in world:query(Phase):with(pair(DependsOn, phase)) do
@ -205,10 +211,9 @@ do
name = "MyWorld",
world = world,
debug = Name,
entities = {}
entities = {},
})
jabby.public.updated = true
scheduler = jabby.scheduler.create("scheduler")
@ -221,7 +226,7 @@ do
RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation
PreAnimation = PreAnimation,
},
world = world,
@ -235,17 +240,17 @@ do
collect = {
under_event = scheduler_collect_systems_under_event,
all = scheduler_collect_systems_all
all = scheduler_collect_systems_all,
},
systems = {
new = scheduler_systems_new,
begin = begin
}
begin = begin,
},
}
end
end
return {
new = scheduler_new
new = scheduler_new,
}

View file

@ -59,11 +59,7 @@ local function spawnMobs()
local cf = CFrame.new(p)
local v = 5
local id = ref()
:set(Velocity, v)
:set(Transform, { new = cf })
:add(Mob)
.id()
local id = ref():set(Velocity, v):set(Transform, { new = cf }):add(Mob).id()
blink.SpawnMob.FireAll(id, cf, v)
end
@ -74,5 +70,4 @@ return function(scheduler: std.Scheduler)
local system_new = scheduler.systems.new
system_new(mobsMove, phases.Heartbeat)
system_new(spawnMobs, phases.Heartbeat)
end

View file

@ -17,10 +17,7 @@ local conn = {}
local function players()
for _, player in playersAdded do
world:set(
std.world:entity(),
cts.Transform
)
world:set(std.world:entity(), cts.Transform)
local e = ref(player.UserId):set(Player, player)
local characterAdd = player.CharacterAdded

View file

@ -22,7 +22,7 @@ end
local function syncTransforms()
for _, id, cf in blink.UpdateTransform.Iter() do
local e = ref("server-"..id)
local e = ref("server-" .. id)
local transform = e:get(cts.Transform)
if not transform then
continue

View file

@ -16,7 +16,7 @@ local function syncMobs()
part.Parent = model
model.Parent = workspace
ref("server-"..id)
ref("server-" .. id)
:set(cts.Transform, { new = cf, old = cf })
:set(cts.Velocity, vel)
:set(cts.Model, model)

View file

@ -23,14 +23,12 @@ print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
-- Overwrite the value of the Position component
world:set(bob, Position, Vector3.new(40, 50, 60))
local alice = world:entity()
-- Create another named entity
world:set(alice, Name, "Alice")
world:set(alice, Position, Vector3.new(10, 20, 30))
world:add(alice, Walking)
-- Remove tag
world:remove(alice, Walking)

View file

@ -22,26 +22,14 @@ do
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
end
function Vector3.__mul(left, right)
if typeof(right) == "number" then
return Vector3.new(
left.X * right,
left.Y * right,
left.Z * right
)
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
end
return Vector3.new(
left.X * right.X,
left.Y * right.Y,
left.Z * right.Z
)
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
@ -107,7 +95,6 @@ do
world:add(mercury, Planet)
world:set(mercury, Position, Vector3.one)
iterate(sun, Vector3.zero)
end

View file

@ -18,26 +18,14 @@ do
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
end
function Vector3.__mul(left, right)
if typeof(right) == "number" then
return Vector3.new(
left.X * right,
left.Y * right,
left.Z * right
)
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
end
return Vector3.new(
left.X * right.X,
left.Y * right.Y,
left.Z * right.Z
)
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)

View file

@ -2,11 +2,17 @@ local jecs = require("@jecs")
type World = jecs.WorldShim
type Tracker<T> = { track: (world: World, fn: (changes: {
type Tracker<T> = {
track: (
world: World,
fn: (
changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
}
local function diff(a, b)
@ -139,26 +145,14 @@ do
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
end
function Vector3.__mul(left, right)
if typeof(right) == "number" then
return Vector3.new(
left.X * right,
left.Y * right,
left.Z * right
)
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
end
return Vector3.new(
left.X * right.X,
left.Y * right.Y,
left.Z * right.Z
)
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)

View file

@ -5,13 +5,13 @@ local __ = jecs.Wildcard
local Name = jecs.Name
local world = jecs.World.new()
type Id<T=nil> = number & {__T: T}
type Id<T = nil> = number & { __T: T }
local Voxel = world:component() :: Id
local Position = world:component() :: Id<Vector3>
local Perception = world:component() :: Id<{
range: number,
fov: number,
dir: Vector3
dir: Vector3,
}>
local PrimaryPart = world:component() :: Id<Part>
@ -42,9 +42,7 @@ end
local map = {}
local grid = 50
local function add_to_voxel(source: number, position: Vector3,
prev_voxel_id: number?)
local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
local hash = position // grid
local voxel_id = map[hash]
if not voxel_id then
@ -93,9 +91,7 @@ local function perceive_enemies(dt: number)
continue
end
if is_in_fov(self_position, target_position,
self_perception.dir, self_perception.fov) then
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
local p = target_position
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
end
@ -106,7 +102,7 @@ local player = world:entity()
world:set(player, Perception, {
range = 100,
fov = 90,
dir = Vector3.new(1, 0, 0)
dir = Vector3.new(1, 0, 0),
})
world:set(player, Name, "LocalPlayer")
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
@ -117,11 +113,10 @@ local enemy = world:entity()
world:set(enemy, Name, "Enemy $1")
world:set(enemy, Position, Vector3.new(50, 0, 20))
add_to_voxel(player, primary_part.Position)
add_to_voxel(enemy, world)
local dt = 1/60
local dt = 1 / 60
reconcile_client_owned_assembly_to_voxel(dt)
update_camera_direction(dt)
perceive_enemies(dt)

View file

@ -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,8 +21,8 @@ type Archetype = {
},
types: Ty,
type: string | number,
entities: {number},
columns: {Column},
entities: { number },
columns: { Column },
records: {},
}
@ -31,12 +31,12 @@ type Record = {
row: number,
}
type EntityIndex = {[i24]: Record}
type ComponentIndex = {[i24]: ArchetypeMap}
type EntityIndex = { [i24]: Record }
type ComponentIndex = { [i24]: ArchetypeMap }
type ArchetypeRecord = number
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
type Archetypes = {[ArchetypeId]: Archetype}
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord }, size: number }
type Archetypes = { [ArchetypeId]: Archetype }
type ArchetypeDiff = {
added: Ty,
@ -134,7 +134,7 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
local archetypesMap = componentIndex[destinationId]
if not archetypesMap then
archetypesMap = {size = 0, sparse = {}}
archetypesMap = { size = 0, sparse = {} }
componentIndex[destinationId] = archetypesMap
end
@ -143,27 +143,27 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet
end
end
local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype
local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype
local ty = hash(types)
local id = world.nextArchetypeId + 1
world.nextArchetypeId = id
local length = #types
local columns = table.create(length) :: {any}
local columns = table.create(length) :: { any }
for index in types do
columns[index] = {}
end
local archetype = {
columns = columns;
edges = {};
entities = {};
id = id;
records = {};
type = ty;
types = types;
columns = columns,
edges = {},
entities = {},
id = id,
records = {},
type = ty,
types = types,
}
world.archetypeIndex[ty] = archetype
world.archetypes[id] = archetype
@ -178,17 +178,17 @@ local World = {}
World.__index = World
function World.new()
local self = setmetatable({
archetypeIndex = {};
archetypes = {};
componentIndex = {};
entityIndex = {};
archetypeIndex = {},
archetypes = {},
componentIndex = {},
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)
return self
end
@ -197,21 +197,21 @@ local function emit(world, eventDescription)
local event = eventDescription.event
table.insert(world.hooks[event], {
archetype = eventDescription.archetype;
ids = eventDescription.ids;
offset = eventDescription.offset;
otherArchetype = eventDescription.otherArchetype;
archetype = eventDescription.archetype,
ids = eventDescription.ids,
offset = eventDescription.offset,
otherArchetype = eventDescription.otherArchetype,
})
end
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
if #added > 0 then
emit(world, {
archetype = archetype;
event = ON_ADD;
ids = added;
offset = row;
otherArchetype = otherArchetype;
archetype = archetype,
event = ON_ADD,
ids = added,
offset = row,
otherArchetype = otherArchetype,
})
end
end
@ -232,7 +232,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
@ -326,7 +326,7 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
if #to.types > 0 then
-- When there is no previous archetype it should create the archetype
newEntity(entityId, record, to)
onNotifyAdd(world, to, from, record.row, {componentId})
onNotifyAdd(world, to, from, record.row, { componentId })
end
end
@ -401,8 +401,8 @@ local function noop(_self: Query, ...: i53): () -> (number, ...any)
end
local EmptyQuery = {
__iter = noop;
without = noop;
__iter = noop,
without = noop,
}
EmptyQuery.__index = EmptyQuery
setmetatable(EmptyQuery, EmptyQuery)
@ -418,7 +418,7 @@ function World.query(world: World, ...: i53): Query
local compatibleArchetypes = {}
local length = 0
local components = {...}
local components = { ... }
local archetypes = world.archetypes
local queryLength = #components
@ -456,7 +456,7 @@ function World.query(world: World, ...: i53): Query
end
length += 1
compatibleArchetypes[length] = {archetype, indices}
compatibleArchetypes[length] = { archetype, indices }
end
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
@ -468,7 +468,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][1]
local records = archetype.records
@ -599,7 +599,7 @@ function World.delete(world: World, entityId: i53)
end
function World.observer(world: World, ...)
local componentIds = {...}
local componentIds = { ... }
local idsCount = #componentIds
local hooks = world.hooks
@ -647,13 +647,13 @@ function World.observer(world: World, ...)
return archetype.entities[row], unpack(queryOutput, 1, idsCount)
end
end;
end,
}
end
return table.freeze({
World = World;
ON_ADD = ON_ADD;
ON_REMOVE = ON_REMOVE;
ON_SET = ON_SET;
World = World,
ON_ADD = ON_ADD,
ON_REMOVE = ON_REMOVE,
ON_SET = ON_SET,
})

View file

@ -1,6 +1,6 @@
[tools]
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"
rojo = "rojo-rbx/rojo@7.4.4"
stylua = "johnnymorganz/stylua@0.20.0"
selene = "kampfkarren/selene@0.27.1"
Blink = "1Axen/Blink@0.14.1"

View file

@ -972,8 +972,6 @@ local function world_cleanup(world)
world.archetypeIndex = new_archetype_map
end
local world_delete: (world: World, entity: i53, destruct: boolean?) -> ()
do
function world_delete(world: World, entity: i53, destruct: boolean?)

View file

@ -1,5 +1,10 @@
column_width = 120
line_endings = "Unix"
indent_type = "Tabs"
indent_width = 4
quote_style = "ForceDouble"
call_parentheses = "Always"
collapse_simple_statement = "Never"
[sort_requires]
enabled = true

View file

@ -1,7 +1,6 @@
-- original author @centauri
local bt
do
local FAILURE = 0
local SUCCESS = 1
local RUNNING = 2
@ -40,17 +39,17 @@ end
local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE
local btree = FALLBACK {
SEQUENCE {
local btree = FALLBACK({
SEQUENCE({
function()
return 1
end,
function()
return 0
end
},
SEQUENCE {
end,
}),
SEQUENCE({
function()
print(3)
local start = os.clock()
@ -60,22 +59,25 @@ local btree = FALLBACK {
coroutine.yield()
end
return 0
end
},
end,
}),
function()
return 1
end
}
end,
})
function wait(seconds)
local start = os.clock()
while os.clock() - start < seconds do end
while os.clock() - start < seconds do
end
return os.clock() - start
end
local function panic(str)
-- We don't want to interrupt the loop when we error
coroutine.resume(coroutine.create(function() error(str) end))
coroutine.resume(coroutine.create(function()
error(str)
end))
end
local jecs = require("@jecs")
@ -113,7 +115,8 @@ local function Scheduler(world, ...)
dt = sinceLastFrame
local didNotYield, why = xpcall(function()
for _ in run do end
for _ in run do
end
end, debug.traceback)
if didNotYield then
@ -123,10 +126,7 @@ local function Scheduler(world, ...)
if string.find(why, "thread is not yieldable") then
N -= 1
local name = table.remove(systems, i)
panic("Not allowed to yield in the systems."
.. "\n"
.. `System: {name} has been ejected`
)
panic("Not allowed to yield in the systems." .. "\n" .. `System: {name} has been ejected`)
else
panic(why)
end

View file

@ -6,7 +6,7 @@ local function create_cache(hook)
local column = {}
self[component] = column
return column
end
end,
})
return function(world, component, fn)
@ -23,7 +23,7 @@ end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove)
OnRemove = create_cache(jecs.OnRemove),
}
local world = jecs.World.new()
@ -38,10 +38,10 @@ hooks.OnSet(world, Position, function(entity, value)
order ..= "-$2"
end)
world:set(world:entity(), Position, {x=1,y=0,z=1})
world:set(world:entity(), Position, { x = 1, y = 0, z = 1 })
-- Output:
-- $1 270 (1, 0, 1)
-- $2 270 {1, 0, 1}
assert(order == "$1".."-".."$2")
assert(order == "$1" .. "-" .. "$2")

View file

@ -1,4 +1,3 @@
local function calculateAverage(times)
local sum = 0
for _, time in ipairs(times) do
@ -13,21 +12,23 @@ local CASES = {
jecs = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
for _ in q do
end
end
end,
mirror = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
for _ in q do
end
end
end,
}
for name, fn in CASES do
local times = {}
local allocations = {}
local ecs = require("@"..name)
local ecs = require("@" .. name)
local world = ecs.World.new()
local A, B, C = world:component(), world:component(), world:component()

View file

@ -1,5 +1,5 @@
local testkit = require("@testkit")
local jecs = require("@jecs")
local testkit = require("@testkit")
local world = jecs.World.new()

View file

@ -1,5 +1,4 @@
local jecs: typeof(require("../jecs/src")) = require("@jecs");
local jecs: typeof(require("../jecs/src")) = require("@jecs")
local testkit = require("@testkit")
local BENCH, START = testkit.benchmark()
@ -24,7 +23,7 @@ local function CHECK_NO_ERR<T...>(s: string, fn: (T...) -> (), ...: T...)
CHECK(msg == s, 2)
end
end
local N = 2^8
local N = 2 ^ 8
type World = jecs.WorldShim
@ -154,7 +153,8 @@ TEST("world:cleanup()", function()
end)
TEST("world:entity()", function()
do CASE "unique IDs"
do
CASE("unique IDs")
local world = jecs.World.new()
local set = {}
for i = 1, N do
@ -163,7 +163,8 @@ TEST("world:entity()", function()
set[e] = true
end
end
do CASE "generations"
do
CASE("generations")
local world = jecs.World.new()
local e = world:entity()
CHECK(ECS_ID(e) == 1 + jecs.Rest)
@ -173,7 +174,8 @@ TEST("world:entity()", function()
CHECK(ECS_GENERATION(e) == 1) -- 1
end
do CASE "pairs"
do
CASE("pairs")
local world = jecs.World.new()
local _e = world:entity()
local e2 = world:entity()
@ -191,7 +193,8 @@ TEST("world:entity()", function()
end)
TEST("world:set()", function()
do CASE "archetype move"
do
CASE("archetype move")
do
local world = jecs.World.new()
@ -225,7 +228,8 @@ TEST("world:set()", function()
end
end
do CASE "arbitrary order"
do
CASE("arbitrary order")
local world = jecs.World.new()
local Health = world:entity()
@ -238,7 +242,8 @@ TEST("world:set()", function()
CHECK(world:get(id, Poison) == 5)
end
do CASE "pairs"
do
CASE("pairs")
local world = jecs.World.new()
local C1 = world:component()
@ -266,7 +271,8 @@ TEST("world:set()", function()
end)
TEST("world:remove()", function()
do CASE "should allow remove a component that doesn't exist on entity"
do
CASE("should allow remove a component that doesn't exist on entity")
local world = jecs.World.new()
local Health = world:component()
@ -287,8 +293,8 @@ TEST("world:remove()", function()
end)
TEST("world:add()", function()
do CASE "idempotent"
do
CASE("idempotent")
local world = jecs.World.new()
local d = debug_world_inspect(world)
local _1, _2 = world:component(), world:component()
@ -300,7 +306,8 @@ TEST("world:add()", function()
CHECK(d.archetype(e) == "1_2")
end
do CASE "archetype move"
do
CASE("archetype move")
do
local world = jecs.World.new()
@ -325,7 +332,8 @@ TEST("world:add()", function()
end)
TEST("world:query()", function()
do CASE "multiple iter"
do
CASE("multiple iter")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
@ -342,7 +350,8 @@ TEST("world:query()", function()
end
CHECK(counter == 2)
end
do CASE "tag"
do
CASE("tag")
local world = jecs.World.new()
local A = world:entity()
local e = world:entity()
@ -351,7 +360,8 @@ TEST("world:query()", function()
CHECK(a == nil)
end
end
do CASE "pairs"
do
CASE("pairs")
local world = jecs.World.new()
local C1 = world:component()
@ -373,7 +383,8 @@ TEST("world:query()", function()
CHECK(d == nil)
end
end
do CASE "query single component"
do
CASE("query single component")
do
local world = jecs.World.new()
local A = world:component()
@ -415,17 +426,18 @@ TEST("world:query()", function()
local i = 0
local j = 0
for _ in q do
i+=1
i += 1
end
for _ in q do
j+=1
j += 1
end
CHECK(i == 2)
CHECK(j == 0)
end
end
do CASE "query missing component"
do
CASE("query missing component")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
@ -446,7 +458,8 @@ TEST("world:query()", function()
CHECK(counter == 0)
end
do CASE "query more than 8 components"
do
CASE("query more than 8 components")
local world = jecs.World.new()
local components = {}
@ -456,23 +469,24 @@ TEST("world:query()", function()
end
local e = world:entity()
for i, id in components do
world:set(e, id, 13^i)
world:set(e, id, 13 ^ i)
end
for entity, a, b, c, d, e, f, g, h, i in world:query(unpack(components)) do
CHECK(a == 13^1)
CHECK(b == 13^2)
CHECK(c == 13^3)
CHECK(d == 13^4)
CHECK(e == 13^5)
CHECK(f == 13^6)
CHECK(g == 13^7)
CHECK(h == 13^8)
CHECK(i == 13^9)
CHECK(a == 13 ^ 1)
CHECK(b == 13 ^ 2)
CHECK(c == 13 ^ 3)
CHECK(d == 13 ^ 4)
CHECK(e == 13 ^ 5)
CHECK(f == 13 ^ 6)
CHECK(g == 13 ^ 7)
CHECK(h == 13 ^ 8)
CHECK(i == 13 ^ 9)
end
end
do CASE "should be able to get next results"
do
CASE("should be able to get next results")
local world = jecs.World.new() :: World
world:component()
local A = world:component()
@ -502,7 +516,8 @@ TEST("world:query()", function()
CHECK(true)
end
do CASE("should query all matching entities when irrelevant component is removed")
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()
@ -531,7 +546,8 @@ TEST("world:query()", function()
CHECK(added == N)
end
do CASE("should query all entities without B")
do
CASE("should query all entities without B")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
@ -555,7 +571,8 @@ TEST("world:query()", function()
CHECK(#entities == 0)
end
do CASE("should allow querying for relations")
do
CASE("should allow querying for relations")
local world = jecs.World.new()
local Eats = world:component()
local Apples = world:component()
@ -568,7 +585,8 @@ TEST("world:query()", function()
end
end
do CASE("should allow wildcards in queries")
do
CASE("should allow wildcards in queries")
local world = jecs.World.new()
local Eats = world:component()
local Apples = world:entity()
@ -587,7 +605,8 @@ TEST("world:query()", function()
end
end
do CASE("should match against multiple pairs")
do
CASE("should match against multiple pairs")
local world = jecs.World.new()
local Eats = world:component()
local Apples = world:entity()
@ -619,7 +638,8 @@ TEST("world:query()", function()
CHECK(count == 1)
end
do CASE "should only relate alive entities"
do
CASE("should only relate alive entities")
SKIP()
local world = jecs.World.new()
local Eats = world:entity()
@ -646,7 +666,8 @@ TEST("world:query()", function()
CHECK(world:get(bob, pair(Eats, Apples)) == nil)
end
do CASE("should error when setting invalid pair")
do
CASE("should error when setting invalid pair")
local world = jecs.World.new()
local Eats = world:component()
local Apples = world:component()
@ -657,7 +678,8 @@ TEST("world:query()", function()
world:set(bob, pair(Eats, Apples), "bob eats apples")
end
do CASE("should find target for ChildOf")
do
CASE("should find target for ChildOf")
local world = jecs.World.new()
local ChildOf = jecs.ChildOf
@ -680,7 +702,8 @@ TEST("world:query()", function()
CHECK(count == 2)
end
do CASE "despawning while iterating"
do
CASE("despawning while iterating")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
@ -699,8 +722,10 @@ TEST("world:query()", function()
CHECK(count == 2)
end
do CASE "iterator invalidation"
do CASE "adding"
do
CASE("iterator invalidation")
do
CASE("adding")
SKIP()
local world = jecs.World.new()
local A = world:component()
@ -722,7 +747,8 @@ TEST("world:query()", function()
CHECK(count == 2)
end
do CASE "spawning"
do
CASE("spawning")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
@ -743,7 +769,8 @@ TEST("world:query()", function()
end
end
do CASE "should not find any entities"
do
CASE("should not find any entities")
local world = jecs.World.new()
local Hello = world:component()
@ -761,7 +788,8 @@ TEST("world:query()", function()
CHECK(withoutCount == 0)
end
do CASE "Empty Query"
do
CASE("Empty Query")
do
local world = jecs.World.new()
local A = world:component()
@ -794,7 +822,8 @@ TEST("world:query()", function()
end
end
do CASE "without"
do
CASE("without")
do
-- REGRESSION TEST
local world = jecs.World.new()
@ -810,7 +839,8 @@ TEST("world:query()", function()
end)
TEST("world:clear()", function()
do CASE("should remove its components")
do
CASE("should remove its components")
local world = jecs.World.new() :: World
local A = world:component()
local B = world:component()
@ -828,7 +858,8 @@ TEST("world:clear()", function()
CHECK(world:get(e, B) == nil)
end
do CASE "should move last record"
do
CASE("should move last record")
local world = world_new()
local A = world:component()
@ -872,7 +903,8 @@ TEST("world:clear()", function()
end)
TEST("world:has()", function()
do CASE "should find Tag on entity"
do
CASE("should find Tag on entity")
local world = jecs.World.new()
local Tag = world:entity()
@ -883,7 +915,8 @@ TEST("world:has()", function()
CHECK(world:has(e, Tag))
end
do CASE "should return false when missing one tag"
do
CASE("should return false when missing one tag")
local world = jecs.World.new()
local A = world:entity()
@ -901,7 +934,8 @@ TEST("world:has()", function()
end)
TEST("world:component()", function()
do CASE "only components should have EcsComponent trait"
do
CASE("only components should have EcsComponent trait")
local world = jecs.World.new() :: World
local A = world:component()
local e = world:entity()
@ -910,7 +944,8 @@ TEST("world:component()", function()
CHECK(not world:has(e, jecs.Component))
end
do CASE "tag"
do
CASE("tag")
local world = jecs.World.new() :: World
local A = world:component()
local B = world:entity()
@ -928,7 +963,8 @@ TEST("world:component()", function()
end)
TEST("world:delete", function()
do CASE "bug: Empty entity does not respect cleanup policy"
do
CASE("bug: Empty entity does not respect cleanup policy")
local world = world_new()
local parent = world:entity()
local tag = world:entity()
@ -947,7 +983,8 @@ TEST("world:delete", function()
CHECK(not world:contains(tag))
CHECK(not world:has(entity, tag)) -- => true
end
do CASE("should allow deleting components")
do
CASE("should allow deleting components")
local world = jecs.World.new()
local Health = world:component()
@ -969,7 +1006,8 @@ TEST("world:delete", function()
CHECK(world:get(id1, Health) == 50)
end
do CASE "delete entities using another Entity as component with Delete cleanup action"
do
CASE("delete entities using another Entity as component with Delete cleanup action")
local world = jecs.World.new()
local Health = world:entity()
@ -998,7 +1036,8 @@ TEST("world:delete", function()
CHECK(not world:has(id1, Health))
end
do CASE "delete children"
do
CASE("delete children")
local world = jecs.World.new()
local Health = world:component()
@ -1018,7 +1057,6 @@ TEST("world:delete", function()
table.insert(children, child)
end
BENCH("delete children of entity", function()
world:delete(e)
end)
@ -1050,7 +1088,8 @@ TEST("world:delete", function()
end
end
do CASE "fast delete"
do
CASE("fast delete")
local world = jecs.World.new()
local entities = {}
@ -1076,7 +1115,8 @@ TEST("world:delete", function()
end
end
do CASE "cycle"
do
CASE("cycle")
local world = jecs.World.new()
local Likes = world:component()
world:add(Likes, pair(jecs.OnDeleteTarget, jecs.Delete))
@ -1093,7 +1133,8 @@ TEST("world:delete", function()
end)
TEST("world:target", function()
do CASE "nth index"
do
CASE("nth index")
local world = world_new()
local A = world:component()
local B = world:component()
@ -1117,7 +1158,8 @@ TEST("world:target", function()
CHECK(world:target(e, C, 1) == nil)
end
do CASE "infer index when unspecified"
do
CASE("infer index when unspecified")
local world = world_new()
local A = world:component()
local B = world:component()
@ -1136,7 +1178,8 @@ TEST("world:target", function()
CHECK(world:target(e, C) == world:target(e, C, 0))
end
do CASE "loop until no target"
do
CASE("loop until no target")
local world = world_new()
local ROOT = world:entity()
@ -1152,14 +1195,13 @@ TEST("world:target", function()
local i = 0
local target = world:target(e1, ROOT, 0)
while target do
i+=1
i += 1
CHECK(targets[i] == target)
target = world:target(e1, ROOT, i)
end
CHECK(i == 10)
end
end)
TEST("world:contains", function()
@ -1167,16 +1209,23 @@ TEST("world:contains", function()
local id = world:entity()
CHECK(world:contains(id))
do CASE "should not exist after delete"
do
CASE("should not exist after delete")
world:delete(id)
CHECK(not world:contains(id))
end
end)
type Tracker<T> = { track: (world: World, fn: (changes: {
type Tracker<T> = {
track: (
world: World,
fn: (
changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
}
type Entity<T = any> = number & { __nominal_type_dont_use: T }
@ -1298,7 +1347,8 @@ end
TEST("changetracker:track()", function()
local world = jecs.World.new()
do CASE "added"
do
CASE("added")
local Test = world:component() :: Entity<{ foo: number }>
local TestTracker = ChangeTracker(world, Test)
@ -1309,7 +1359,7 @@ TEST("changetracker:track()", function()
TestTracker.track(function(changes)
local added = 0
for e, test in changes.added() do
added+=1
added += 1
CHECK(test == data)
end
for e, old, new in changes.changed() do
@ -1321,7 +1371,8 @@ TEST("changetracker:track()", function()
CHECK(added == 1)
end)
end
do CASE "changed"
do
CASE("changed")
local Test = world:component() :: Entity<{ foo: number }>
local TestTracker = ChangeTracker(world, Test)
@ -1329,8 +1380,7 @@ TEST("changetracker:track()", function()
local e1 = world:entity()
world:set(e1, Test, data)
TestTracker.track(function(changes)
end)
TestTracker.track(function(changes) end)
data.foo += 1
@ -1344,12 +1394,13 @@ TEST("changetracker:track()", function()
CHECK(new == data)
CHECK(old ~= new)
CHECK(diff(new, old))
changed +=1
changed += 1
end
CHECK(changed == 1)
end)
end
do CASE "removed"
do
CASE("removed")
local Test = world:component() :: Entity<{ foo: number }>
local TestTracker = ChangeTracker(world, Test)
@ -1357,8 +1408,7 @@ TEST("changetracker:track()", function()
local e1 = world:entity()
world:set(e1, Test, data)
TestTracker.track(function(changes)
end)
TestTracker.track(function(changes) end)
world:remove(e1, Test)
@ -1378,7 +1428,8 @@ TEST("changetracker:track()", function()
end)
end
do CASE "multiple change trackers"
do
CASE("multiple change trackers")
local A = world:component()
local B = world:component()
local trackerA = ChangeTracker(world, A)
@ -1402,9 +1453,7 @@ TEST("changetracker:track()", function()
CHECK(new == "b2")
end
end)
end
end)
local function create_cache(hook)
@ -1413,7 +1462,7 @@ local function create_cache(hook)
local column = {}
self[component] = column
return column
end
end,
})
return function(world, component, fn)
@ -1430,11 +1479,12 @@ end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove)
OnRemove = create_cache(jecs.OnRemove),
}
TEST("Hooks", function()
do CASE "OnAdd"
do
CASE("OnAdd")
local world = jecs.World.new()
local Transform = world:component()
local e1 = world:entity()
@ -1444,7 +1494,8 @@ TEST("Hooks", function()
world:add(e1, Transform)
end
do CASE "OnSet"
do
CASE("OnSet")
local world = jecs.World.new()
local Number = world:component()
local e1 = world:entity()
@ -1462,7 +1513,8 @@ TEST("Hooks", function()
world:set(e1, Number, 1)
end
do CASE "OnRemove"
do
CASE("OnRemove")
do
-- basic
local world = jecs.World.new()
@ -1496,7 +1548,8 @@ TEST("Hooks", function()
end
end
do CASE "the filip incident"
do
CASE("the filip incident")
local world = jecs.World.new()
export type Iterator<T> = () -> (Entity, T?, T?)
@ -1588,52 +1641,52 @@ TEST("Hooks", function()
local iter, destroy = ChangeTracker(Transform)
local e1 = world:entity()
world:set(e1, Transform, {1,1})
world:set(e1, Transform, { 1, 1 })
local counter = 0
for _ in iter do
counter += 1
end
CHECK(counter == 1)
end
end)
TEST("scheduler", function()
type System = {
callback: (world: World) -> ()
callback: (world: World) -> (),
}
type Systems = { System }
type Events = {
RenderStepped: Systems,
Heartbeat: Systems
Heartbeat: Systems,
}
local scheduler_new: (w: World) -> {
local scheduler_new: (
w: World
) -> {
components: {
Disabled: Entity,
System: Entity<System>,
Phase: Entity,
DependsOn: Entity
DependsOn: Entity,
},
collect: {
under_event: (event: Entity) -> Systems,
all: () -> Events
all: () -> Events,
},
systems: {
run: (events: Events) -> (),
new: (callback: (world: World) -> (), phase: Entity) -> Entity
new: (callback: (world: World) -> (), phase: Entity) -> Entity,
},
phases: {
RenderStepped: Entity,
Heartbeat: Entity
Heartbeat: Entity,
},
phase: (after: Entity) -> Entity
phase: (after: Entity) -> Entity,
}
do
@ -1731,18 +1784,19 @@ TEST("scheduler", function()
collect = {
under_event = scheduler_collect_systems_under_event,
all = scheduler_collect_systems_all
all = scheduler_collect_systems_all,
},
systems = {
new = scheduler_systems_new,
run = scheduler_systems_run
}
run = scheduler_systems_run,
},
}
end
end
do CASE "event dependant phase"
do
CASE("event dependant phase")
local world = jecs.World.new()
local scheduler = scheduler_new(world)
@ -1755,7 +1809,8 @@ TEST("scheduler", function()
CHECK(world:target(Physics, DependsOn, 0) == Heartbeat)
end
do CASE "user-defined sub phases"
do
CASE("user-defined sub phases")
local world = jecs.World.new()
local scheduler = scheduler_new(world)
local components = scheduler.components
@ -1768,7 +1823,8 @@ TEST("scheduler", function()
CHECK(world:target(B, DependsOn, 0) == A)
end
do CASE "phase order"
do
CASE("phase order")
local world = jecs.World.new()
local scheduler = scheduler_new(world)
@ -1799,7 +1855,8 @@ TEST("scheduler", function()
CHECK(order == "BEGIN->move->hit->END")
end
do CASE "collect only systems under phase recursive"
do
CASE("collect only systems under phase recursive")
local world = jecs.World.new()
local scheduler = scheduler_new(world)
local phases = scheduler.phases
@ -1809,14 +1866,11 @@ TEST("scheduler", function()
local Physics = scheduler.phase(Heartbeat)
local Collisions = scheduler.phase(Physics)
local function move()
end
local function move() end
local function hit()
end
local function hit() end
local function camera()
end
local function camera() end
local createSystem = scheduler.systems.new
@ -1845,7 +1899,8 @@ TEST("scheduler", function()
end)
TEST("repro", function()
do CASE ""
do
CASE("")
local world = world_new()
local reproEntity = world:component()
local components = { Cooldown = world:component() }
@ -1859,7 +1914,7 @@ TEST("repro", function()
if cooldown <= 0 then
table.insert(toRemove, id)
print('removing')
print("removing")
-- world:remove(id, components.Cooldown)
else
world:set(id, components.Cooldown, cooldown)

View file

@ -96,7 +96,7 @@ local function convert_units(unit: string, value: number): (number, string)
return value * sign, prefix_colors[order](prefixes[order] .. unit)
end
local WALL = color.gray "│"
local WALL = color.gray("│")
--------------------------------------------------------------------------------
-- Testing
@ -118,7 +118,7 @@ type Case = {
name: string,
result: number,
line: number?,
focus: boolean
focus: boolean,
}
local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5
@ -129,35 +129,38 @@ local test: Test?
local tests: { Test } = {}
local function output_test_result(test: Test)
if check_for_focused then
local any_focused = test.focus
for _, case in test.cases do
any_focused = any_focused or case.focus
end
if not any_focused then return end
if not any_focused then
return
end
end
print(color.white(test.name))
for _, case in test.cases do
local status = ({
[PASS] = color.green "PASS",
[FAIL] = color.red "FAIL",
[NONE] = color.orange "NONE",
[ERROR] = color.red "FAIL",
[SKIPPED] = color.yellow "SKIP"
[PASS] = color.green("PASS"),
[FAIL] = color.red("FAIL"),
[NONE] = color.orange("NONE"),
[ERROR] = color.red("FAIL"),
[SKIPPED] = color.yellow("SKIP"),
})[case.result]
local line = case.result == FAIL and color.red(`{case.line}:`) or ""
if check_for_focused and case.focus == false and test.focus == false then continue end
if check_for_focused and case.focus == false and test.focus == false then
continue
end
print(`{status}{WALL} {line}{color.gray(case.name)}`)
end
if test.error then
print(color.gray "error: " .. color.red(test.error.message))
print(color.gray "trace: " .. color.red(test.error.trace))
print(color.gray("error: ") .. color.red(test.error.message))
print(color.gray("trace: ") .. color.red(test.error.trace))
else
print()
end
@ -170,7 +173,7 @@ local function CASE(name: string)
local case = {
name = name,
result = NONE,
focus = false
focus = false,
}
test.case = case
@ -183,7 +186,7 @@ local function CHECK<T>(value: T, stack: number?): T?
local case = test.case
if not case then
CASE ""
CASE("")
case = test.case
end
@ -208,7 +211,7 @@ local function TEST(name: string, fn: () -> ())
name = name,
cases = {},
duration = 0,
focus = false
focus = false,
}
assert(test)
@ -221,7 +224,9 @@ local function TEST(name: string, fn: () -> ())
end)
test.duration = os.clock() - start
if not test.case then CASE "" end
if not test.case then
CASE("")
end
assert(test.case, "no active case")
if not success then
@ -271,27 +276,14 @@ local function FINISH(): boolean
output_test_result(test)
end
print(
color.gray(
string.format(
`{passed_cases}/{total_cases} test cases passed in %.3f ms.`,
duration * 1e3
)
)
)
print(color.gray(string.format(`{passed_cases}/{total_cases} test cases passed in %.3f ms.`, duration * 1e3)))
if check_for_focused then
print(
color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`)
)
print(color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`))
end
local fails = total_cases - passed_cases
print(
(fails > 0 and color.red or color.green)(
`{fails} {fails == 1 and "fail" or "fails"}`
)
)
print((fails > 0 and color.red or color.green)(`{fails} {fails == 1 and "fail" or "fails"}`))
check_for_focused = false
return success, table.clear(tests)
@ -331,7 +323,7 @@ local function BENCH(name: string, fn: () -> ())
bench = {}
assert(bench);
(collectgarbage :: any) "collect"
(collectgarbage :: any)("collect")
local mem_start = gcinfo()
local time_start = os.clock()
@ -345,7 +337,7 @@ local function BENCH(name: string, fn: () -> ())
local mem_stop = gcinfo()
if not success then
print(`{WALL}{color.red "ERROR"}{WALL} {name}`)
print(`{WALL}{color.red("ERROR")}{WALL} {name}`)
print(color.gray(err_msg :: string))
else
time_start = bench.time_start or time_start
@ -353,14 +345,10 @@ local function BENCH(name: string, fn: () -> ())
local n = bench.iterations or 1
local d, d_unit = convert_units("s", (time_stop - time_start) / n)
local a, a_unit =
convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
local a, a_unit = convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
local function round(x: number): string
return x > 0
and x < 10
and (x - math.floor(x)) > 0
and string.format("%2.1f", x)
return x > 0 and x < 10 and (x - math.floor(x)) > 0 and string.format("%2.1f", x)
or string.format("%3.f", x)
end
@ -394,9 +382,9 @@ local function print2(v: unknown)
if type(value) == "string" then
local n = str.n
str[n + 1] = '"'
str[n + 1] = "\""
str[n + 2] = value
str[n + 3] = '"'
str[n + 3] = "\""
str.n = n + 3
elseif type(value) ~= "table" then
local n = str.n
@ -468,25 +456,35 @@ end
--------------------------------------------------------------------------------
local function shallow_eq(a: {}, b: {}): boolean
if #a ~= #b then return false end
if #a ~= #b then
return false
end
for i, v in next, a do
if b[i] ~= v then return false end
if b[i] ~= v then
return false
end
end
for i, v in next, b do
if a[i] ~= v then return false end
if a[i] ~= v then
return false
end
end
return true
end
local function deep_eq(a: {}, b: {}): boolean
if #a ~= #b then return false end
if #a ~= #b then
return false
end
for i, v in next, a do
if type(b[i]) == "table" and type(v) == "table" then
if deep_eq(b[i], v) == false then return false end
if deep_eq(b[i], v) == false then
return false
end
elseif b[i] ~= v then
return false
end
@ -494,7 +492,9 @@ local function deep_eq(a: {}, b: {}): boolean
for i, v in next, b do
if type(a[i]) == "table" and type(v) == "table" then
if deep_eq(a[i], v) == false then return false end
if deep_eq(a[i], v) == false then
return false
end
elseif a[i] ~= v then
return false
end