mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 01:20:04 +00:00
parent
f82318c642
commit
e81b572480
45 changed files with 2867 additions and 2855 deletions
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
local jecs = require("@jecs")
|
||||
local mirror = require("../mirror/init")
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ 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
|
||||
|
@ -237,5 +239,4 @@ do TITLE(`query {N} entities`)
|
|||
for i = 13, 0, -1 do
|
||||
view_bench(2 ^ i)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,7 +69,6 @@ return {
|
|||
end
|
||||
end,
|
||||
|
||||
|
||||
ECR = function()
|
||||
local e = registry2.create()
|
||||
for i = 1, 5000 do
|
||||
|
@ -84,7 +83,6 @@ return {
|
|||
end
|
||||
end,
|
||||
|
||||
|
||||
Jecs = function()
|
||||
local e = ecs:entity()
|
||||
for i = 1, 5000 do
|
||||
|
@ -96,7 +94,6 @@ return {
|
|||
ecs:set(e, C6, { value = false })
|
||||
ecs:set(e, C7, { value = false })
|
||||
ecs:set(e, C8, { value = false })
|
||||
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
|
|
@ -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,7 +48,6 @@ local E6 = mcs:entity()
|
|||
local E7 = mcs:entity()
|
||||
local E8 = mcs:entity()
|
||||
|
||||
|
||||
local registry2 = ecr.registry()
|
||||
|
||||
local function flip()
|
||||
|
@ -123,11 +121,8 @@ for i = 1, N do
|
|||
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)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -252,7 +252,7 @@ Reliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
|
|||
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)
|
||||
|
@ -268,7 +268,7 @@ Unreliable.OnClientEvent:Connect(function(Buffer: buffer, Instances: {Instance})
|
|||
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)
|
||||
|
@ -305,7 +305,7 @@ return {
|
|||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
SpawnMob = {
|
||||
Iter = function(): () -> (number, number, CFrame, number)
|
||||
|
@ -331,7 +331,6 @@ return {
|
|||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
}
|
|
@ -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,7 +232,6 @@ function Types.WriteEVENT_SpawnMob(Value1: number, Value2: CFrame, Value3: numbe
|
|||
buffer.writeu8(SendBuffer, BLOCK_START + 33, Value3)
|
||||
end
|
||||
|
||||
|
||||
local PlayersMap: { [Player]: BufferSave } = {}
|
||||
|
||||
Players.PlayerRemoving:Connect(function(Player)
|
||||
|
@ -264,7 +263,7 @@ Reliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instance
|
|||
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)
|
||||
|
@ -277,7 +276,7 @@ Unreliable.OnServerEvent:Connect(function(Player: Player, Buffer: buffer, Instan
|
|||
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)
|
||||
|
@ -370,5 +369,4 @@ return {
|
|||
end
|
||||
end,
|
||||
},
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -34,7 +34,7 @@ end
|
|||
local bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING
|
||||
RUNNING = RUNNING,
|
||||
}
|
||||
|
||||
return bt
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -11,7 +11,8 @@ local components: {
|
|||
Transform: Entity<CFrame>,
|
||||
Velocity: Entity<number>,
|
||||
Previous: Entity,
|
||||
} = {
|
||||
} =
|
||||
{
|
||||
Character = world:component(),
|
||||
Mob = world:component(),
|
||||
Model = world:component(),
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@ local std = {
|
|||
world = world :: World,
|
||||
pair = jecs.pair,
|
||||
__ = jecs.w,
|
||||
hooks = require(script.hooks)
|
||||
hooks = require(script.hooks),
|
||||
}
|
||||
|
||||
return std
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -11,7 +11,7 @@ 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,7 +113,6 @@ 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)
|
||||
|
||||
|
|
|
@ -157,13 +157,13 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet
|
|||
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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
local function calculateAverage(times)
|
||||
local sum = 0
|
||||
for _, time in ipairs(times) do
|
||||
|
@ -13,15 +12,17 @@ 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
local testkit = require("@testkit")
|
||||
local jecs = require("@jecs")
|
||||
local testkit = require("@testkit")
|
||||
|
||||
local world = jecs.World.new()
|
||||
|
||||
|
|
247
test/tests.luau
247
test/tests.luau
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -425,7 +436,8 @@ TEST("world:query()", function()
|
|||
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 = {}
|
||||
|
||||
|
@ -472,7 +485,8 @@ TEST("world:query()", function()
|
|||
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()
|
||||
|
@ -1159,7 +1202,6 @@ TEST("world:target", function()
|
|||
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1349,7 +1399,8 @@ TEST("changetracker:track()", function()
|
|||
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?)
|
||||
|
@ -1595,45 +1648,45 @@ TEST("Hooks", function()
|
|||
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)
|
||||
|
|
96
testkit.luau
96
testkit.luau
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue