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

@ -14,7 +14,7 @@ jobs:
- name: Run Stylua
uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
# CLI arguments
args: ./src
token: ${{ secrets.GITHUB_TOKEN }}
version: latest # NOTE: we recommend pinning to a specific version in case of formatting changes
# CLI arguments
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()
@ -22,17 +23,17 @@ do TITLE "create"
end)
local A = world:component()
local B = world:component()
local B = world:component()
BENCH("pair", function()
for i = 1, START(N) do
jecs.pair(A, B)
end
end)
end
do TITLE "set"
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,14 +198,13 @@ do TITLE(`query {N} entities`)
end
end
START()
for id in world:query(A, B, C, D) do
end
end)
BENCH(`inlined query`, function()
local world = jecs.World.new()
local world = jecs.World.new()
local A = world:component()
local B = world:component()
local C = world:component()
@ -219,15 +222,14 @@ 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
local columns, records = archetype.columns, archetype.records
local a = columns[records[A].column]
local b = columns[records[B].column]
local c = columns[records[C].column]
local d = columns[records[D].column]
for row in archetype.entities do
for row in archetype.entities do
local _1, _2, _3, _4 = a[row], b[row], c[row], d[row]
end
end
@ -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

@ -20,7 +20,7 @@ do
TITLE("one component in common")
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
BENCH("1 component", function()
BENCH("1 component", function()
for _ in world:query(A) do
end
end)
@ -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,37 +12,35 @@ 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
}
for i = 1, 1000 do
table.insert(matter_entities, newWorld:spawn(A(), B()))
local e = ecs:entity()
ecs:set(e, C, {})
ecs:set(e, D, {})
table.insert(jecs_entities, e)
end
return entities
end;
local matter_entities = {}
local jecs_entities = {}
local entities = {
matter = matter_entities,
jecs = jecs_entities,
}
for i = 1, 1000 do
table.insert(matter_entities, newWorld:spawn(A(), B()))
local e = ecs:entity()
ecs:set(e, C, {})
ecs:set(e, D, {})
table.insert(jecs_entities, e)
end
return entities
end,
Functions = {
Matter = function(_, entities)
for _, entity in entities.matter do
newWorld:despawn(entity)
end
end;
for _, entity in entities.matter do
newWorld:despawn(entity)
end
end,
Jecs = function(_, entities)
for _, entity in entities.jecs do
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()
@ -53,51 +52,49 @@ return {
end,
Functions = {
Matter = function()
local e = newWorld:spawn()
for i = 1, 5000 do
newWorld:insert(e,
A1({ value = true }),
A2({ value = true }),
A3({ value = true }),
A4({ value = true }),
A5({ value = true }),
A6({ value = true }),
A7({ value = true }),
A8({ value = true })
)
end
Matter = function()
local e = newWorld:spawn()
for i = 1, 5000 do
newWorld:insert(
e,
A1({ value = true }),
A2({ value = true }),
A3({ value = true }),
A4({ value = true }),
A5({ value = true }),
A6({ value = true }),
A7({ value = true }),
A8({ value = true })
)
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})
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 })
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})
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 })
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,69 +66,66 @@ 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)
print(combination)
end
hm += 1
end
@ -148,7 +143,7 @@ local WALL = gray(" │ ")
local count = 0
for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do
count += #archetype.entities
count += #archetype.entities
end
print(count)
@ -165,13 +160,13 @@ return {
end,
ECR = function()
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
end
end,
for entityId, firstComponent in registry2:view(B2, B4, B6, B8) do
end
end,
Jecs = function()
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
end
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
end
end,
},
}

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

@ -10,11 +10,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
if not RunService:IsClient() then
error("Client network module can only be required from the client.")
error("Client network module can only be required from the client.")
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
@ -31,128 +32,128 @@ local RecieveInstances = {}
local RecieveInstanceCursor = 0
type Entry = {
value: any,
next: Entry?
value: any,
next: Entry?,
}
type Queue = {
head: Entry?,
tail: Entry?
head: Entry?,
tail: Entry?,
}
type BufferSave = {
Size: number,
Cursor: number,
Buffer: buffer,
Instances: {Instance}
Size: number,
Cursor: number,
Buffer: buffer,
Instances: { Instance },
}
local function Read(Bytes: number)
local Offset = RecieveCursor
RecieveCursor += Bytes
return Offset
local Offset = RecieveCursor
RecieveCursor += Bytes
return Offset
end
local function Save(): BufferSave
return {
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances
}
return {
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances,
}
end
local function Load(Save: BufferSave?)
if Save then
SendSize = Save.Size
SendCursor = Save.Cursor
SendOffset = Save.Cursor
SendBuffer = Save.Buffer
SendInstances = Save.Instances
return
end
if Save then
SendSize = Save.Size
SendCursor = Save.Cursor
SendOffset = Save.Cursor
SendBuffer = Save.Buffer
SendInstances = Save.Instances
return
end
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
SendInstances = {}
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
SendInstances = {}
end
local function Invoke()
if Invocations == 255 then
Invocations = 0
end
if Invocations == 255 then
Invocations = 0
end
local Invocation = Invocations
Invocations += 1
return Invocation
local Invocation = Invocations
Invocations += 1
return Invocation
end
local function Allocate(Bytes: number)
local InUse = (SendCursor + Bytes)
if InUse > SendSize then
--> Avoid resizing the buffer for every write
while InUse > SendSize do
SendSize *= 1.5
end
local InUse = (SendCursor + Bytes)
if InUse > SendSize then
--> Avoid resizing the buffer for every write
while InUse > SendSize do
SendSize *= 1.5
end
local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer
end
local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer
end
SendOffset = SendCursor
SendCursor += Bytes
return SendOffset
SendOffset = SendCursor
SendCursor += Bytes
return SendOffset
end
local function CreateQueue(): Queue
return {
head = nil,
tail = nil
}
return {
head = nil,
tail = nil,
}
end
local function Pop(queue: Queue): any
local head = queue.head
if head == nil then
return
end
local head = queue.head
if head == nil then
return
end
queue.head = head.next
return head.value
queue.head = head.next
return head.value
end
local function Push(queue: Queue, value: any)
local entry: Entry = {
value = value,
next = nil
}
local entry: Entry = {
value = value,
next = nil,
}
if queue.tail ~= nil then
queue.tail.next = entry
end
if queue.tail ~= nil then
queue.tail.next = entry
end
queue.tail = entry
queue.tail = entry
if queue.head == nil then
queue.head = entry
end
if queue.head == nil then
queue.head = entry
end
end
local Types = {}
local Calls = table.create(256)
local Events: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Reliable = table.create(256),
Unreliable = table.create(256),
}
local Queue: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Reliable = table.create(256),
Unreliable = table.create(256),
}
Queue.Unreliable[0] = CreateQueue()
@ -220,21 +221,20 @@ 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
end
if SendCursor <= 0 then
return
end
local Buffer = buffer.create(SendCursor)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
Reliable:FireServer(Buffer, SendInstances)
local Buffer = buffer.create(SendCursor)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
Reliable:FireServer(Buffer, SendInstances)
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
table.clear(SendInstances)
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
table.clear(SendInstances)
end
local Elapsed = 0
@ -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,53 +285,52 @@ 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
return index, unpack(arguments, 1, arguments.n)
end
return
return
end
end,
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
return index, unpack(arguments, 1, arguments.n)
end
return
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
return index, unpack(arguments, 1, arguments.n)
end
return
return
end
end,
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
return index, unpack(arguments, 1, arguments.n)
end
return
return
end
end
end,
},
}
}

View file

@ -11,23 +11,24 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
if not RunService:IsServer() then
error("Server network module can only be required from the server.")
error("Server network module can only be required from the server.")
end
local Reliable: RemoteEvent = ReplicatedStorage:FindFirstChild("BLINK_RELIABLE_REMOTE") :: RemoteEvent
if not Reliable then
local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"
RemoteEvent.Parent = ReplicatedStorage
Reliable = RemoteEvent
local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "BLINK_RELIABLE_REMOTE"
RemoteEvent.Parent = ReplicatedStorage
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"
UnreliableRemoteEvent.Parent = ReplicatedStorage
Unreliable = UnreliableRemoteEvent
local UnreliableRemoteEvent = Instance.new("UnreliableRemoteEvent")
UnreliableRemoteEvent.Name = "BLINK_UNRELIABLE_REMOTE"
UnreliableRemoteEvent.Parent = ReplicatedStorage
Unreliable = UnreliableRemoteEvent
end
local Invocations = 0
@ -45,131 +46,130 @@ local RecieveInstances = {}
local RecieveInstanceCursor = 0
type Entry = {
value: any,
next: Entry?
value: any,
next: Entry?,
}
type Queue = {
head: Entry?,
tail: Entry?
head: Entry?,
tail: Entry?,
}
type BufferSave = {
Size: number,
Cursor: number,
Buffer: buffer,
Instances: {Instance}
Size: number,
Cursor: number,
Buffer: buffer,
Instances: { Instance },
}
local function Read(Bytes: number)
local Offset = RecieveCursor
RecieveCursor += Bytes
return Offset
local Offset = RecieveCursor
RecieveCursor += Bytes
return Offset
end
local function Save(): BufferSave
return {
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances
}
return {
Size = SendSize,
Cursor = SendCursor,
Buffer = SendBuffer,
Instances = SendInstances,
}
end
local function Load(Save: BufferSave?)
if Save then
SendSize = Save.Size
SendCursor = Save.Cursor
SendOffset = Save.Cursor
SendBuffer = Save.Buffer
SendInstances = Save.Instances
return
end
if Save then
SendSize = Save.Size
SendCursor = Save.Cursor
SendOffset = Save.Cursor
SendBuffer = Save.Buffer
SendInstances = Save.Instances
return
end
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
SendInstances = {}
SendSize = 64
SendCursor = 0
SendOffset = 0
SendBuffer = buffer.create(64)
SendInstances = {}
end
local function Invoke()
if Invocations == 255 then
Invocations = 0
end
if Invocations == 255 then
Invocations = 0
end
local Invocation = Invocations
Invocations += 1
return Invocation
local Invocation = Invocations
Invocations += 1
return Invocation
end
local function Allocate(Bytes: number)
local InUse = (SendCursor + Bytes)
if InUse > SendSize then
--> Avoid resizing the buffer for every write
while InUse > SendSize do
SendSize *= 1.5
end
local InUse = (SendCursor + Bytes)
if InUse > SendSize then
--> Avoid resizing the buffer for every write
while InUse > SendSize do
SendSize *= 1.5
end
local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer
end
local Buffer = buffer.create(SendSize)
buffer.copy(Buffer, 0, SendBuffer, 0, SendCursor)
SendBuffer = Buffer
end
SendOffset = SendCursor
SendCursor += Bytes
return SendOffset
SendOffset = SendCursor
SendCursor += Bytes
return SendOffset
end
local function CreateQueue(): Queue
return {
head = nil,
tail = nil
}
return {
head = nil,
tail = nil,
}
end
local function Pop(queue: Queue): any
local head = queue.head
if head == nil then
return
end
local head = queue.head
if head == nil then
return
end
queue.head = head.next
return head.value
queue.head = head.next
return head.value
end
local function Push(queue: Queue, value: any)
local entry: Entry = {
value = value,
next = nil
}
local entry: Entry = {
value = value,
next = nil,
}
if queue.tail ~= nil then
queue.tail.next = entry
end
if queue.tail ~= nil then
queue.tail.next = entry
end
queue.tail = entry
queue.tail = entry
if queue.head == nil then
queue.head = entry
end
if queue.head == nil then
queue.head = entry
end
end
local Types = {}
local Calls = table.create(256)
local Events: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Reliable = table.create(256),
Unreliable = table.create(256),
}
local Queue: any = {
Reliable = table.create(256),
Unreliable = table.create(256)
Reliable = table.create(256),
Unreliable = table.create(256),
}
function Types.ReadEVENT_UpdateTransform(): (number, CFrame)
-- Read BLOCK: 32 bytes
local BLOCK_START = Read(32)
@ -232,52 +232,51 @@ 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
PlayersMap[Player] = nil
end)
local function StepReplication()
for Player, Send in PlayersMap do
if Send.Cursor <= 0 then
continue
end
for Player, Send in PlayersMap do
if Send.Cursor <= 0 then
continue
end
local Buffer = buffer.create(Send.Cursor)
buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
Reliable:FireClient(Player, Buffer, Send.Instances)
local Buffer = buffer.create(Send.Cursor)
buffer.copy(Buffer, 0, Send.Buffer, 0, Send.Cursor)
Reliable:FireClient(Player, Buffer, Send.Instances)
Send.Size = 64
Send.Cursor = 0
Send.Buffer = buffer.create(64)
table.clear(Send.Instances)
end
Send.Size = 64
Send.Cursor = 0
Send.Buffer = buffer.create(64)
table.clear(Send.Instances)
end
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,4 +1,4 @@
_G.JECS_DEBUG = true
_G.JECS_HI_COMPONENT_ID = 32
require(game:GetService("ReplicatedStorage").ecs)
return
_G.JECS_DEBUG = true
_G.JECS_HI_COMPONENT_ID = 32
require(game:GetService("ReplicatedStorage").ecs)
return

View file

@ -1,33 +1,33 @@
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
local world = std.world
local function start(modules)
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
for _, module in modules do
require(module)(scheduler)
end
local events = scheduler.collect.all()
scheduler.systems.begin(events)
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
for _, module in modules do
require(module)(scheduler)
end
local events = scheduler.collect.all()
scheduler.systems.begin(events)
jabby.set_check_function(function(player)
return true
end)
if RunService:IsClient() then
local client = jabby.obtain_client()
local dtor
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.F4 then
if dtor then
dtor()
end
dtor = client.spawn_app(client.apps.home)
end
end)
jabby.set_check_function(function(player)
return true
end)
if RunService:IsClient() then
local client = jabby.obtain_client()
local dtor
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.F4 then
if dtor then
dtor()
end
dtor = client.spawn_app(client.apps.home)
end
end)
end
end

View file

@ -8,33 +8,33 @@ local FAILURE = 1
local RUNNING = 2
local function SEQUENCE(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == FAILURE or status == RUNNING then
return status
end
end
return SUCCESS
end
return function(...)
for _, node in nodes do
local status = node(...)
if status == FAILURE or status == RUNNING then
return status
end
end
return SUCCESS
end
end
local function FALLBACK(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == SUCCESS or status == RUNNING then
return status
end
end
return FAILURE
end
return function(...)
for _, node in nodes do
local status = node(...)
if status == SUCCESS or status == RUNNING then
return status
end
end
return FAILURE
end
end
local bt = {
SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK,
RUNNING = RUNNING
SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK,
RUNNING = RUNNING,
}
return bt

View file

@ -1,133 +1,139 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.World
type Tracker<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
type Tracker<T> = {
track: (
world: World,
fn: (
changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
}
type Entity<T = any> = number & { __nominal_type_dont_use: T }
local function diff(a, b)
local size = 0
for k, v in a do
if b[k] ~= v then
return true
end
size += 1
end
for k, v in b do
size -= 1
end
local size = 0
for k, v in a do
if b[k] ~= v then
return true
end
size += 1
end
for k, v in b do
size -= 1
end
if size ~= 0 then
return true
end
if size ~= 0 then
return true
end
return false
return false
end
local function ChangeTracker<T>(world: World, T: Entity<T>): Tracker<T>
local sparse = world.entityIndex.sparse
local PreviousT = jecs.pair(jecs.Rest, T)
local add = {}
local added
local removed
local is_trivial
local sparse = world.entityIndex.sparse
local PreviousT = jecs.pair(jecs.Rest, T)
local add = {}
local added
local removed
local is_trivial
local function changes_added()
added = true
local q = world:query(T):without(PreviousT):drain()
return function()
local id, data = q.next()
if not id then
return nil
end
local function changes_added()
added = true
local q = world:query(T):without(PreviousT):drain()
return function()
local id, data = q.next()
if not id then
return nil
end
is_trivial = typeof(data) ~= "table"
is_trivial = typeof(data) ~= "table"
add[id] = data
add[id] = data
return id, data
end
end
return id, data
end
end
local function changes_changed()
local q = world:query(T, PreviousT):drain()
local function changes_changed()
local q = world:query(T, PreviousT):drain()
return function()
local id, new, old = q.next()
while true do
if not id then
return nil
end
return function()
local id, new, old = q.next()
while true do
if not id then
return nil
end
if not is_trivial then
if diff(new, old) then
break
end
elseif new ~= old then
break
end
if not is_trivial then
if diff(new, old) then
break
end
elseif new ~= old then
break
end
id, new, old = q.next()
end
id, new, old = q.next()
end
local record = sparse[id]
local archetype = record.archetype
local column = archetype.records[PreviousT].column
local data = if is_trivial then new else table.clone(new)
archetype.columns[column][record.row] = data
local record = sparse[id]
local archetype = record.archetype
local column = archetype.records[PreviousT].column
local data = if is_trivial then new else table.clone(new)
archetype.columns[column][record.row] = data
return id, old, new
end
end
return id, old, new
end
end
local function changes_removed()
removed = true
local function changes_removed()
removed = true
local q = world:query(PreviousT):without(T):drain()
return function()
local id = q.next()
if id then
world:remove(id, PreviousT)
end
return id
end
end
local q = world:query(PreviousT):without(T):drain()
return function()
local id = q.next()
if id then
world:remove(id, PreviousT)
end
return id
end
end
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}
local function track(fn)
added = false
removed = false
local function track(fn)
added = false
removed = false
fn(changes)
fn(changes)
if not added then
for _ in changes_added() do
end
end
if not added then
for _ in changes_added() do
end
end
if not removed then
for _ in changes_removed() do
end
end
if not removed then
for _ in changes_removed() do
end
end
for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end
end
for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end
end
local tracker = { track = track }
local tracker = { track = track }
return tracker
return tracker
end
return ChangeTracker

View file

@ -1,25 +1,26 @@
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,
Model: Entity<Model>,
Player: Entity,
Target: Entity,
Transform: Entity<CFrame>,
Velocity: Entity<number>,
Previous: Entity,
} = {
Character = world:component(),
Mob = world:component(),
Model = world:component(),
Player = world:component(),
Target = world:component(),
Transform = world:component(),
Velocity = world:component(),
Previous = world:component(),
}
Character: Entity<Model>,
Mob: Entity,
Model: Entity<Model>,
Player: Entity,
Target: Entity,
Transform: Entity<CFrame>,
Velocity: Entity<number>,
Previous: Entity,
} =
{
Character = world:component(),
Mob = world:component(),
Model = world:component(),
Player = world:component(),
Target = world:component(),
Transform = world:component(),
Velocity = world:component(),
Previous = world:component(),
}
return table.freeze(components)

View file

@ -1,11 +1,11 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local singleton = world:entity()
local function ctx()
-- Cannot cache handles because they will get invalidated
return handle(singleton)
-- Cannot cache handles because they will get invalidated
return handle(singleton)
end
return ctx

View file

@ -2,55 +2,55 @@ local jecs = require(game:GetService("ReplicatedStorage").ecs)
local world = require(script.Parent.world)
type Handle = {
has: (self: Handle, id: jecs.Entity) -> boolean,
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
has: (self: Handle, id: jecs.Entity) -> boolean,
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,
}
local handle: (e: jecs.Entity) -> Handle
do
local e
local function has(_, id)
return world:has(e, id)
end
local function get(_, id)
return world:get(e, id)
end
local function set(self, id, value)
world:set(e, id, value)
return self
end
local function add(self, id)
world:add(e, id)
return self
end
local function clear(self)
world:clear(e)
return self
end
local function delete(self)
world:delete(e)
end
local function id()
return e
end
local e
local function has(_, id)
return world:has(e, id)
end
local function get(_, id)
return world:get(e, id)
end
local function set(self, id, value)
world:set(e, id, value)
return self
end
local function add(self, id)
world:add(e, id)
return self
end
local function clear(self)
world:clear(e)
return self
end
local function delete(self)
world:delete(e)
end
local function id()
return e
end
local entity = {
has = has,
get = get,
set = set,
add = add,
clear = clear,
id = id,
}
local entity = {
has = has,
get = get,
set = set,
add = add,
clear = clear,
id = id,
}
function handle(id)
e = id
return entity
end
function handle(id)
e = id
return entity
end
end
return handle

View file

@ -1,32 +1,32 @@
--!native
--!optimize 2
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs)
local function create_cache(hook)
local columns = setmetatable({}, {
__index = function(self, component)
local column = {}
self[component] = column
return column
end
})
return function(world, component, fn)
local column = columns[component]
table.insert(column, fn)
world:set(component, hook, function(entity, value)
for _, callback in column do
callback(entity, value)
end
end)
end
end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove)
}
return hooks
--!native
--!optimize 2
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs)
local function create_cache(hook)
local columns = setmetatable({}, {
__index = function(self, component)
local column = {}
self[component] = column
return column
end,
})
return function(world, component, fn)
local column = columns[component]
table.insert(column, fn)
world:set(component, hook, function(entity, value)
for _, callback in column do
callback(entity, value)
end
end)
end
end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove),
}
return hooks

View file

@ -7,19 +7,19 @@ local Scheduler = require(script.scheduler)
export type Scheduler = Scheduler.Scheduler
local std = {
ChangeTracker = require(script.changetracker),
Scheduler = Scheduler,
bt = require(script.bt),
collect = require(script.collect),
components = require(script.components),
ctx = require(script.ctx),
handle = require(script.handle),
interval = require(script.interval),
ref = require(script.ref),
world = world :: World,
pair = jecs.pair,
__ = jecs.w,
hooks = require(script.hooks)
ChangeTracker = require(script.changetracker),
Scheduler = Scheduler,
bt = require(script.bt),
collect = require(script.collect),
components = require(script.components),
ctx = require(script.ctx),
handle = require(script.handle),
interval = require(script.interval),
ref = require(script.ref),
world = world :: World,
pair = jecs.pair,
__ = jecs.w,
hooks = require(script.hooks),
}
return std

View file

@ -1,19 +1,19 @@
local function interval(s)
local pin
local pin
local function throttle()
if not pin then
pin = os.clock()
end
local function throttle()
if not pin then
pin = os.clock()
end
local elapsed = os.clock() - pin > s
if elapsed then
pin = os.clock()
end
local elapsed = os.clock() - pin > s
if elapsed then
pin = os.clock()
end
return elapsed
end
return throttle
return elapsed
end
return throttle
end
return interval

View file

@ -1,24 +1,24 @@
local world = require(script.Parent.world)
local handle = require(script.Parent.handle)
local world = require(script.Parent.world)
local refs = {}
local function fini(key)
return function()
refs[key] = nil
end
return function()
refs[key] = nil
end
end
local function ref(key): (handle.Handle, (() -> ())?)
if not key then
return handle(world:entity())
end
local e = refs[key]
if not e then
e = world:entity()
refs[key] = e
end
-- Cannot cache handles because they will get invalidated
return handle(e), fini(key)
if not key then
return handle(world:entity())
end
local e = refs[key]
if not e then
e = world:entity()
refs[key] = e
end
-- Cannot cache handles because they will get invalidated
return handle(e), fini(key)
end
return ref

View file

@ -1,8 +1,8 @@
local reserved = 0
local function reserve()
reserved += 1
return reserved
reserved += 1
return reserved
end
-- If you don't like passing around a world singleton
@ -19,13 +19,13 @@ end
world:set(e, components.Transform, CFrame)
]]
local function register(world)
for _ = 1, reserved do
world:component()
end
return world
for _ = 1, reserved do
world:component()
end
return world
end
return {
reserve = reserve,
register = register,
reserve = reserve,
register = register,
}

View file

@ -7,245 +7,250 @@ 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) -> (),
id: number,
callback: (world: World) -> (),
id: number,
}
type Systems = { System }
type Events = {
RenderStepped: Systems,
Heartbeat: Systems
RenderStepped: Systems,
Heartbeat: Systems,
}
export type Scheduler = {
components: {
Disabled: Entity,
System: Entity<System>,
Phase: Entity,
DependsOn: Entity
},
components: {
Disabled: Entity,
System: Entity<System>,
Phase: Entity,
DependsOn: Entity,
},
collect: {
under_event: (event: Entity) -> Systems,
all: () -> Events
},
collect: {
under_event: (event: Entity) -> Systems,
all: () -> Events,
},
systems: {
begin: (events: Events) -> { [Entity]: thread },
new: (callback: (dt: number) -> (), phase: Entity) -> Entity
},
systems: {
begin: (events: Events) -> { [Entity]: thread },
new: (callback: (dt: number) -> (), phase: Entity) -> Entity,
},
phases: {
RenderStepped: Entity,
Heartbeat: Entity,
},
phases: {
RenderStepped: Entity,
Heartbeat: Entity,
},
phase: (after: Entity) -> Entity,
phase: (after: Entity) -> Entity,
debugging: boolean,
debugging: boolean,
}
local scheduler_new: (w: World, components: { [string]: Entity }) -> Scheduler
do
local world: World
local Disabled: Entity
local System: Entity<System>
local DependsOn: Entity
local Phase: Entity
local Event: Entity<RBXScriptSignal>
local world: World
local Disabled: Entity
local System: Entity<System>
local DependsOn: Entity
local Phase: Entity
local Event: Entity<RBXScriptSignal>
local scheduler
local scheduler
local RenderStepped
local Heartbeat
local PreAnimation
local PreSimulation
local RenderStepped
local Heartbeat
local PreAnimation
local PreSimulation
local sys: System
local dt
local sys: System
local dt
local function run()
local id = sys.id
local system_data = scheduler.system_data[id]
if system_data.paused then return end
local function run()
local id = sys.id
local system_data = scheduler.system_data[id]
if system_data.paused then
return
end
scheduler:mark_system_frame_start(id)
sys.callback(dt)
scheduler:mark_system_frame_end(id)
end
scheduler:mark_system_frame_start(id)
sys.callback(dt)
scheduler:mark_system_frame_end(id)
end
local function panic(str)
-- We don't want to interrupt the loop when we error
task.spawn(error, str)
end
local function begin(events: { Systems })
local threads = {}
for event, systems in events do
if not event then continue end
local event_name = tostring(event)
threads[event] = task.spawn(function()
while true do
dt = event:Wait()
local function panic(str)
-- We don't want to interrupt the loop when we error
task.spawn(error, str)
end
local function begin(events: { Systems })
local threads = {}
for event, systems in events do
if not event then
continue
end
local event_name = tostring(event)
threads[event] = task.spawn(function()
while true do
dt = event:Wait()
debug.profilebegin(event_name)
for _, s in systems do
sys = s
local didNotYield, why = xpcall(function()
for _ in run do break end
end, debug.traceback)
debug.profilebegin(event_name)
for _, s in systems do
sys = s
local didNotYield, why = xpcall(function()
for _ in run do
break
end
end, debug.traceback)
if didNotYield then
continue
end
if didNotYield then
continue
end
if string.find(why, "thread is not yieldable") then
panic("Not allowed to yield in the systems."
.. "\n"
.. "System: "
.. debug.info(s.callback, "n")
.. " has been ejected"
)
continue
end
panic(why)
end
debug.profileend()
end
end)
end
return threads
end
if string.find(why, "thread is not yieldable") then
panic(
"Not allowed to yield in the systems."
.. "\n"
.. "System: "
.. debug.info(s.callback, "n")
.. " has been ejected"
)
continue
end
panic(why)
end
debug.profileend()
end
end)
end
return threads
end
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
local phase_name = world:get(phase, Name)
for _, s in world:query(System):with(pair(DependsOn, phase)) do
table.insert(systems, {
id = scheduler:register_system({
name = s.name,
phase = phase_name
}),
callback = s.callback
})
end
for after in world:query(Phase):with(pair(DependsOn, phase)) do
scheduler_collect_systems_under_phase_recursive(systems, after)
end
end
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
local phase_name = world:get(phase, Name)
for _, s in world:query(System):with(pair(DependsOn, phase)) do
table.insert(systems, {
id = scheduler:register_system({
name = s.name,
phase = phase_name,
}),
callback = s.callback,
})
end
for after in world:query(Phase):with(pair(DependsOn, phase)) do
scheduler_collect_systems_under_phase_recursive(systems, after)
end
end
local function scheduler_collect_systems_under_event(event)
local systems = {}
scheduler_collect_systems_under_phase_recursive(systems, event)
return systems
end
local function scheduler_collect_systems_under_event(event)
local systems = {}
scheduler_collect_systems_under_phase_recursive(systems, event)
return systems
end
local function scheduler_collect_systems_all()
local events = {}
for phase, event in world:query(Event):with(Phase) do
events[event] = scheduler_collect_systems_under_event(phase)
end
return events
end
local function scheduler_collect_systems_all()
local events = {}
for phase, event in world:query(Event):with(Phase) do
events[event] = scheduler_collect_systems_under_event(phase)
end
return events
end
local function scheduler_phase_new(after)
local phase = world:entity()
world:add(phase, Phase)
local dependency = pair(DependsOn, after)
world:add(phase, dependency)
return phase
end
local function scheduler_phase_new(after)
local phase = world:entity()
world:add(phase, Phase)
local dependency = pair(DependsOn, after)
world:add(phase, dependency)
return phase
end
local function scheduler_systems_new(callback, phase)
local system = world:entity()
local name = debug.info(callback, "n")
world:set(system, System, { callback = callback, name = name })
world:add(system, pair(DependsOn, phase))
return system
end
local function scheduler_systems_new(callback, phase)
local system = world:entity()
local name = debug.info(callback, "n")
world:set(system, System, { callback = callback, name = name })
world:add(system, pair(DependsOn, phase))
return system
end
function scheduler_new(w: World, components: { [string]: Entity })
world = w
Disabled = world:component()
System = world:component()
Phase = world:component()
DependsOn = world:component()
Event = world:component()
function scheduler_new(w: World, components: { [string]: Entity })
world = w
Disabled = world:component()
System = world:component()
Phase = world:component()
DependsOn = world:component()
Event = world:component()
RenderStepped = world:component()
Heartbeat = world:component()
PreSimulation = world:component()
PreAnimation = world:component()
RenderStepped = world:component()
Heartbeat = world:component()
PreSimulation = world:component()
PreAnimation = world:component()
local RunService = game:GetService("RunService")
if RunService:IsClient() then
world:add(RenderStepped, Phase)
world:set(RenderStepped, Event, RunService.RenderStepped)
end
local RunService = game:GetService("RunService")
if RunService:IsClient() then
world:add(RenderStepped, Phase)
world:set(RenderStepped, Event, RunService.RenderStepped)
end
world:add(Heartbeat, Phase)
world:set(Heartbeat, Event, RunService.Heartbeat)
world:add(Heartbeat, Phase)
world:set(Heartbeat, Event, RunService.Heartbeat)
world:add(PreSimulation, Phase)
world:set(PreSimulation, Event, RunService.PreSimulation)
world:add(PreSimulation, Phase)
world:set(PreSimulation, Event, RunService.PreSimulation)
world:add(PreAnimation, Phase)
world:set(PreAnimation, Event, RunService.PreAnimation)
world:add(PreAnimation, Phase)
world:set(PreAnimation, Event, RunService.PreAnimation)
for name, component in components do
world:set(component, Name, name)
end
for name, component in components do
world:set(component, Name, name)
end
table.insert(jabby.public, {
class_name = "World",
name = "MyWorld",
world = world,
debug = Name,
entities = {}
})
table.insert(jabby.public, {
class_name = "World",
name = "MyWorld",
world = world,
debug = Name,
entities = {},
})
jabby.public.updated = true
scheduler = jabby.scheduler.create("scheduler")
jabby.public.updated = true
scheduler = jabby.scheduler.create("scheduler")
table.insert(jabby.public, scheduler)
table.insert(jabby.public, scheduler)
return {
phase = scheduler_phase_new,
return {
phase = scheduler_phase_new,
phases = {
RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation,
},
phases = {
RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation
},
world = world,
world = world,
components = {
DependsOn = DependsOn,
Disabled = Disabled,
Phase = Phase,
System = System,
},
components = {
DependsOn = DependsOn,
Disabled = Disabled,
Phase = Phase,
System = System,
},
collect = {
under_event = scheduler_collect_systems_under_event,
all = scheduler_collect_systems_all,
},
collect = {
under_event = scheduler_collect_systems_under_event,
all = scheduler_collect_systems_all
},
systems = {
new = scheduler_systems_new,
begin = begin
}
}
end
systems = {
new = scheduler_systems_new,
begin = begin,
},
}
end
end
return {
new = scheduler_new
new = scheduler_new,
}

View file

@ -21,58 +21,53 @@ local Player = cts.Player
local Character = cts.Character
local function mobsMove(dt: number)
local targets = {}
for _, character in world:query(Character):with(Player):iter() do
table.insert(targets, (character.PrimaryPart :: Part).Position)
end
local targets = {}
for _, character in world:query(Character):with(Player):iter() do
table.insert(targets, (character.PrimaryPart :: Part).Position)
end
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
local cf = transform.new
local p = cf.Position
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
local cf = transform.new
local p = cf.Position
local target
local closest
local target
local closest
for _, pos in targets do
local distance = (p - pos).Magnitude
if not target or distance < closest then
target = pos
closest = distance
end
end
for _, pos in targets do
local distance = (p - pos).Magnitude
if not target or distance < closest then
target = pos
closest = distance
end
end
if not target then
continue
end
if not target then
continue
end
local moving = CFrame.new(p + (target - p).Unit * dt * v)
transform.new = moving
blink.UpdateTransform.FireAll(mob, moving)
end
local moving = CFrame.new(p + (target - p).Unit * dt * v)
transform.new = moving
blink.UpdateTransform.FireAll(mob, moving)
end
end
local throttle = interval(5)
local function spawnMobs()
if throttle() then
local p = Vector3.new(0, 5, 0)
local cf = CFrame.new(p)
local v = 5
if throttle() then
local p = Vector3.new(0, 5, 0)
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
blink.SpawnMob.FireAll(id, cf, v)
end
end
return function(scheduler: std.Scheduler)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(mobsMove, phases.Heartbeat)
system_new(spawnMobs, phases.Heartbeat)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(mobsMove, phases.Heartbeat)
system_new(spawnMobs, phases.Heartbeat)
end

View file

@ -16,31 +16,28 @@ local world: std.World = std.world
local conn = {}
local function players()
for _, player in playersAdded do
world:set(
std.world:entity(),
cts.Transform
)
for _, player in playersAdded do
world:set(std.world:entity(), cts.Transform)
local e = ref(player.UserId):set(Player, player)
local characterAdd = player.CharacterAdded
conn[e.id()] = characterAdd:Connect(function(rig)
while rig.Parent ~= workspace do
task.wait()
end
e:set(Character, rig)
end)
end
local e = ref(player.UserId):set(Player, player)
local characterAdd = player.CharacterAdded
conn[e.id()] = characterAdd:Connect(function(rig)
while rig.Parent ~= workspace do
task.wait()
end
e:set(Character, rig)
end)
end
for _, player in playersRemoved do
local id = ref(player.UserId):clear().id()
conn[id]:Disconnect()
conn[id] = nil
end
for _, player in playersRemoved do
local id = ref(player.UserId):clear().id()
conn[id]:Disconnect()
conn[id] = nil
end
end
return function(scheduler: std.Scheduler)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(players, phases.Heartbeat)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(players, phases.Heartbeat)
end

View file

@ -10,30 +10,30 @@ local Model = cts.Model
local Transform = cts.Transform
local function move(dt: number)
for _, transform, model in world:query(Transform, Model):iter() do
local cf = transform.new
if cf ~= transform.old then
local origo = model.PrimaryPart.CFrame
model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
transform.old = cf
end
end
for _, transform, model in world:query(Transform, Model):iter() do
local cf = transform.new
if cf ~= transform.old then
local origo = model.PrimaryPart.CFrame
model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
transform.old = cf
end
end
end
local function syncTransforms()
for _, id, cf in blink.UpdateTransform.Iter() do
local e = ref("server-"..id)
local transform = e:get(cts.Transform)
if not transform then
continue
end
transform.new = cf
end
for _, id, cf in blink.UpdateTransform.Iter() do
local e = ref("server-" .. id)
local transform = e:get(cts.Transform)
if not transform then
continue
end
transform.new = cf
end
end
return function(scheduler: std.Scheduler)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(move, phases.Heartbeat)
system_new(syncTransforms, phases.RenderStepped)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(move, phases.Heartbeat)
system_new(syncTransforms, phases.RenderStepped)
end

View file

@ -6,26 +6,26 @@ local world = std.world
local cts = std.components
local function syncMobs()
for _, id, cf, vel in blink.SpawnMob.Iter() do
local part = Instance.new("Part")
part.Size = Vector3.one * 5
part.BrickColor = BrickColor.Red()
part.Anchored = true
local model = Instance.new("Model")
model.PrimaryPart = part
part.Parent = model
model.Parent = workspace
for _, id, cf, vel in blink.SpawnMob.Iter() do
local part = Instance.new("Part")
part.Size = Vector3.one * 5
part.BrickColor = BrickColor.Red()
part.Anchored = true
local model = Instance.new("Model")
model.PrimaryPart = part
part.Parent = model
model.Parent = workspace
ref("server-"..id)
:set(cts.Transform, { new = cf, old = cf })
:set(cts.Velocity, vel)
:set(cts.Model, model)
:add(cts.Mob)
end
ref("server-" .. id)
:set(cts.Transform, { new = cf, old = cf })
:set(cts.Velocity, vel)
:set(cts.Model, model)
:add(cts.Mob)
end
end
return function(scheduler: std.Scheduler)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(syncMobs, phases.RenderStepped)
local phases = scheduler.phases
local system_new = scheduler.systems.new
system_new(syncMobs, phases.RenderStepped)
end

View file

@ -23,20 +23,18 @@ 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)
-- Iterate all entities with Position
for entity, p in world:query(Position) do
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
end
-- Output:

View file

@ -11,67 +11,55 @@ local Moon = world:component()
local Vector3
do
Vector3 = {}
Vector3.__index = Vector3
Vector3 = {}
Vector3.__index = Vector3
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__add(left, right)
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
)
end
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)
end
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
end
local function path(entity)
local str = world:get(entity, Name)
local parent
while true do
parent = world:parent(entity)
if not parent then
break
end
entity = parent
str = world:get(parent, Name) .. "/" .. str
end
return str
local str = world:get(entity, Name)
local parent
while true do
parent = world:parent(entity)
if not parent then
break
end
entity = parent
str = world:get(parent, Name) .. "/" .. str
end
return str
end
local function iterate(entity, parent)
local p = world:get(entity, Position)
local actual = p + parent
print(path(entity))
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
local p = world:get(entity, Position)
local actual = p + parent
print(path(entity))
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
for child in world:query(pair(ChildOf, entity)) do
--print(world:get(child, Name))
iterate(child, actual)
end
for child in world:query(pair(ChildOf, entity)) do
--print(world:get(child, Name))
iterate(child, actual)
end
end
local sun = world:entity()
@ -79,36 +67,35 @@ world:add(sun, Star)
world:set(sun, Position, Vector3.one)
world:set(sun, Name, "Sun")
do
local earth = world:entity()
world:set(earth, Name, "Earth")
world:add(earth, pair(ChildOf, sun))
world:add(earth, Planet)
world:set(earth, Position, Vector3.one * 3)
local earth = world:entity()
world:set(earth, Name, "Earth")
world:add(earth, pair(ChildOf, sun))
world:add(earth, Planet)
world:set(earth, Position, Vector3.one * 3)
do
local moon = world:entity()
world:set(moon, Name, "Moon")
world:add(moon, pair(ChildOf, earth))
world:add(moon, Moon)
world:set(moon, Position, Vector3.one * 0.1)
do
local moon = world:entity()
world:set(moon, Name, "Moon")
world:add(moon, pair(ChildOf, earth))
world:add(moon, Moon)
world:set(moon, Position, Vector3.one * 0.1)
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
end
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
end
local venus = world:entity()
world:set(venus, Name, "Venus")
world:add(venus, pair(ChildOf, sun))
world:add(venus, Planet)
world:set(venus, Position, Vector3.one * 2)
local venus = world:entity()
world:set(venus, Name, "Venus")
world:add(venus, pair(ChildOf, sun))
world:add(venus, Planet)
world:set(venus, Position, Vector3.one * 2)
local mercury = world:entity()
world:set(mercury, Name, "Mercury")
world:add(mercury, pair(ChildOf, sun))
world:add(mercury, Planet)
world:set(mercury, Position, Vector3.one)
local mercury = world:entity()
world:set(mercury, Name, "Mercury")
world:add(mercury, pair(ChildOf, sun))
world:add(mercury, Planet)
world:set(mercury, Position, Vector3.one)
iterate(sun, Vector3.zero)
iterate(sun, Vector3.zero)
end
-- Output:

View file

@ -1,21 +1,21 @@
local jecs = require("@jecs")
local world = jecs.World.new()
local Model = world:component()
-- It is important to define hooks for the component before the component is ever used
-- otherwise the hooks will never invoke!
world:set(Model, jecs.OnRemove, function(entity)
-- OnRemove is invoked before the component and its value is removed
-- which provides a stable reference to the entity at deletion.
-- This means that it is safe to retrieve the data inside of a hook
local model = world:get(entity, Model)
model:Destroy()
end)
world:set(Model, jecs.OnSet, function(entity, model)
-- OnSet is invoked after the data has been assigned.
-- It also returns the data for faster access.
-- There may be some logic to do some side effects on reassignments
model:SetAttribute("entityId", entity)
end)
local jecs = require("@jecs")
local world = jecs.World.new()
local Model = world:component()
-- It is important to define hooks for the component before the component is ever used
-- otherwise the hooks will never invoke!
world:set(Model, jecs.OnRemove, function(entity)
-- OnRemove is invoked before the component and its value is removed
-- which provides a stable reference to the entity at deletion.
-- This means that it is safe to retrieve the data inside of a hook
local model = world:get(entity, Model)
model:Destroy()
end)
world:set(Model, jecs.OnSet, function(entity, model)
-- OnSet is invoked after the data has been assigned.
-- It also returns the data for faster access.
-- There may be some logic to do some side effects on reassignments
model:SetAttribute("entityId", entity)
end)

View file

@ -7,41 +7,29 @@ local Name = world:component()
local Vector3
do
Vector3 = {}
Vector3.__index = Vector3
Vector3 = {}
Vector3.__index = Vector3
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__add(left, right)
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
)
end
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)
end
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
end
-- Create a few test entities for a Position, Velocity query
@ -62,12 +50,12 @@ world:set(e3, Position, Vector3.new(10, 20, 30))
-- Create an uncached query for Position, Velocity.
for entity, p, v in world:query(Position, Velocity) do
-- Iterate entities matching the query
p.X += v.X
p.Y += v.Y
p.Z += v.Z
-- Iterate entities matching the query
p.X += v.X
p.Y += v.Y
p.Z += v.Z
print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
end
-- Output:

View file

@ -2,179 +2,173 @@ local jecs = require("@jecs")
type World = jecs.WorldShim
type Tracker<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
type Tracker<T> = {
track: (
world: World,
fn: (
changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T),
}
) -> ()
) -> (),
}
local function diff(a, b)
local size = 0
for k, v in a do
if b[k] ~= v then
return true
end
size += 1
end
for k, v in b do
size -= 1
end
local size = 0
for k, v in a do
if b[k] ~= v then
return true
end
size += 1
end
for k, v in b do
size -= 1
end
if size ~= 0 then
return true
end
if size ~= 0 then
return true
end
return false
return false
end
type Entity<T> = number & { __nominal_type_dont_use: T }
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
local PreviousT = jecs.pair(jecs.Rest, T)
local add = {}
local added
local removed
local is_trivial
local PreviousT = jecs.pair(jecs.Rest, T)
local add = {}
local added
local removed
local is_trivial
local function changes_added()
added = true
local q = world:query(T):without(PreviousT):drain()
return function()
local id, data = q.next()
if not id then
return nil
end
local function changes_added()
added = true
local q = world:query(T):without(PreviousT):drain()
return function()
local id, data = q.next()
if not id then
return nil
end
is_trivial = typeof(data) ~= "table"
is_trivial = typeof(data) ~= "table"
add[id] = data
add[id] = data
return id, data
end
end
return id, data
end
end
local function changes_changed()
local q = world:query(T, PreviousT):drain()
local function changes_changed()
local q = world:query(T, PreviousT):drain()
return function()
local id, new, old = q.next()
while true do
if not id then
return nil
end
return function()
local id, new, old = q.next()
while true do
if not id then
return nil
end
if not is_trivial then
if diff(new, old) then
break
end
elseif new ~= old then
break
end
if not is_trivial then
if diff(new, old) then
break
end
elseif new ~= old then
break
end
id, new, old = q.next()
end
id, new, old = q.next()
end
add[id] = new
add[id] = new
return id, old, new
end
end
return id, old, new
end
end
local function changes_removed()
removed = true
local function changes_removed()
removed = true
local q = world:query(PreviousT):without(T):drain()
return function()
local id = q.next()
if id then
world:remove(id, PreviousT)
end
return id
end
end
local q = world:query(PreviousT):without(T):drain()
return function()
local id = q.next()
if id then
world:remove(id, PreviousT)
end
return id
end
end
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}
local function track(fn)
added = false
removed = false
local function track(fn)
added = false
removed = false
fn(changes)
fn(changes)
if not added then
for _ in changes_added() do
end
end
if not added then
for _ in changes_added() do
end
end
if not removed then
for _ in changes_removed() do
end
end
if not removed then
for _ in changes_removed() do
end
end
for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end
end
for e, data in add do
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end
end
local tracker = { track = track }
local tracker = { track = track }
return tracker
return tracker
end
local Vector3
do
Vector3 = {}
Vector3.__index = Vector3
Vector3 = {}
Vector3.__index = Vector3
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.__add(left, right)
return Vector3.new(
left.X + right.X,
left.Y + right.Y,
left.Z + right.Z
)
end
function Vector3.__add(left, right)
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
)
end
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)
end
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
end
local world = jecs.World.new()
local Name = world:component()
local function named(ctr, name)
local e = ctr(world)
world:set(e, Name, name)
return e
local e = ctr(world)
world:set(e, Name, name)
return e
end
local function name(e)
return world:get(e, Name)
return world:get(e, Name)
end
local Position = named(world.component, "Position")
@ -189,50 +183,50 @@ local e2 = named(world.entity, "e2")
world:set(e2, Position, Vector3.new(10, 20, 30))
PositionTracker.track(function(changes)
-- You can iterate over different types of changes: Added, Changed, Removed
-- You can iterate over different types of changes: Added, Changed, Removed
-- added queries for every entity with a new Position component
for e, p in changes.added() do
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
end
-- added queries for every entity with a new Position component
for e, p in changes.added() do
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
end
-- changed queries for entities who's changed their data since
-- last was it tracked
for _ in changes.changed() do
print([[This won't print because it is the first time
-- changed queries for entities who's changed their data since
-- last was it tracked
for _ in changes.changed() do
print([[This won't print because it is the first time
we are tracking the Position component]])
end
end
-- removed queries for entities who's removed their Position component
-- since last it was tracked
for _ in changes.removed() do
print([[This won't print because it is the first time
-- removed queries for entities who's removed their Position component
-- since last it was tracked
for _ in changes.removed() do
print([[This won't print because it is the first time
we are tracking the Position component]])
end
end
end)
world:set(e1, Position, Vector3.new(1, 1, 2) * 999)
PositionTracker.track(function(changes)
for e, p in changes.added() do
print([[This won't never print no Position component was added
for e, p in changes.added() do
print([[This won't never print no Position component was added
since last time we tracked]])
end
end
for e, old, new in changes.changed() do
print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
end
for e, old, new in changes.changed() do
print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
end
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
-- This ensures you will not have any off-by-one frame errors.
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
-- This ensures you will not have any off-by-one frame errors.
end)
world:remove(e2, Position)
PositionTracker.track(function(changes)
for e in changes.removed() do
print(`Position was removed from {name(e)}`)
end
for e in changes.removed() do
print(`Position was removed from {name(e)}`)
end
end)
-- Output:

View file

@ -1,130 +1,125 @@
local jecs = require("@jecs")
local pair = jecs.pair
local ChildOf = jecs.ChildOf
local __ = jecs.Wildcard
local Name = jecs.Name
local world = jecs.World.new()
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
}>
local PrimaryPart = world:component() :: Id<Part>
local local_player = game:GetService("Players").LocalPlayer
local function distance(a: Vector3, b: Vector3)
return (b - a).Magnitude
end
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
local to_target = b - a
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
local angle_difference = math.abs(forward_angle - angle_to_target)
if angle_difference > 180 then
angle_difference = 360 - angle_difference
end
return angle_difference <= (fov_angle / 2)
end
local map = {}
local grid = 50
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
voxel_id = world:entity()
world:add(voxel_id, Voxel)
world:set(voxel_id, Position, hash)
map[hash] = voxel_id
end
if prev_voxel_id ~= nil then
world:remove(source, pair(ChildOf, prev_voxel_id))
end
world:add(source, pair(ChildOf, voxel_id))
end
local function reconcile_client_owned_assembly_to_voxel(dt: number)
for e, part, position in world:query(PrimaryPart, Position) do
local p = part.Position
if p ~= position then
world:set(e, Position, p)
local voxel_id = world:target(e, ChildOf, 0)
if map[p // grid] == voxel_id then
continue
end
add_to_voxel(e, p, voxel_id)
end
end
end
local function update_camera_direction(dt: number)
for _, perception in world:query(Perception) do
perception.dir = workspace.CurrentCamera.CFrame.LookVector
end
end
local function perceive_enemies(dt: number)
local it = world:query(Perception, Position, PrimaryPart)
-- There is only going to be one entity matching the query
local e, self_perception, self_position, self_primary_part = it()
local voxel_id = map[self_primary_part.Position // grid]
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
for enemy, target_position in nearby_entities_query do
if distance(self_position, target_position) > self_perception.range then
continue
end
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
end
end
local player = world:entity()
world:set(player, Perception, {
range = 100,
fov = 90,
dir = Vector3.new(1, 0, 0)
})
world:set(player, Name, "LocalPlayer")
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
world:set(player, PrimaryPart, primary_part)
world:set(player, Position, Vector3.zero)
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
reconcile_client_owned_assembly_to_voxel(dt)
update_camera_direction(dt)
perceive_enemies(dt)
-- Output:
-- LocalPlayer can see target Enemy $1
local jecs = require("@jecs")
local pair = jecs.pair
local ChildOf = jecs.ChildOf
local __ = jecs.Wildcard
local Name = jecs.Name
local world = jecs.World.new()
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,
}>
local PrimaryPart = world:component() :: Id<Part>
local local_player = game:GetService("Players").LocalPlayer
local function distance(a: Vector3, b: Vector3)
return (b - a).Magnitude
end
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
local to_target = b - a
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
local angle_difference = math.abs(forward_angle - angle_to_target)
if angle_difference > 180 then
angle_difference = 360 - angle_difference
end
return angle_difference <= (fov_angle / 2)
end
local map = {}
local grid = 50
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
voxel_id = world:entity()
world:add(voxel_id, Voxel)
world:set(voxel_id, Position, hash)
map[hash] = voxel_id
end
if prev_voxel_id ~= nil then
world:remove(source, pair(ChildOf, prev_voxel_id))
end
world:add(source, pair(ChildOf, voxel_id))
end
local function reconcile_client_owned_assembly_to_voxel(dt: number)
for e, part, position in world:query(PrimaryPart, Position) do
local p = part.Position
if p ~= position then
world:set(e, Position, p)
local voxel_id = world:target(e, ChildOf, 0)
if map[p // grid] == voxel_id then
continue
end
add_to_voxel(e, p, voxel_id)
end
end
end
local function update_camera_direction(dt: number)
for _, perception in world:query(Perception) do
perception.dir = workspace.CurrentCamera.CFrame.LookVector
end
end
local function perceive_enemies(dt: number)
local it = world:query(Perception, Position, PrimaryPart)
-- There is only going to be one entity matching the query
local e, self_perception, self_position, self_primary_part = it()
local voxel_id = map[self_primary_part.Position // grid]
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
for enemy, target_position in nearby_entities_query do
if distance(self_position, target_position) > self_perception.range then
continue
end
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
end
end
local player = world:entity()
world:set(player, Perception, {
range = 100,
fov = 90,
dir = Vector3.new(1, 0, 0),
})
world:set(player, Name, "LocalPlayer")
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
world:set(player, PrimaryPart, primary_part)
world:set(player, Position, Vector3.zero)
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
reconcile_client_owned_assembly_to_voxel(dt)
update_camera_direction(dt)
perceive_enemies(dt)
-- Output:
-- LocalPlayer can see target Enemy $1

View file

@ -4,12 +4,12 @@ local world = jecs.World.new()
local Name = world:component()
local function named(ctr, name)
local e = ctr(world)
world:set(e, Name, name)
return e
local e = ctr(world)
world:set(e, Name, name)
return e
end
local function name(e)
return world:get(e, Name)
return world:get(e, Name)
end
local Eats = world:component()
@ -27,9 +27,9 @@ local __ = jecs.Wildcard
-- Create a query that matches edible components
for entity, amount in world:query(pair(Eats, __)) do
-- Iterate the query
local food = world:target(entity, Eats)
print(`{name(entity)} eats {amount} {name(food)}`)
-- Iterate the query
local food = world:target(entity, Eats)
print(`{name(entity)} eats {amount} {name(food)}`)
end
-- Output:

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

@ -16,7 +16,7 @@ return {
end,
yellow = function(s: any)
return `\27[33;1m{s}\27[0m`
return `\27[33;1m{s}\27[0m`
end,
red_highlight = function(s: any)
@ -30,4 +30,4 @@ return {
gray = function(s: any)
return `\27[30;1m{s}\27[0m`
end,
}
}

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,152 +1,152 @@
-- original author @centauri
local bt
do
local FAILURE = 0
local SUCCESS = 1
local RUNNING = 2
local FAILURE = 0
local SUCCESS = 1
local RUNNING = 2
local function SEQUENCE(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == FAILURE or status == RUNNING then
return status
end
end
return SUCCESS
end
end
local function FALLBACK(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == SUCCESS or status == RUNNING then
return status
end
end
return FAILURE
end
end
bt = {
SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK,
RUNNING = RUNNING,
SUCCESS = SUCCESS,
FAILURE = FAILURE,
}
local function SEQUENCE(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == FAILURE or status == RUNNING then
return status
end
end
return SUCCESS
end
end
local function FALLBACK(nodes)
return function(...)
for _, node in nodes do
local status = node(...)
if status == SUCCESS or status == RUNNING then
return status
end
end
return FAILURE
end
end
bt = {
SEQUENCE = SEQUENCE,
FALLBACK = FALLBACK,
RUNNING = RUNNING,
SUCCESS = SUCCESS,
FAILURE = FAILURE,
}
end
local SEQUENCE, FALLBACK = bt.SEQUENCE, bt.FALLBACK
local RUNNING, SUCCESS, FAILURE = bt.FAILURE, bt.SUCCESS, bt.FAILURE
local btree = FALLBACK {
SEQUENCE {
function()
return 1
end,
local btree = FALLBACK({
SEQUENCE({
function()
return 1
end,
function()
return 0
end
},
SEQUENCE {
function()
print(3)
local start = os.clock()
local now = os.clock()
while os.clock() - now < 4 do
print("yielding")
coroutine.yield()
end
return 0
end
},
function()
return 1
end
}
function()
return 0
end,
}),
SEQUENCE({
function()
print(3)
local start = os.clock()
local now = os.clock()
while os.clock() - now < 4 do
print("yielding")
coroutine.yield()
end
return 0
end,
}),
function()
return 1
end,
})
function wait(seconds)
local start = os.clock()
while os.clock() - start < seconds do end
return os.clock() - start
local start = os.clock()
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))
-- We don't want to interrupt the loop when we error
coroutine.resume(coroutine.create(function()
error(str)
end))
end
local jecs = require("@jecs")
local world = jecs.World.new()
local function Scheduler(world, ...)
local systems = { ... }
local systemsNames = {}
local N = #systems
local system
local dt
local systems = { ... }
local systemsNames = {}
local N = #systems
local system
local dt
for i, module in systems do
local sys = if typeof(module) == "function" then module else require(module)
systems[i] = sys
local file, line = debug.info(2, "sl")
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
end
for i, module in systems do
local sys = if typeof(module) == "function" then module else require(module)
systems[i] = sys
local file, line = debug.info(2, "sl")
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
end
local function run()
local name = systemsNames[system]
local function run()
local name = systemsNames[system]
--debug.profilebegin(name)
--debug.setmemorycategory(name)
system(world, dt)
--debug.profileend()
end
--debug.profilebegin(name)
--debug.setmemorycategory(name)
system(world, dt)
--debug.profileend()
end
local function loop(sinceLastFrame)
--debug.profilebegin("loop()")
local start = os.clock()
for i = N, 1, -1 do
system = systems[i]
local function loop(sinceLastFrame)
--debug.profilebegin("loop()")
local start = os.clock()
for i = N, 1, -1 do
system = systems[i]
dt = sinceLastFrame
dt = sinceLastFrame
local didNotYield, why = xpcall(function()
for _ in run do end
end, debug.traceback)
local didNotYield, why = xpcall(function()
for _ in run do
end
end, debug.traceback)
if didNotYield then
if didNotYield then
continue
end
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)
panic(why)
end
end
end
--debug.profileend()
--debug.resetmemorycategory()
return os.clock() - start
end
--debug.profileend()
--debug.resetmemorycategory()
return os.clock() - start
end
return loop
return loop
end
local co = coroutine.create(btree)
local function ai(world, dt)
coroutine.resume(co)
coroutine.resume(co)
end
local loop = Scheduler(world, ai)
while wait(0.2) do
print("frame time: ", loop(0.2))
print("frame time: ", loop(0.2))
end

View file

@ -1,47 +1,47 @@
local jecs = require("@jecs")
local function create_cache(hook)
local columns = setmetatable({}, {
__index = function(self, component)
local column = {}
self[component] = column
return column
end
})
return function(world, component, fn)
local column = columns[component]
table.insert(column, fn)
world:set(component, hook, function(entity, value)
for _, callback in column do
callback(entity, value)
end
end)
end
end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove)
}
local world = jecs.World.new()
local Position = world:component()
local order = ""
hooks.OnSet(world, Position, function(entity, value)
print("$1", entity, `({value.x}, {value.y}, {value.z})`)
order ..= "$1"
end)
hooks.OnSet(world, Position, function(entity, value)
print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`)
order ..= "-$2"
end)
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")
local jecs = require("@jecs")
local function create_cache(hook)
local columns = setmetatable({}, {
__index = function(self, component)
local column = {}
self[component] = column
return column
end,
})
return function(world, component, fn)
local column = columns[component]
table.insert(column, fn)
world:set(component, hook, function(entity, value)
for _, callback in column do
callback(entity, value)
end
end)
end
end
local hooks = {
OnSet = create_cache(jecs.OnSet),
OnAdd = create_cache(jecs.OnAdd),
OnRemove = create_cache(jecs.OnRemove),
}
local world = jecs.World.new()
local Position = world:component()
local order = ""
hooks.OnSet(world, Position, function(entity, value)
print("$1", entity, `({value.x}, {value.y}, {value.z})`)
order ..= "$1"
end)
hooks.OnSet(world, Position, function(entity, value)
print("$2", entity, `\{{value.x}, {value.y}, {value.z}}`)
order ..= "-$2"
end)
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")

View file

@ -1,56 +1,57 @@
local function calculateAverage(times)
local sum = 0
for _, time in ipairs(times) do
sum = sum + time
end
return sum / #times
local sum = 0
for _, time in ipairs(times) do
sum = sum + time
end
return sum / #times
end
-- Main logic to time the test function
local CASES = {
jecs = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
end
end,
mirror = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do end
end
end
jecs = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do
end
end
end,
mirror = function(world, ...)
for i = 1, 100 do
local q = world:query(...)
for _ in q do
end
end
end,
}
for name, fn in CASES do
local times = {}
local allocations = {}
local ecs = require("@"..name)
local world = ecs.World.new()
local A, B, C = world:component(), world:component(), world:component()
local times = {}
local allocations = {}
local ecs = require("@" .. name)
local world = ecs.World.new()
local A, B, C = world:component(), world:component(), world:component()
for i = 1, 5 do
local e = world:entity()
world:add(e, A)
world:add(e, B)
world:add(e, C)
end
for i = 1, 5 do
local e = world:entity()
world:add(e, A)
world:add(e, B)
world:add(e, C)
end
collectgarbage("collect")
local count = collectgarbage("count")
collectgarbage("collect")
local count = collectgarbage("count")
for i = 1, 50000 do
local startTime = os.clock()
fn(world, A, B, C)
local allocated = collectgarbage("count")
collectgarbage("collect")
local endTime = os.clock()
table.insert(times, endTime - startTime)
table.insert(allocations, allocated)
end
for i = 1, 50000 do
local startTime = os.clock()
fn(world, A, B, C)
local allocated = collectgarbage("count")
collectgarbage("collect")
local endTime = os.clock()
table.insert(times, endTime - startTime)
table.insert(allocations, allocated)
end
print(name, "gc cycle time", calculateAverage(times))
print(name, "memory allocated", calculateAverage(allocations))
print(name, "gc cycle time", calculateAverage(times))
print(name, "memory allocated", calculateAverage(allocations))
end

View file

@ -1,14 +1,14 @@
local testkit = require("@testkit")
local jecs = require("@jecs")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
local e = world:entity()
world:add(e, A)
world:add(e, B)
local archetype_id = world.archetypeIndex["1_2"].id
world:delete(e)
testkit.print(world)
local jecs = require("@jecs")
local testkit = require("@testkit")
local world = jecs.World.new()
local A = world:component()
local B = world:component()
local e = world:entity()
world:add(e, A)
world:add(e, B)
local archetype_id = world.archetypeIndex["1_2"].id
world:delete(e)
testkit.print(world)

File diff suppressed because it is too large Load diff

View file

@ -41,7 +41,7 @@ local color = {
end,
orange = function(s: string): string
return if disable_ansi then s else `\27[38;5;208m{s}\27[0m`
return if disable_ansi then s else `\27[38;5;208m{s}\27[0m`
end,
}
@ -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,48 +129,51 @@ 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
end
local function CASE(name: string)
skip = false
skip = false
assert(test, "no active test")
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
@ -192,7 +195,7 @@ local function CHECK<T>(value: T, stack: number?): T?
if case.result ~= FAIL then
case.result = value and PASS or FAIL
if skip then
case.result = SKIPPED
case.result = SKIPPED
end
case.line = debug.info(stack and stack + 1 or 2, "l")
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