mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Merge branch 'main' of https://github.com/Ukendio/jecs into release-0.2.6
This commit is contained in:
commit
33d3453651
14 changed files with 1442 additions and 401 deletions
71
demo.project.json
Normal file
71
demo.project.json
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"name": "demo",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"ReplicatedStorage": {
|
||||||
|
"Shared": {
|
||||||
|
"$path": "demo/src/shared"
|
||||||
|
},
|
||||||
|
"ecs": {
|
||||||
|
"$path": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ServerScriptService": {
|
||||||
|
"Server": {
|
||||||
|
"$path": "demo/src/server"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"StarterPlayer": {
|
||||||
|
"StarterPlayerScripts": {
|
||||||
|
"Client": {
|
||||||
|
"$path": "demo/src/client"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Workspace": {
|
||||||
|
"$properties": {
|
||||||
|
"FilteringEnabled": true
|
||||||
|
},
|
||||||
|
"Baseplate": {
|
||||||
|
"$className": "Part",
|
||||||
|
"$properties": {
|
||||||
|
"Anchored": true,
|
||||||
|
"Color": [
|
||||||
|
0.38823,
|
||||||
|
0.37254,
|
||||||
|
0.38823
|
||||||
|
],
|
||||||
|
"Locked": true,
|
||||||
|
"Position": [
|
||||||
|
0,
|
||||||
|
-10,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"Size": [
|
||||||
|
512,
|
||||||
|
20,
|
||||||
|
512
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Lighting": {
|
||||||
|
"$properties": {
|
||||||
|
"Ambient": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"Brightness": 2,
|
||||||
|
"GlobalShadows": true,
|
||||||
|
"Outlines": false,
|
||||||
|
"Technology": "Voxel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SoundService": {
|
||||||
|
"$properties": {
|
||||||
|
"RespectFilteringEnabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
demo/.gitignore
vendored
Normal file
6
demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Project place file
|
||||||
|
/example.rbxlx
|
||||||
|
|
||||||
|
# Roblox Studio lock files
|
||||||
|
/*.rbxlx.lock
|
||||||
|
/*.rbxl.lock
|
17
demo/README.md
Normal file
17
demo/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# example
|
||||||
|
Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
To build the place from scratch, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rojo build -o "example.rbxlx"
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, open `example.rbxlx` in Roblox Studio and start the Rojo server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rojo serve
|
||||||
|
```
|
||||||
|
|
||||||
|
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
1
demo/src/client/init.client.luau
Normal file
1
demo/src/client/init.client.luau
Normal file
|
@ -0,0 +1 @@
|
||||||
|
print("Hello world, from client!")
|
1
demo/src/server/init.server.luau
Normal file
1
demo/src/server/init.server.luau
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
258
demo/src/shared/common.luau
Normal file
258
demo/src/shared/common.luau
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
--!optimize 2
|
||||||
|
--!native
|
||||||
|
|
||||||
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
|
|
||||||
|
type World = jecs.WorldShim
|
||||||
|
type Entity<T = any> = jecs.Entity<T>
|
||||||
|
|
||||||
|
local function panic(str)
|
||||||
|
-- We don't want to interrupt the loop when we error
|
||||||
|
task.spawn(error, str)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function Scheduler(world, ...)
|
||||||
|
local systems = { ... }
|
||||||
|
local systemsNames = {}
|
||||||
|
local N = #systems
|
||||||
|
local system
|
||||||
|
local dt
|
||||||
|
|
||||||
|
for i, module in systems do
|
||||||
|
local sys = 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]
|
||||||
|
|
||||||
|
debug.profilebegin(name)
|
||||||
|
debug.setmemorycategory(name)
|
||||||
|
system(world, dt)
|
||||||
|
debug.profileend()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loop(sinceLastFrame)
|
||||||
|
debug.profilebegin("loop()")
|
||||||
|
|
||||||
|
for i = N, 1, -1 do
|
||||||
|
system = systems[i]
|
||||||
|
|
||||||
|
dt = sinceLastFrame
|
||||||
|
|
||||||
|
local didNotYield, why = xpcall(function()
|
||||||
|
for _ in run do end
|
||||||
|
end, debug.traceback)
|
||||||
|
|
||||||
|
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`
|
||||||
|
)
|
||||||
|
else
|
||||||
|
panic(why)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
debug.profileend()
|
||||||
|
debug.resetmemorycategory()
|
||||||
|
end
|
||||||
|
|
||||||
|
return loop
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if size ~= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
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 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"
|
||||||
|
|
||||||
|
add[id] = data
|
||||||
|
|
||||||
|
return id, data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not is_trivial then
|
||||||
|
if diff(new, old) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif new ~= old then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
id, new, old = q.next()
|
||||||
|
end
|
||||||
|
|
||||||
|
local record = world.entityIndex.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
|
||||||
|
|
||||||
|
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 changes = {
|
||||||
|
added = changes_added,
|
||||||
|
changed = changes_changed,
|
||||||
|
removed = changes_removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function track(fn)
|
||||||
|
added = false
|
||||||
|
removed = false
|
||||||
|
|
||||||
|
fn(changes)
|
||||||
|
|
||||||
|
if not added then
|
||||||
|
for _ in changes_added() 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
|
||||||
|
|
||||||
|
local tracker = { track = track }
|
||||||
|
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
|
||||||
|
local bt
|
||||||
|
do
|
||||||
|
local SUCCESS = 0
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function interval(s)
|
||||||
|
local pin
|
||||||
|
|
||||||
|
local function throttle()
|
||||||
|
if not pin then
|
||||||
|
pin = os.clock()
|
||||||
|
end
|
||||||
|
|
||||||
|
local elapsed = os.clock() - pin > s
|
||||||
|
if elapsed then
|
||||||
|
pin = os.clock()
|
||||||
|
end
|
||||||
|
|
||||||
|
return elapsed
|
||||||
|
end
|
||||||
|
return throttle
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
Scheduler = Scheduler,
|
||||||
|
ChangeTracker = ChangeTracker,
|
||||||
|
interval = interval,
|
||||||
|
BehaviorTree = bt
|
||||||
|
}
|
11
examples/README.md
Normal file
11
examples/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
This folder contains code examples for the Luau/Typescript APIs.
|
||||||
|
|
||||||
|
## Run with Luau
|
||||||
|
To run the examples with Luau, run the following commands from the root of the repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd examples/luau
|
||||||
|
luau path/to/file.luau
|
||||||
|
```
|
45
examples/luau/entities/basics.luau
Normal file
45
examples/luau/entities/basics.luau
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local Position = world:component()
|
||||||
|
local Walking = world:component()
|
||||||
|
local Name = world:component()
|
||||||
|
|
||||||
|
-- Create an entity with name Bob
|
||||||
|
local bob = world:entity()
|
||||||
|
|
||||||
|
-- The set operation finds or creates a component, and sets it.
|
||||||
|
world:set(bob, Position, Vector3.new(10, 20, 30))
|
||||||
|
-- Name the entity Bob
|
||||||
|
world:set(bob, Name, "Bob")
|
||||||
|
-- The add operation adds a component without setting a value. This is
|
||||||
|
-- useful for tags, or when adding a component with its default value.
|
||||||
|
world:add(bob, Walking)
|
||||||
|
|
||||||
|
-- Get the value for the Position component
|
||||||
|
local pos = world:get(bob, Position)
|
||||||
|
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}\}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- {10, 20, 30}
|
||||||
|
-- Alice: {10, 20, 30}
|
||||||
|
-- Bob: {40, 50, 60}
|
125
examples/luau/entities/hierarchy.luau
Normal file
125
examples/luau/entities/hierarchy.luau
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
local ChildOf = jecs.ChildOf
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local Name = world:component()
|
||||||
|
local Position = world:component()
|
||||||
|
local Star = world:component()
|
||||||
|
local Planet = world:component()
|
||||||
|
local Moon = world:component()
|
||||||
|
|
||||||
|
local Vector3
|
||||||
|
do
|
||||||
|
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.__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
|
||||||
|
|
||||||
|
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
|
||||||
|
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}}`)
|
||||||
|
|
||||||
|
for child in world:query(pair(ChildOf, entity)) do
|
||||||
|
--print(world:get(child, Name))
|
||||||
|
iterate(child, actual)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local sun = world:entity()
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
iterate(sun, Vector3.zero)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- Child of Earth? true
|
||||||
|
-- Sun
|
||||||
|
-- {1, 1, 1}
|
||||||
|
-- Sun/Mercury
|
||||||
|
-- {2, 2, 2}
|
||||||
|
-- Sun/Venus
|
||||||
|
-- {3, 3, 3}
|
||||||
|
-- Sun/Earth
|
||||||
|
-- {4, 4, 4}
|
||||||
|
-- Sun/Earth/Moon
|
||||||
|
-- {4.1, 4.1, 4.1}
|
75
examples/luau/queries/basics.luau
Normal file
75
examples/luau/queries/basics.luau
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local Position = world:component()
|
||||||
|
local Velocity = world:component()
|
||||||
|
local Name = world:component()
|
||||||
|
|
||||||
|
local Vector3
|
||||||
|
do
|
||||||
|
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.__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
|
||||||
|
|
||||||
|
Vector3.one = Vector3.new(1, 1, 1)
|
||||||
|
Vector3.zero = Vector3.new()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a few test entities for a Position, Velocity query
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:set(e1, Name, "e1")
|
||||||
|
world:set(e1, Position, Vector3.new(10, 20, 30))
|
||||||
|
world:set(e1, Velocity, Vector3.new(1, 2, 3))
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:set(e2, Name, "e2")
|
||||||
|
world:set(e2, Position, Vector3.new(10, 20, 30))
|
||||||
|
world:set(e2, Velocity, Vector3.new(4, 5, 6))
|
||||||
|
|
||||||
|
-- This entity will not match as it does not have Position, Velocity
|
||||||
|
local e3 = world:entity()
|
||||||
|
world:set(e3, Name, "e3")
|
||||||
|
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
|
||||||
|
|
||||||
|
print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- e2: {14, 25, 36}
|
||||||
|
-- e1: {11, 22, 33}
|
242
examples/luau/queries/changetracking.luau
Normal file
242
examples/luau/queries/changetracking.luau
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
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)
|
||||||
|
}) -> ()) -> ()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if size ~= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
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 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"
|
||||||
|
|
||||||
|
add[id] = data
|
||||||
|
|
||||||
|
return id, data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not is_trivial then
|
||||||
|
if diff(new, old) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif new ~= old then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
id, new, old = q.next()
|
||||||
|
end
|
||||||
|
|
||||||
|
add[id] = new
|
||||||
|
|
||||||
|
return id, old, new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 changes = {
|
||||||
|
added = changes_added,
|
||||||
|
changed = changes_changed,
|
||||||
|
removed = changes_removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function track(fn)
|
||||||
|
added = false
|
||||||
|
removed = false
|
||||||
|
|
||||||
|
fn(changes)
|
||||||
|
|
||||||
|
if not added then
|
||||||
|
for _ in changes_added() 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
|
||||||
|
|
||||||
|
local tracker = { track = track }
|
||||||
|
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
|
||||||
|
local Vector3
|
||||||
|
do
|
||||||
|
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.__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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
local function name(e)
|
||||||
|
return world:get(e, Name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Position = named(world.component, "Position")
|
||||||
|
|
||||||
|
-- Create the ChangeTracker with the component type to track
|
||||||
|
local PositionTracker = ChangeTracker(world, Position)
|
||||||
|
|
||||||
|
local e1 = named(world.entity, "e1")
|
||||||
|
world:set(e1, Position, Vector3.new(10, 20, 30))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
we are tracking the Position component]])
|
||||||
|
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
|
||||||
|
we are tracking the Position component]])
|
||||||
|
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
|
||||||
|
since last time we tracked]])
|
||||||
|
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.
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:remove(e2, Position)
|
||||||
|
|
||||||
|
PositionTracker.track(function(changes)
|
||||||
|
for e in changes.removed() do
|
||||||
|
print(`Position was removed from {name(e)}`)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- Added 265: {10, 20, 30}
|
||||||
|
-- Added 264: {10, 20, 30}
|
||||||
|
-- e1's Position changed from {10, 20, 30} to {999, 999, 1998}
|
||||||
|
-- Position was removed from e2
|
37
examples/luau/queries/wildcards.luau
Normal file
37
examples/luau/queries/wildcards.luau
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
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
|
||||||
|
end
|
||||||
|
local function name(e)
|
||||||
|
return world:get(e, Name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Eats = world:component()
|
||||||
|
local Apples = named(world.entity, "Apples")
|
||||||
|
local Oranges = named(world.entity, "Oranges")
|
||||||
|
|
||||||
|
local bob = named(world.entity, "Bob")
|
||||||
|
world:set(bob, pair(Eats, Apples), 10)
|
||||||
|
|
||||||
|
local alice = named(world.entity, "Alice")
|
||||||
|
world:set(alice, pair(Eats, Oranges), 5)
|
||||||
|
|
||||||
|
-- Aliasing the wildcard to symbols improves readability and ease of writing
|
||||||
|
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)}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- Alice eats 5 Oranges
|
||||||
|
-- Bob eats 10 Apples
|
802
src/init.luau
802
src/init.luau
|
@ -714,16 +714,16 @@ export type Query = typeof({
|
||||||
|
|
||||||
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
||||||
|
|
||||||
local world_query: (World, ...i53) -> Query
|
local noop: Item = function()
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
local Arm = function(self: Query, ...)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
local world_query
|
||||||
do
|
do
|
||||||
|
|
||||||
local noop: Item = function()
|
|
||||||
return nil :: any
|
|
||||||
end
|
|
||||||
|
|
||||||
local Arm = function(self: Query, ...)
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
local EmptyQuery: Query = {
|
local EmptyQuery: Query = {
|
||||||
__iter = function(): Item
|
__iter = function(): Item
|
||||||
return noop
|
return noop
|
||||||
|
@ -737,260 +737,245 @@ do
|
||||||
|
|
||||||
setmetatable(EmptyQuery, EmptyQuery)
|
setmetatable(EmptyQuery, EmptyQuery)
|
||||||
|
|
||||||
local lastArchetype: number
|
local function world_query_replace_values(row, columns, ...)
|
||||||
local archetype: Archetype
|
for i, column in columns do
|
||||||
local queryOutput: { any }
|
column[row] = select(i, ...)
|
||||||
local entities: { number }
|
|
||||||
local i: number
|
|
||||||
|
|
||||||
local compatible_archetypes: { Archetype }
|
|
||||||
local ids: { number }
|
|
||||||
local columns
|
|
||||||
|
|
||||||
local A, B, C, D, E, F, G, H, I
|
|
||||||
local a, b, c, d, e, f, g, h
|
|
||||||
|
|
||||||
local init
|
|
||||||
local drain
|
|
||||||
|
|
||||||
local function query_init(query)
|
|
||||||
if init and drain then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
init = true
|
|
||||||
lastArchetype = 1
|
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
|
||||||
|
|
||||||
if not archetype then
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
queryOutput = {}
|
|
||||||
|
|
||||||
entities = archetype.entities
|
|
||||||
i = #entities
|
|
||||||
columns = archetype.columns
|
|
||||||
|
|
||||||
local records = archetype.records
|
|
||||||
if not B then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
elseif not C then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
elseif not D then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[C].column]
|
|
||||||
elseif not E then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[C].column]
|
|
||||||
d = columns[records[D].column]
|
|
||||||
elseif not F then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[C].column]
|
|
||||||
d = columns[records[D].column]
|
|
||||||
e = columns[records[E].column]
|
|
||||||
elseif not G then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[C].column]
|
|
||||||
d = columns[records[D].column]
|
|
||||||
e = columns[records[E].column]
|
|
||||||
f = columns[records[F].column]
|
|
||||||
elseif not H then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[C].column]
|
|
||||||
d = columns[records[D].column]
|
|
||||||
e = columns[records[E].column]
|
|
||||||
f = columns[records[F].column]
|
|
||||||
g = columns[records[G].column]
|
|
||||||
elseif H then
|
|
||||||
a = columns[records[A].column]
|
|
||||||
b = columns[records[B].column]
|
|
||||||
c = columns[records[D].column]
|
|
||||||
e = columns[records[E].column]
|
|
||||||
f = columns[records[F].column]
|
|
||||||
g = columns[records[G].column]
|
|
||||||
h = columns[records[H].column]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local world_query_iter_next
|
function world_query(world, ...)
|
||||||
|
-- breaking
|
||||||
|
if (...) == nil then
|
||||||
|
error("Missing components")
|
||||||
|
end
|
||||||
|
|
||||||
local function world_query_iter_create()
|
local compatible_archetypes = {}
|
||||||
if not B then
|
local length = 0
|
||||||
function world_query_iter_next(): any
|
|
||||||
|
local ids = { ... }
|
||||||
|
local A, B, C, D, E, F, G, H, I = ...
|
||||||
|
local a, b, c, d, e, f, g, h
|
||||||
|
|
||||||
|
local archetypes = world.archetypes
|
||||||
|
|
||||||
|
local idr: ArchetypeMap
|
||||||
|
local componentIndex = world.componentIndex
|
||||||
|
|
||||||
|
for _, id in ids do
|
||||||
|
local map = componentIndex[id]
|
||||||
|
if not map then
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
if idr == nil or map.size < idr.size then
|
||||||
|
idr = map
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for archetype_id in idr.cache do
|
||||||
|
local compatibleArchetype = archetypes[archetype_id]
|
||||||
|
local tr = compatibleArchetype.records
|
||||||
|
|
||||||
|
local skip = false
|
||||||
|
|
||||||
|
for i, id in ids do
|
||||||
|
local index = tr[id]
|
||||||
|
if not index then
|
||||||
|
skip = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if skip then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
length += 1
|
||||||
|
compatible_archetypes[length] = compatibleArchetype
|
||||||
|
end
|
||||||
|
|
||||||
|
if length == 0 then
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
local lastArchetype = 1
|
||||||
|
local archetype
|
||||||
|
local columns
|
||||||
|
local entities
|
||||||
|
local i
|
||||||
|
local queryOutput
|
||||||
|
|
||||||
|
local world_query_iter_next
|
||||||
|
|
||||||
|
if not B then
|
||||||
|
function world_query_iter_next(): any
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
return entityId, a[row]
|
return entityId, a[row]
|
||||||
end
|
end
|
||||||
elseif not C then
|
elseif not C then
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
return entityId, a[row], b[row]
|
return entityId, a[row], b[row]
|
||||||
end
|
end
|
||||||
elseif not D then
|
elseif not D then
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
return entityId, a[row], b[row], c[row]
|
return entityId, a[row], b[row], c[row]
|
||||||
end
|
end
|
||||||
elseif not E then
|
elseif not E then
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
d = columns[records[D].column]
|
d = columns[records[D].column]
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
return entityId, a[row], b[row], c[row], d[row]
|
return entityId, a[row], b[row], c[row], d[row]
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
function world_query_iter_next(): any
|
function world_query_iter_next(): any
|
||||||
local entityId = entities[i]
|
local entityId = entities[i]
|
||||||
while entityId == nil do
|
while entityId == nil do
|
||||||
lastArchetype += 1
|
lastArchetype += 1
|
||||||
archetype = compatible_archetypes[lastArchetype]
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
if not archetype then
|
if not archetype then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
if i == 0 then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
|
|
||||||
if not F then
|
if not F then
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
d = columns[records[D].column]
|
d = columns[records[D].column]
|
||||||
e = columns[records[E].column]
|
e = columns[records[E].column]
|
||||||
elseif not G then
|
elseif not G then
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
d = columns[records[D].column]
|
d = columns[records[D].column]
|
||||||
e = columns[records[E].column]
|
e = columns[records[E].column]
|
||||||
f = columns[records[F].column]
|
f = columns[records[F].column]
|
||||||
elseif not H then
|
elseif not H then
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
d = columns[records[D].column]
|
d = columns[records[D].column]
|
||||||
e = columns[records[E].column]
|
e = columns[records[E].column]
|
||||||
f = columns[records[F].column]
|
f = columns[records[F].column]
|
||||||
g = columns[records[G].column]
|
g = columns[records[G].column]
|
||||||
elseif not I then
|
elseif not I then
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
d = columns[records[D].column]
|
d = columns[records[D].column]
|
||||||
e = columns[records[E].column]
|
e = columns[records[E].column]
|
||||||
f = columns[records[F].column]
|
f = columns[records[F].column]
|
||||||
g = columns[records[G].column]
|
g = columns[records[G].column]
|
||||||
h = columns[records[H].column]
|
h = columns[records[H].column]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local row = i
|
local row = i
|
||||||
i-=1
|
i-=1
|
||||||
|
|
||||||
if not F then
|
if not F then
|
||||||
return entityId, a[row], b[row], c[row], d[row], e[row]
|
return entityId, a[row], b[row], c[row], d[row], e[row]
|
||||||
elseif not G then
|
elseif not G then
|
||||||
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
|
||||||
elseif not H then
|
elseif not H then
|
||||||
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
|
||||||
elseif not I then
|
elseif not I then
|
||||||
|
@ -999,218 +984,233 @@ do
|
||||||
|
|
||||||
local field = archetype.records
|
local field = archetype.records
|
||||||
for j, id in ids do
|
for j, id in ids do
|
||||||
queryOutput[j] = columns[field[id].column][row]
|
queryOutput[j] = columns[field[id].column][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return entityId, unpack(queryOutput)
|
return entityId, unpack(queryOutput)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_without(self, ...)
|
|
||||||
local withoutComponents = { ... }
|
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
|
||||||
local archetype = compatible_archetypes[i]
|
|
||||||
local records = archetype.records
|
|
||||||
local shouldRemove = false
|
|
||||||
|
|
||||||
for _, componentId in withoutComponents do
|
|
||||||
if records[componentId] then
|
|
||||||
shouldRemove = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if shouldRemove then
|
|
||||||
local last = #compatible_archetypes
|
|
||||||
if last ~= i then
|
|
||||||
compatible_archetypes[i] = compatible_archetypes[last]
|
|
||||||
end
|
|
||||||
compatible_archetypes[last] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_replace_values(row, columns, ...)
|
|
||||||
for i, column in columns do
|
|
||||||
column[row] = select(i, ...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_replace(query, fn: (...any) -> (...any))
|
|
||||||
query_init(query)
|
|
||||||
|
|
||||||
for i, archetype in compatible_archetypes do
|
|
||||||
local columns = archetype.columns
|
|
||||||
local tr = archetype.records
|
|
||||||
for row in archetype.entities do
|
|
||||||
if not B then
|
|
||||||
local va = columns[tr[A].column]
|
|
||||||
local pa = fn(va[row])
|
|
||||||
|
|
||||||
va[row] = pa
|
|
||||||
elseif not C then
|
|
||||||
local va = columns[tr[A].column]
|
|
||||||
local vb = columns[tr[B].column]
|
|
||||||
|
|
||||||
va[row], vb[row] = fn(va[row], vb[row])
|
|
||||||
elseif not D then
|
|
||||||
local va = columns[tr[A].column]
|
|
||||||
local vb = columns[tr[B].column]
|
|
||||||
local vc = columns[tr[C].column]
|
|
||||||
|
|
||||||
va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row])
|
|
||||||
elseif not E then
|
|
||||||
local va = columns[tr[A].column]
|
|
||||||
local vb = columns[tr[B].column]
|
|
||||||
local vc = columns[tr[C].column]
|
|
||||||
local vd = columns[tr[D].column]
|
|
||||||
|
|
||||||
va[row], vb[row], vc[row], vd[row] = fn(
|
|
||||||
va[row], vb[row], vc[row], vd[row])
|
|
||||||
else
|
|
||||||
local field = archetype.records
|
|
||||||
for j, id in ids do
|
|
||||||
queryOutput[j] = columns[field[id].column][row]
|
|
||||||
end
|
|
||||||
world_query_replace_values(row, columns,
|
|
||||||
fn(unpack(queryOutput)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_with(query, ...)
|
|
||||||
local ids = { ... }
|
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
|
||||||
local archetype = compatible_archetypes[i]
|
|
||||||
local records = archetype.records
|
|
||||||
local shouldRemove = false
|
|
||||||
|
|
||||||
for _, id in ids do
|
|
||||||
if not records[id] then
|
|
||||||
shouldRemove = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if shouldRemove then
|
|
||||||
local last = #compatible_archetypes
|
|
||||||
if last ~= i then
|
|
||||||
compatible_archetypes[i] = compatible_archetypes[last]
|
|
||||||
end
|
|
||||||
compatible_archetypes[last] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
query_init(query)
|
|
||||||
|
|
||||||
return query
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Meant for directly iterating over archetypes to minimize
|
|
||||||
-- function call overhead. Should not be used unless iterating over
|
|
||||||
-- hundreds of thousands of entities in bulk.
|
|
||||||
local function world_query_archetypes()
|
|
||||||
return compatible_archetypes
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_drain(query)
|
|
||||||
drain = true
|
|
||||||
query_init(query)
|
|
||||||
return query
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_iter(query)
|
|
||||||
query_init(query)
|
|
||||||
return world_query_iter_next
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_query_next()
|
|
||||||
if not drain then
|
|
||||||
error("Did you forget to call query:drain()?")
|
|
||||||
end
|
end
|
||||||
return world_query_iter_next()
|
|
||||||
end
|
|
||||||
|
|
||||||
local it = {
|
local init = false
|
||||||
__iter = world_query_iter,
|
local drain = false
|
||||||
drain = world_query_drain,
|
|
||||||
next = world_query_next,
|
|
||||||
with = world_query_with,
|
|
||||||
without = world_query_without,
|
|
||||||
replace = world_query_replace,
|
|
||||||
archetypes = world_query_archetypes
|
|
||||||
} :: any
|
|
||||||
|
|
||||||
setmetatable(it, it)
|
local function query_init(query)
|
||||||
|
if init and drain then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
function world_query(world: World, ...: any): Query
|
init = true
|
||||||
-- breaking
|
lastArchetype = 1
|
||||||
if (...) == nil then
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
error("Missing components")
|
|
||||||
end
|
|
||||||
|
|
||||||
compatible_archetypes = {}
|
if not archetype then
|
||||||
local length = 0
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local components = { ... } :: any
|
queryOutput = {}
|
||||||
A, B, C, D, E, F, G, H, I = ...
|
|
||||||
|
|
||||||
local archetypes = world.archetypes
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
columns = archetype.columns
|
||||||
|
|
||||||
local firstArchetypeMap: ArchetypeMap
|
local records = archetype.records
|
||||||
local componentIndex = world.componentIndex
|
if not B then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
elseif not C then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
elseif not D then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
elseif not E then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
elseif not F then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
e = columns[records[E].column]
|
||||||
|
elseif not G then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
e = columns[records[E].column]
|
||||||
|
f = columns[records[F].column]
|
||||||
|
elseif not H then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
e = columns[records[E].column]
|
||||||
|
f = columns[records[F].column]
|
||||||
|
g = columns[records[G].column]
|
||||||
|
elseif not I then
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
e = columns[records[E].column]
|
||||||
|
f = columns[records[F].column]
|
||||||
|
g = columns[records[G].column]
|
||||||
|
h = columns[records[H].column]
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
for _, componentId in components do
|
local function world_query_without(query, ...)
|
||||||
local map = componentIndex[componentId]
|
local withoutComponents = { ... }
|
||||||
if not map then
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
return EmptyQuery
|
local archetype = compatible_archetypes[i]
|
||||||
end
|
local records = archetype.records
|
||||||
|
local shouldRemove = false
|
||||||
|
|
||||||
if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then
|
for _, componentId in withoutComponents do
|
||||||
firstArchetypeMap = map
|
if records[componentId] then
|
||||||
end
|
shouldRemove = true
|
||||||
end
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for id in firstArchetypeMap.cache do
|
if shouldRemove then
|
||||||
local compatibleArchetype = archetypes[id]
|
local last = #compatible_archetypes
|
||||||
local archetypeRecords = compatibleArchetype.records
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil
|
||||||
|
length -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local skip = false
|
if length == 0 then
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
for i, componentId in components do
|
return query
|
||||||
local index = archetypeRecords[componentId]
|
end
|
||||||
if not index then
|
|
||||||
skip = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if skip then
|
local function world_query_replace(query, fn: (...any) -> (...any))
|
||||||
continue
|
query_init(query)
|
||||||
end
|
|
||||||
|
|
||||||
length += 1
|
for i, archetype in compatible_archetypes do
|
||||||
compatible_archetypes[length] = compatibleArchetype
|
local columns = archetype.columns
|
||||||
end
|
local tr = archetype.records
|
||||||
|
for row in archetype.entities do
|
||||||
|
if not B then
|
||||||
|
local va = columns[tr[A].column]
|
||||||
|
local pa = fn(va[row])
|
||||||
|
|
||||||
drain = false
|
va[row] = pa
|
||||||
init = false
|
elseif not C then
|
||||||
ids = components
|
local va = columns[tr[A].column]
|
||||||
|
local vb = columns[tr[B].column]
|
||||||
|
|
||||||
world_query_iter_create()
|
va[row], vb[row] = fn(va[row], vb[row])
|
||||||
|
elseif not D then
|
||||||
|
local va = columns[tr[A].column]
|
||||||
|
local vb = columns[tr[B].column]
|
||||||
|
local vc = columns[tr[C].column]
|
||||||
|
|
||||||
return it
|
va[row], vb[row], vc[row] = fn(va[row], vb[row], vc[row])
|
||||||
|
elseif not E then
|
||||||
|
local va = columns[tr[A].column]
|
||||||
|
local vb = columns[tr[B].column]
|
||||||
|
local vc = columns[tr[C].column]
|
||||||
|
local vd = columns[tr[D].column]
|
||||||
|
|
||||||
|
va[row], vb[row], vc[row], vd[row] = fn(
|
||||||
|
va[row], vb[row], vc[row], vd[row])
|
||||||
|
else
|
||||||
|
local field = archetype.records
|
||||||
|
for j, id in ids do
|
||||||
|
queryOutput[j] = columns[field[id].column][row]
|
||||||
|
end
|
||||||
|
world_query_replace_values(row, columns,
|
||||||
|
fn(unpack(queryOutput)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function world_query_with(query, ...)
|
||||||
|
local with = { ... }
|
||||||
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
|
local archetype = compatible_archetypes[i]
|
||||||
|
local tr = archetype.records
|
||||||
|
local shouldRemove = false
|
||||||
|
|
||||||
|
for _, id in with do
|
||||||
|
if not tr[id] then
|
||||||
|
shouldRemove = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if shouldRemove then
|
||||||
|
local last = #compatible_archetypes
|
||||||
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil
|
||||||
|
length -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if length == 0 then
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
return query
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Meant for directly iterating over archetypes to minimize
|
||||||
|
-- function call overhead. Should not be used unless iterating over
|
||||||
|
-- hundreds of thousands of entities in bulk.
|
||||||
|
local function world_query_archetypes()
|
||||||
|
return compatible_archetypes
|
||||||
|
end
|
||||||
|
|
||||||
|
local function world_query_drain(query)
|
||||||
|
drain = true
|
||||||
|
if query_init(query) then
|
||||||
|
return query
|
||||||
|
end
|
||||||
|
return EmptyQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
local function world_query_iter(query)
|
||||||
|
query_init(query)
|
||||||
|
return world_query_iter_next
|
||||||
|
end
|
||||||
|
|
||||||
|
local function world_query_next(world)
|
||||||
|
if not drain then
|
||||||
|
error("Did you forget to call query:drain()?")
|
||||||
|
end
|
||||||
|
return world_query_iter_next(world)
|
||||||
|
end
|
||||||
|
|
||||||
|
local it = {
|
||||||
|
__iter = world_query_iter,
|
||||||
|
drain = world_query_drain,
|
||||||
|
next = world_query_next,
|
||||||
|
with = world_query_with,
|
||||||
|
without = world_query_without,
|
||||||
|
replace = world_query_replace,
|
||||||
|
archetypes = world_query_archetypes
|
||||||
|
} :: any
|
||||||
|
|
||||||
|
setmetatable(it, it)
|
||||||
|
|
||||||
|
return it
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53)
|
|
||||||
-- __nominal_type_dont_use could not be any or T as it causes a type error
|
-- __nominal_type_dont_use could not be any or T as it causes a type error
|
||||||
-- or produces a union
|
-- or produces a union
|
||||||
export type Entity<T = any> = number & { __nominal_type_dont_use: T }
|
export type Entity<T = any> = number & { __DO_NOT_USE_OR_YOU_WILL_BE_FIRED: T }
|
||||||
export type Pair = number
|
export type Pair = number
|
||||||
|
|
||||||
export type QueryShim<T...> = typeof(setmetatable({
|
export type QueryShim<T...> = typeof(setmetatable({
|
||||||
|
|
152
test/btree.luau
Normal file
152
test/btree.luau
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
-- original author @centauri
|
||||||
|
local bt
|
||||||
|
do
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local function panic(str)
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
--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]
|
||||||
|
|
||||||
|
dt = sinceLastFrame
|
||||||
|
|
||||||
|
local didNotYield, why = xpcall(function()
|
||||||
|
for _ in run do end
|
||||||
|
end, debug.traceback)
|
||||||
|
|
||||||
|
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`
|
||||||
|
)
|
||||||
|
else
|
||||||
|
panic(why)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--debug.profileend()
|
||||||
|
--debug.resetmemorycategory()
|
||||||
|
return os.clock() - start
|
||||||
|
end
|
||||||
|
|
||||||
|
return loop
|
||||||
|
end
|
||||||
|
|
||||||
|
local co = coroutine.create(btree)
|
||||||
|
local function ai(world, dt)
|
||||||
|
coroutine.resume(co)
|
||||||
|
end
|
||||||
|
|
||||||
|
local loop = Scheduler(world, ai)
|
||||||
|
|
||||||
|
while wait(0.2) do
|
||||||
|
print("frame time: ", loop(0.2))
|
||||||
|
end
|
Loading…
Reference in a new issue