mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Add cached queries (#166)
* Initial commit * Add tests * Dedup observers * Handle filters on table creation * Handle Archetype deletion * Remove print * Fix type errors * Cleanup code * Manually inline code * Build terms for cached queries * Specialized cached query iterator * Remove shadowed variable * Inverse statement * Rework demo * Fix metatable * Use generalized iteration
This commit is contained in:
parent
0f2e0eba76
commit
ec4fa3ff3e
19 changed files with 865 additions and 628 deletions
|
@ -2,18 +2,16 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
local UserInputService = game:GetService("UserInputService")
|
local UserInputService = game:GetService("UserInputService")
|
||||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||||
local std = require(ReplicatedStorage.std)
|
local std = ReplicatedStorage.std
|
||||||
local Scheduler = std.Scheduler
|
local scheduler = require(std.scheduler)
|
||||||
local world = std.world
|
local world = require(std.world)
|
||||||
|
|
||||||
local function start(modules)
|
local function start(modules)
|
||||||
local scheduler = Scheduler.new(world, require(ReplicatedStorage.std.components))
|
|
||||||
for _, module in modules do
|
for _, module in modules do
|
||||||
require(module)(scheduler)
|
require(module)
|
||||||
end
|
end
|
||||||
local events = scheduler.collect.all()
|
local events = scheduler.COLLECT()
|
||||||
scheduler.systems.begin(events)
|
scheduler.BEGIN(events)
|
||||||
|
|
||||||
jabby.set_check_function(function(player)
|
jabby.set_check_function(function(player)
|
||||||
return true
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -3,15 +3,15 @@
|
||||||
|
|
||||||
-- original author @centau
|
-- original author @centau
|
||||||
|
|
||||||
local SUCCESS = 0
|
local FAILURE = -1
|
||||||
local FAILURE = 1
|
local RUNNING = 0
|
||||||
local RUNNING = 2
|
local SUCCESS = 1
|
||||||
|
|
||||||
local function SEQUENCE(nodes)
|
local function SEQUENCE(nodes)
|
||||||
return function(...)
|
return function(...)
|
||||||
for _, node in nodes do
|
for _, node in nodes do
|
||||||
local status = node(...)
|
local status = node(...)
|
||||||
if status == FAILURE or status == RUNNING then
|
if status <= RUNNING then
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -23,7 +23,7 @@ local function FALLBACK(nodes)
|
||||||
return function(...)
|
return function(...)
|
||||||
for _, node in nodes do
|
for _, node in nodes do
|
||||||
local status = node(...)
|
local status = node(...)
|
||||||
if status == SUCCESS or status == RUNNING then
|
if status > FAILURE then
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ local components: {
|
||||||
Model: Entity<Model>,
|
Model: Entity<Model>,
|
||||||
Player: Entity,
|
Player: Entity,
|
||||||
Target: Entity,
|
Target: Entity,
|
||||||
Transform: Entity<CFrame>,
|
Transform: Entity<{ new: CFrame, old: CFrame }>,
|
||||||
Velocity: Entity<number>,
|
Velocity: Entity<number>,
|
||||||
Previous: Entity,
|
Previous: Entity,
|
||||||
} =
|
} =
|
||||||
|
@ -23,4 +23,8 @@ local components: {
|
||||||
Previous = world:component(),
|
Previous = world:component(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name, component in components :: {[string]: jecs.Entity} do
|
||||||
|
world:set(component, jecs.Name, name)
|
||||||
|
end
|
||||||
|
|
||||||
return table.freeze(components)
|
return table.freeze(components)
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
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)
|
|
||||||
end
|
|
||||||
|
|
||||||
return ctx
|
|
|
@ -1,56 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 entity = {
|
|
||||||
has = has,
|
|
||||||
get = get,
|
|
||||||
set = set,
|
|
||||||
add = add,
|
|
||||||
clear = clear,
|
|
||||||
id = id,
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle(id)
|
|
||||||
e = id
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return handle
|
|
|
@ -1,32 +0,0 @@
|
||||||
--!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
|
|
|
@ -1,25 +0,0 @@
|
||||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
|
||||||
|
|
||||||
local world = require(script.world) :: jecs.World
|
|
||||||
export type World = jecs.World
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
|
|
||||||
return std
|
|
|
@ -16,4 +16,4 @@ local function interval(s)
|
||||||
return throttle
|
return throttle
|
||||||
end
|
end
|
||||||
|
|
||||||
return interval
|
return interval
|
14
demo/src/ReplicatedStorage/std/phases.luau
Normal file
14
demo/src/ReplicatedStorage/std/phases.luau
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
local std = game:GetService("ReplicatedStorage").std
|
||||||
|
local Players = game:GetService("Players")
|
||||||
|
|
||||||
|
local scheduler = require(std.scheduler)
|
||||||
|
local PHASE = scheduler.PHASE
|
||||||
|
|
||||||
|
return {
|
||||||
|
PlayerAdded = PHASE({
|
||||||
|
event = Players.PlayerAdded
|
||||||
|
}),
|
||||||
|
PlayerRemoved = PHASE({
|
||||||
|
event = Players.PlayerRemoving
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,16 +1,18 @@
|
||||||
local handle = require(script.Parent.handle)
|
|
||||||
local world = require(script.Parent.world)
|
local world = require(script.Parent.world)
|
||||||
local refs = {}
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
|
local refs: {[any]: jecs.Entity} = {}
|
||||||
|
|
||||||
local function fini(key)
|
local function fini(key): () -> ()
|
||||||
return function()
|
return function()
|
||||||
refs[key] = nil
|
refs[key] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ref(key): (handle.Handle, (() -> ())?)
|
local function noop() end
|
||||||
|
|
||||||
|
local function ref(key): (jecs.Entity, () -> ())
|
||||||
if not key then
|
if not key then
|
||||||
return handle(world:entity())
|
return world:entity(), noop
|
||||||
end
|
end
|
||||||
local e = refs[key]
|
local e = refs[key]
|
||||||
if not e then
|
if not e then
|
||||||
|
@ -18,7 +20,7 @@ local function ref(key): (handle.Handle, (() -> ())?)
|
||||||
refs[key] = e
|
refs[key] = e
|
||||||
end
|
end
|
||||||
-- Cannot cache handles because they will get invalidated
|
-- Cannot cache handles because they will get invalidated
|
||||||
return handle(e), fini(key)
|
return e, fini(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
local reserved = 0
|
|
||||||
|
|
||||||
local function reserve()
|
|
||||||
reserved += 1
|
|
||||||
return reserved
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If you don't like passing around a world singleton
|
|
||||||
-- and you need to register component IDs, just register them.
|
|
||||||
-- I dont use this because I like adding component traits
|
|
||||||
--[[
|
|
||||||
local components = {
|
|
||||||
Model = registry.reserve(),
|
|
||||||
Transform = registry.reserve(),
|
|
||||||
}
|
|
||||||
|
|
||||||
local world = registry.register(jecs.World.new())
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, components.Transform, CFrame)
|
|
||||||
]]
|
|
||||||
local function register(world)
|
|
||||||
for _ = 1, reserved do
|
|
||||||
world:component()
|
|
||||||
end
|
|
||||||
return world
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
reserve = reserve,
|
|
||||||
register = register,
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
--!native
|
--!native
|
||||||
--!optimize 2
|
--!optimize 2
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local RunService = game:GetService("RunService")
|
||||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||||
local jecs = require(ReplicatedStorage.ecs)
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
|
@ -8,6 +9,7 @@ local Name = jecs.Name
|
||||||
|
|
||||||
type World = jecs.World
|
type World = jecs.World
|
||||||
type Entity<T = nil> = jecs.Entity<T>
|
type Entity<T = nil> = jecs.Entity<T>
|
||||||
|
type Id<T = unknown> = jecs.Id<T>
|
||||||
|
|
||||||
type System = {
|
type System = {
|
||||||
callback: (world: World) -> (),
|
callback: (world: World) -> (),
|
||||||
|
@ -21,226 +23,145 @@ type Events = {
|
||||||
Heartbeat: Systems,
|
Heartbeat: Systems,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Scheduler = {
|
local world = require(script.Parent.world)
|
||||||
components: {
|
local Disabled = world:entity()
|
||||||
Disabled: Entity,
|
local System = world:component() :: Id<{ callback: (any) -> (), name: string}>
|
||||||
System: Entity<System>,
|
local DependsOn = world:entity()
|
||||||
Phase: Entity,
|
local Event = world:component() :: Id<RBXScriptSignal>
|
||||||
DependsOn: Entity,
|
local Phase = world:entity()
|
||||||
},
|
|
||||||
|
|
||||||
collect: {
|
local PreRender = world:entity()
|
||||||
under_event: (event: Entity) -> Systems,
|
local Heartbeat = world:entity()
|
||||||
all: () -> Events,
|
local PreAnimation = world:entity()
|
||||||
},
|
local PreSimulation = world:entity()
|
||||||
|
|
||||||
systems: {
|
local sys: System
|
||||||
begin: (events: Events) -> { [Entity]: thread },
|
local dt: number
|
||||||
new: (callback: (dt: number) -> (), phase: Entity) -> Entity,
|
|
||||||
},
|
|
||||||
|
|
||||||
phases: {
|
local jabby_scheduler = jabby.scheduler.create("Scheduler")
|
||||||
RenderStepped: Entity,
|
|
||||||
Heartbeat: Entity,
|
|
||||||
},
|
|
||||||
|
|
||||||
phase: (after: Entity) -> Entity,
|
local a, b, c, d
|
||||||
|
local function run()
|
||||||
|
local id = sys.id
|
||||||
|
jabby_scheduler:run(id, sys.callback, a, b, c, d)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
debugging: boolean,
|
world:add(Heartbeat, Phase)
|
||||||
}
|
world:set(Heartbeat, Event, RunService.Heartbeat)
|
||||||
|
|
||||||
local scheduler_new: (w: World, components: { [string]: Entity }) -> Scheduler
|
world:add(PreSimulation, Phase)
|
||||||
|
world:set(PreSimulation, Event, RunService.PreSimulation)
|
||||||
|
|
||||||
do
|
world:add(PreAnimation, Phase)
|
||||||
local world: World
|
world:set(PreAnimation, Event, RunService.PreAnimation)
|
||||||
local Disabled: Entity
|
|
||||||
local System: Entity<System>
|
|
||||||
local DependsOn: Entity
|
|
||||||
local Phase: Entity
|
|
||||||
local Event: Entity<RBXScriptSignal>
|
|
||||||
|
|
||||||
local scheduler
|
table.insert(jabby.public, {
|
||||||
|
class_name = "World",
|
||||||
|
name = "MyWorld",
|
||||||
|
world = world,
|
||||||
|
debug = Name,
|
||||||
|
entities = {},
|
||||||
|
})
|
||||||
|
|
||||||
local RenderStepped
|
jabby.public.updated = true
|
||||||
local Heartbeat
|
|
||||||
local PreAnimation
|
|
||||||
local PreSimulation
|
|
||||||
|
|
||||||
local sys: System
|
table.insert(jabby.public, jabby_scheduler)
|
||||||
local dt
|
|
||||||
|
|
||||||
local function run()
|
if RunService:IsClient() then
|
||||||
local id = sys.id
|
world:add(PreRender, Phase)
|
||||||
scheduler:run(id, sys.callback, dt)
|
world:set(PreRender, Event, (RunService :: RunService).PreRender)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function panic(str)
|
local function begin(events: { [RBXScriptSignal]: Systems })
|
||||||
-- We don't want to interrupt the loop when we error
|
local connections = {}
|
||||||
task.spawn(error, str)
|
for event, systems in events do
|
||||||
end
|
if not event then
|
||||||
local function begin(events: { Systems })
|
continue
|
||||||
local connections = {}
|
end
|
||||||
for event, systems in events do
|
local event_name = tostring(event)
|
||||||
if not event then
|
connections[event] = event:Connect(function(...)
|
||||||
continue
|
debug.profilebegin(event_name)
|
||||||
end
|
for _, s in systems do
|
||||||
local event_name = tostring(event)
|
sys = s
|
||||||
connections[event] = event:Connect(function(delta)
|
a, b, c, d = ...
|
||||||
debug.profilebegin(event_name)
|
|
||||||
for _, s in systems do
|
|
||||||
sys = s
|
|
||||||
dt = delta
|
|
||||||
local didNotYield, why = xpcall(function()
|
|
||||||
for _ in run do
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end, debug.traceback)
|
|
||||||
|
|
||||||
if didNotYield then
|
for _ in run do
|
||||||
continue
|
break
|
||||||
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
|
end
|
||||||
debug.profileend()
|
|
||||||
end)
|
end
|
||||||
end
|
debug.profileend()
|
||||||
return threads
|
end)
|
||||||
end
|
end
|
||||||
|
return connections
|
||||||
|
end
|
||||||
|
|
||||||
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
|
local function scheduler_collect_systems_under_phase_recursive(systems, phase: Entity)
|
||||||
local phase_name = world:get(phase, Name)
|
local phase_name = world:get(phase, Name)
|
||||||
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
||||||
table.insert(systems, {
|
table.insert(systems, {
|
||||||
id = scheduler:register_system({
|
id = jabby_scheduler:register_system({
|
||||||
name = s.name,
|
name = s.name,
|
||||||
phase = phase_name,
|
phase = phase_name,
|
||||||
}),
|
} :: any),
|
||||||
callback = s.callback,
|
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_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_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()
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
world:add(Heartbeat, Phase)
|
|
||||||
world:set(Heartbeat, Event, RunService.Heartbeat)
|
|
||||||
|
|
||||||
world:add(PreSimulation, Phase)
|
|
||||||
world:set(PreSimulation, Event, RunService.PreSimulation)
|
|
||||||
|
|
||||||
world:add(PreAnimation, Phase)
|
|
||||||
world:set(PreAnimation, Event, RunService.PreAnimation)
|
|
||||||
|
|
||||||
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 = {},
|
|
||||||
})
|
})
|
||||||
|
end
|
||||||
jabby.public.updated = true
|
for after in world:query(Phase):with(pair(DependsOn, phase)):iter() do
|
||||||
scheduler = jabby.scheduler.create("scheduler")
|
scheduler_collect_systems_under_phase_recursive(systems, after)
|
||||||
|
|
||||||
table.insert(jabby.public, scheduler)
|
|
||||||
|
|
||||||
return {
|
|
||||||
phase = scheduler_phase_new,
|
|
||||||
|
|
||||||
phases = {
|
|
||||||
RenderStepped = RenderStepped,
|
|
||||||
PreSimulation = PreSimulation,
|
|
||||||
Heartbeat = Heartbeat,
|
|
||||||
PreAnimation = PreAnimation,
|
|
||||||
},
|
|
||||||
|
|
||||||
world = world,
|
|
||||||
|
|
||||||
components = {
|
|
||||||
DependsOn = DependsOn,
|
|
||||||
Disabled = Disabled,
|
|
||||||
Phase = Phase,
|
|
||||||
System = System,
|
|
||||||
},
|
|
||||||
|
|
||||||
collect = {
|
|
||||||
under_event = scheduler_collect_systems_under_event,
|
|
||||||
all = scheduler_collect_systems_all,
|
|
||||||
},
|
|
||||||
|
|
||||||
systems = {
|
|
||||||
new = scheduler_systems_new,
|
|
||||||
begin = begin,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
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_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(d: { after: Entity?, event: RBXScriptSignal? })
|
||||||
|
local phase = world:entity()
|
||||||
|
world:add(phase, Phase)
|
||||||
|
local after = d.after
|
||||||
|
if after then
|
||||||
|
local dependency = pair(DependsOn, after :: Entity)
|
||||||
|
world:add(phase, dependency)
|
||||||
|
end
|
||||||
|
|
||||||
|
local event = d.event
|
||||||
|
if event then
|
||||||
|
world:set(phase, Event, event)
|
||||||
|
end
|
||||||
|
return phase
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scheduler_systems_new(callback: (any) -> (), phase: Entity?)
|
||||||
|
local system = world:entity()
|
||||||
|
world:set(system, System, { callback = callback, name = debug.info(callback, "n") })
|
||||||
|
local depends_on = DependsOn :: jecs.Entity
|
||||||
|
local p: Entity = phase or Heartbeat
|
||||||
|
world:add(system, pair(depends_on, p))
|
||||||
|
|
||||||
|
return system
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
new = scheduler_new,
|
SYSTEM = scheduler_systems_new,
|
||||||
|
BEGIN = begin,
|
||||||
|
PHASE = scheduler_phase_new,
|
||||||
|
COLLECT = scheduler_collect_systems_all,
|
||||||
|
phases = {
|
||||||
|
Heartbeat = Heartbeat,
|
||||||
|
PreSimulation = PreSimulation,
|
||||||
|
PreAnimation = PreAnimation,
|
||||||
|
PreRender = PreRender
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local blink = require(game:GetService("ServerScriptService").net)
|
local blink = require(game:GetService("ServerScriptService").net)
|
||||||
local jecs = require(ReplicatedStorage.ecs)
|
local jecs = require(ReplicatedStorage.ecs)
|
||||||
local __ = jecs.Wildcard
|
local __ = jecs.Wildcard
|
||||||
|
local std = ReplicatedStorage.std
|
||||||
|
local ref = require(std.ref)
|
||||||
|
local interval = require(std.interval)
|
||||||
|
|
||||||
local std = require(ReplicatedStorage.std)
|
local world = require(std.world)
|
||||||
local ref = std.ref
|
local cts = require(std.components)
|
||||||
local interval = std.interval
|
|
||||||
|
|
||||||
local world: std.World = std.world
|
|
||||||
local cts = std.components
|
|
||||||
|
|
||||||
local Mob = cts.Mob
|
local Mob = cts.Mob
|
||||||
local Transform = cts.Transform
|
local Transform = cts.Transform
|
||||||
|
@ -20,13 +19,26 @@ local Velocity = cts.Velocity
|
||||||
local Player = cts.Player
|
local Player = cts.Player
|
||||||
local Character = cts.Character
|
local Character = cts.Character
|
||||||
|
|
||||||
|
local characters = world
|
||||||
|
:query(Character)
|
||||||
|
:with(Player)
|
||||||
|
:cached()
|
||||||
|
|
||||||
|
|
||||||
|
local moving_mobs = world
|
||||||
|
:query(Transform, Velocity)
|
||||||
|
:with(Mob)
|
||||||
|
:cached()
|
||||||
|
|
||||||
|
|
||||||
local function mobsMove(dt: number)
|
local function mobsMove(dt: number)
|
||||||
local targets = {}
|
local targets = {}
|
||||||
for _, character in world:query(Character):with(Player):iter() do
|
|
||||||
|
for _, character in characters do
|
||||||
table.insert(targets, (character.PrimaryPart :: Part).Position)
|
table.insert(targets, (character.PrimaryPart :: Part).Position)
|
||||||
end
|
end
|
||||||
|
|
||||||
for mob, transform, v in world:query(Transform, Velocity):with(Mob):iter() do
|
for mob, transform, v in moving_mobs do
|
||||||
local cf = transform.new
|
local cf = transform.new
|
||||||
local p = cf.Position
|
local p = cf.Position
|
||||||
|
|
||||||
|
@ -53,21 +65,24 @@ end
|
||||||
|
|
||||||
local throttle = interval(5)
|
local throttle = interval(5)
|
||||||
|
|
||||||
local function spawnMobs()
|
local function spawnMobs()
|
||||||
if throttle() then
|
if throttle() then
|
||||||
local p = Vector3.new(0, 5, 0)
|
local p = Vector3.new(0, 5, 0)
|
||||||
local cf = CFrame.new(p)
|
local cf = CFrame.new(p)
|
||||||
local v = 5
|
local v = 5
|
||||||
|
|
||||||
local id = ref():set(Velocity, v):set(Transform, { new = cf }):add(Mob).id()
|
local e = world:entity()
|
||||||
|
world:set(e, Velocity, v)
|
||||||
|
world:set(e, Transform, { new = cf })
|
||||||
|
world:add(e, Mob)
|
||||||
|
|
||||||
blink.SpawnMob.FireAll(id, cf, v)
|
blink.SpawnMob.FireAll(e, cf, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(scheduler: std.Scheduler)
|
local scheduler = require(std.scheduler)
|
||||||
local phases = scheduler.phases
|
|
||||||
local system_new = scheduler.systems.new
|
scheduler.SYSTEM(spawnMobs)
|
||||||
system_new(mobsMove, phases.Heartbeat)
|
scheduler.SYSTEM(mobsMove)
|
||||||
system_new(spawnMobs, phases.Heartbeat)
|
|
||||||
end
|
return 0
|
|
@ -1,43 +1,40 @@
|
||||||
local Players = game:GetService("Players")
|
local Players = game:GetService("Players")
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
local std = require(ReplicatedStorage.std)
|
local std = ReplicatedStorage.std
|
||||||
local ref = std.ref
|
local ref = require(std.ref)
|
||||||
local collect = std.collect
|
local collect = require(std.collect)
|
||||||
|
|
||||||
local cts = std.components
|
local cts = require(std.components)
|
||||||
|
local world = require(std.world)
|
||||||
local Player = cts.Player
|
local Player = cts.Player
|
||||||
local Character = cts.Character
|
local Character = cts.Character
|
||||||
|
|
||||||
local playersAdded = collect(Players.PlayerAdded)
|
|
||||||
local playersRemoved = collect(Players.PlayerRemoving)
|
|
||||||
local world: std.World = std.world
|
|
||||||
|
|
||||||
local conn = {}
|
local conn = {}
|
||||||
|
|
||||||
local function players()
|
local function playersAdded(player: Player)
|
||||||
for _, player in playersAdded do
|
local e = ref(player.UserId)
|
||||||
world:set(std.world:entity(), cts.Transform)
|
world:set(e, Player, player)
|
||||||
|
local characterAdd = player.CharacterAdded
|
||||||
local e = ref(player.UserId):set(Player, player)
|
conn[e] = characterAdd:Connect(function(rig)
|
||||||
local characterAdd = player.CharacterAdded
|
while rig.Parent ~= workspace do
|
||||||
conn[e.id()] = characterAdd:Connect(function(rig)
|
task.wait()
|
||||||
while rig.Parent ~= workspace do
|
end
|
||||||
task.wait()
|
world:set(e, Character, rig)
|
||||||
end
|
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(scheduler: std.Scheduler)
|
local function playersRemoved(player: Player)
|
||||||
local phases = scheduler.phases
|
local e = ref(player.UserId)
|
||||||
local system_new = scheduler.systems.new
|
world:clear(e)
|
||||||
system_new(players, phases.Heartbeat)
|
local connection = conn[e]
|
||||||
|
connection:Disconnect()
|
||||||
|
conn[e] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local scheduler = require(std.scheduler)
|
||||||
|
local phases = require(std.phases)
|
||||||
|
scheduler.SYSTEM(playersAdded, phases.PlayerAdded)
|
||||||
|
scheduler.SYSTEM(playersRemoved, phases.PlayerRemoved)
|
||||||
|
|
||||||
|
return 0
|
|
@ -1,20 +1,22 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local blink = require(ReplicatedStorage.net)
|
local blink = require(ReplicatedStorage.net)
|
||||||
local std = require(ReplicatedStorage.std)
|
local std = ReplicatedStorage.std
|
||||||
local world = std.world
|
local world = require(std.world)
|
||||||
local ref = std.ref
|
local ref = require(std.ref)
|
||||||
|
|
||||||
local cts = std.components
|
local cts = require(std.components)
|
||||||
|
|
||||||
local Model = cts.Model
|
local Model = cts.Model
|
||||||
local Transform = cts.Transform
|
local Transform = cts.Transform
|
||||||
|
|
||||||
|
local moving_models = world:query(Transform, Model):cached()
|
||||||
local function move(dt: number)
|
local function move(dt: number)
|
||||||
for _, transform, model in world:query(Transform, Model):iter() do
|
for _, transform, model in moving_models do
|
||||||
local cf = transform.new
|
local cf = transform.new
|
||||||
if cf ~= transform.old then
|
if cf ~= transform.old then
|
||||||
local origo = model.PrimaryPart.CFrame
|
local part = model.PrimaryPart :: BasePart
|
||||||
model.PrimaryPart.CFrame = origo:Lerp(cf, 1)
|
local origo = part.CFrame
|
||||||
|
part.CFrame = origo:Lerp(cf, 1)
|
||||||
transform.old = cf
|
transform.old = cf
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,8 +24,8 @@ end
|
||||||
|
|
||||||
local function syncTransforms()
|
local function syncTransforms()
|
||||||
for _, id, cf in blink.UpdateTransform.Iter() do
|
for _, id, cf in blink.UpdateTransform.Iter() do
|
||||||
local e = ref("server-" .. id)
|
local e = ref("server-" .. tostring(id))
|
||||||
local transform = e:get(cts.Transform)
|
local transform = world:get(e, Transform)
|
||||||
if not transform then
|
if not transform then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
@ -31,9 +33,9 @@ local function syncTransforms()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(scheduler: std.Scheduler)
|
local scheduler = require(std.scheduler)
|
||||||
local phases = scheduler.phases
|
|
||||||
local system_new = scheduler.systems.new
|
scheduler.SYSTEM(move)
|
||||||
system_new(move, phases.Heartbeat)
|
scheduler.SYSTEM(syncTransforms)
|
||||||
system_new(syncTransforms, phases.RenderStepped)
|
|
||||||
end
|
return 0
|
|
@ -1,9 +1,9 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local blink = require(ReplicatedStorage.net)
|
local blink = require(ReplicatedStorage.net)
|
||||||
local std = require(ReplicatedStorage.std)
|
local std = ReplicatedStorage.std
|
||||||
local ref = std.ref
|
local ref = require(std.ref)
|
||||||
local world = std.world
|
local world = require(std.world)
|
||||||
local cts = std.components
|
local cts = require(std.components)
|
||||||
|
|
||||||
local function syncMobs()
|
local function syncMobs()
|
||||||
for _, id, cf, vel in blink.SpawnMob.Iter() do
|
for _, id, cf, vel in blink.SpawnMob.Iter() do
|
||||||
|
@ -16,16 +16,16 @@ local function syncMobs()
|
||||||
part.Parent = model
|
part.Parent = model
|
||||||
model.Parent = workspace
|
model.Parent = workspace
|
||||||
|
|
||||||
ref("server-" .. id)
|
local e = ref("server-" .. tostring(id))
|
||||||
:set(cts.Transform, { new = cf, old = cf })
|
world:set(e, cts.Transform, { new = cf, old = cf })
|
||||||
:set(cts.Velocity, vel)
|
world:set(e, cts.Velocity, vel)
|
||||||
:set(cts.Model, model)
|
world:set(e, cts.Model, model)
|
||||||
:add(cts.Mob)
|
world:add(e, cts.Mob)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return function(scheduler: std.Scheduler)
|
local scheduler = require(std.scheduler)
|
||||||
local phases = scheduler.phases
|
scheduler.SYSTEM(syncMobs)
|
||||||
local system_new = scheduler.systems.new
|
|
||||||
system_new(syncMobs, phases.RenderStepped)
|
return 0
|
||||||
end
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local std = ReplicatedStorage.std
|
||||||
|
local world = require(std.world)
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local function flip()
|
||||||
|
return math.random() >= 0.15
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, 2^8 do
|
||||||
|
local e = world:entity()
|
||||||
|
if flip() then
|
||||||
|
world:set(e, A, true)
|
||||||
|
end
|
||||||
|
if flip() then
|
||||||
|
world:set(e, B, true)
|
||||||
|
end
|
||||||
|
if flip() then
|
||||||
|
world:set(e, C, true)
|
||||||
|
end
|
||||||
|
if flip() then
|
||||||
|
world:set(e, D, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function uncached()
|
||||||
|
for _ in world:query(A, B, C, D) do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local q = world:query(A, B, C, D):cached()
|
||||||
|
local function cached()
|
||||||
|
for _ in q do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local scheduler = require(std.scheduler)
|
||||||
|
scheduler.SYSTEM(uncached)
|
||||||
|
scheduler.SYSTEM(cached)
|
||||||
|
return 0
|
672
jecs.luau
672
jecs.luau
|
@ -78,30 +78,32 @@ type EntityIndex = {
|
||||||
|
|
||||||
local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256
|
local HI_COMPONENT_ID = _G.__JECS_HI_COMPONENT_ID or 256
|
||||||
-- stylua: ignore start
|
-- stylua: ignore start
|
||||||
local EcsOnAdd = HI_COMPONENT_ID + 1
|
local EcsOnAdd = HI_COMPONENT_ID + 1
|
||||||
local EcsOnRemove = HI_COMPONENT_ID + 2
|
local EcsOnRemove = HI_COMPONENT_ID + 2
|
||||||
local EcsOnSet = HI_COMPONENT_ID + 3
|
local EcsOnSet = HI_COMPONENT_ID + 3
|
||||||
local EcsWildcard = HI_COMPONENT_ID + 4
|
local EcsWildcard = HI_COMPONENT_ID + 4
|
||||||
local EcsChildOf = HI_COMPONENT_ID + 5
|
local EcsChildOf = HI_COMPONENT_ID + 5
|
||||||
local EcsComponent = HI_COMPONENT_ID + 6
|
local EcsComponent = HI_COMPONENT_ID + 6
|
||||||
local EcsOnDelete = HI_COMPONENT_ID + 7
|
local EcsOnDelete = HI_COMPONENT_ID + 7
|
||||||
local EcsOnDeleteTarget = HI_COMPONENT_ID + 8
|
local EcsOnDeleteTarget = HI_COMPONENT_ID + 8
|
||||||
local EcsDelete = HI_COMPONENT_ID + 9
|
local EcsDelete = HI_COMPONENT_ID + 9
|
||||||
local EcsRemove = HI_COMPONENT_ID + 10
|
local EcsRemove = HI_COMPONENT_ID + 10
|
||||||
local EcsName = HI_COMPONENT_ID + 11
|
local EcsName = HI_COMPONENT_ID + 11
|
||||||
local EcsRest = HI_COMPONENT_ID + 12
|
local EcsArchetypeCreate = HI_COMPONENT_ID + 12
|
||||||
|
local EcsArchetypeDelete = HI_COMPONENT_ID + 13
|
||||||
|
local EcsRest = HI_COMPONENT_ID + 14
|
||||||
|
|
||||||
local ECS_PAIR_FLAG = 0x8
|
local ECS_PAIR_FLAG = 0x8
|
||||||
local ECS_ID_FLAGS_MASK = 0x10
|
local ECS_ID_FLAGS_MASK = 0x10
|
||||||
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||||
|
|
||||||
local ECS_ID_DELETE = 0b0000_0001
|
local ECS_ID_DELETE = 0b0000_0001
|
||||||
local ECS_ID_IS_TAG = 0b0000_0010
|
local ECS_ID_IS_TAG = 0b0000_0010
|
||||||
local ECS_ID_HAS_ON_ADD = 0b0000_0100
|
local ECS_ID_HAS_ON_ADD = 0b0000_0100
|
||||||
local ECS_ID_HAS_ON_SET = 0b0000_1000
|
local ECS_ID_HAS_ON_SET = 0b0000_1000
|
||||||
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
|
local ECS_ID_HAS_ON_REMOVE = 0b0001_0000
|
||||||
local ECS_ID_MASK = 0b0000_0000
|
local ECS_ID_MASK = 0b0000_0000
|
||||||
-- stylua: ignore end
|
-- stylua: ignore end
|
||||||
local NULL_ARRAY = table.freeze({}) :: Column
|
local NULL_ARRAY = table.freeze({}) :: Column
|
||||||
|
|
||||||
|
@ -250,6 +252,36 @@ local function ecs_pair_second(world, e)
|
||||||
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_HI(e))
|
return entity_index_get_alive(world.entity_index, ECS_ENTITY_T_HI(e))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function query_match(query, archetype: Archetype)
|
||||||
|
local records = archetype.records
|
||||||
|
local with = query.filter_with
|
||||||
|
|
||||||
|
for _, id in with do
|
||||||
|
if not records[id] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local without = query.filter_without
|
||||||
|
if without then
|
||||||
|
for _, id in without do
|
||||||
|
if records[id] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_observers(world: World, event, component): { Observer }?
|
||||||
|
local cache = world.observerable[event]
|
||||||
|
if not cache then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return cache[component] :: any
|
||||||
|
end
|
||||||
|
|
||||||
local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row: i24, from: Archetype, src_row: i24)
|
local function archetype_move(entity_index: EntityIndex, to: Archetype, dst_row: i24, from: Archetype, src_row: i24)
|
||||||
local src_columns = from.columns
|
local src_columns = from.columns
|
||||||
local dst_columns = to.columns
|
local dst_columns = to.columns
|
||||||
|
@ -549,6 +581,20 @@ local function archetype_create(world: World, id_types: { i24 }, ty, prev: i53?)
|
||||||
local columns = (table.create(length) :: any) :: { Column }
|
local columns = (table.create(length) :: any) :: { Column }
|
||||||
|
|
||||||
local records: { ArchetypeRecord } = {}
|
local records: { ArchetypeRecord } = {}
|
||||||
|
|
||||||
|
local archetype: Archetype = {
|
||||||
|
columns = columns,
|
||||||
|
entities = {},
|
||||||
|
id = archetype_id,
|
||||||
|
records = records,
|
||||||
|
type = ty,
|
||||||
|
types = id_types,
|
||||||
|
|
||||||
|
add = {},
|
||||||
|
remove = {},
|
||||||
|
refs = {} :: GraphEdge,
|
||||||
|
}
|
||||||
|
|
||||||
for i, componentId in id_types do
|
for i, componentId in id_types do
|
||||||
local idr = id_record_ensure(world, componentId)
|
local idr = id_record_ensure(world, componentId)
|
||||||
archetype_append_to_records(idr, archetype_id, records, componentId, i)
|
archetype_append_to_records(idr, archetype_id, records, componentId, i)
|
||||||
|
@ -572,18 +618,17 @@ local function archetype_create(world: World, id_types: { i24 }, ty, prev: i53?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype: Archetype = {
|
for _, id in id_types do
|
||||||
columns = columns,
|
local observer_list = find_observers(world, EcsArchetypeCreate, id)
|
||||||
entities = {},
|
if not observer_list then
|
||||||
id = archetype_id,
|
continue
|
||||||
records = records,
|
end
|
||||||
type = ty,
|
for _, observer in observer_list do
|
||||||
types = id_types,
|
if query_match(observer.query, archetype) then
|
||||||
|
observer.callback(archetype)
|
||||||
add = {},
|
end
|
||||||
remove = {},
|
end
|
||||||
refs = {} :: GraphEdge,
|
end
|
||||||
}
|
|
||||||
|
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[archetype_id] = archetype
|
world.archetypes[archetype_id] = archetype
|
||||||
|
@ -626,13 +671,13 @@ local function find_insert(id_types: { i53 }, toAdd: i53): number
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_archetype_with(world: World, node: Archetype, id: i53): Archetype
|
local function find_archetype_with(world: World, node: Archetype, id: i53): Archetype
|
||||||
local types = node.types
|
local id_types = node.types
|
||||||
-- Component IDs are added incrementally, so inserting and sorting
|
-- Component IDs are added incrementally, so inserting and sorting
|
||||||
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
||||||
-- point in the types array.
|
-- point in the types array.
|
||||||
|
|
||||||
local dst = table.clone(node.types) :: { i53 }
|
local dst = table.clone(node.types) :: { i53 }
|
||||||
local at = find_insert(types, id)
|
local at = find_insert(id_types, id)
|
||||||
if at == -1 then
|
if at == -1 then
|
||||||
-- If it finds a duplicate, it just means it is the same archetype so it can return it
|
-- If it finds a duplicate, it just means it is the same archetype so it can return it
|
||||||
-- directly instead of needing to hash types for a lookup to the archetype.
|
-- directly instead of needing to hash types for a lookup to the archetype.
|
||||||
|
@ -644,13 +689,13 @@ local function find_archetype_with(world: World, node: Archetype, id: i53): Arch
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_archetype_without(world: World, node: Archetype, id: i53): Archetype
|
local function find_archetype_without(world: World, node: Archetype, id: i53): Archetype
|
||||||
local types = node.types
|
local id_types = node.types
|
||||||
local at = table.find(types, id)
|
local at = table.find(id_types, id)
|
||||||
if at == nil then
|
if at == nil then
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
local dst = table.clone(types)
|
local dst = table.clone(id_types)
|
||||||
table.remove(dst, at)
|
table.remove(dst, at)
|
||||||
|
|
||||||
return archetype_ensure(world, dst)
|
return archetype_ensure(world, dst)
|
||||||
|
@ -1006,6 +1051,18 @@ local function archetype_destroy(world: World, archetype: Archetype)
|
||||||
world.archetypeIndex[archetype.type] = nil :: any
|
world.archetypeIndex[archetype.type] = nil :: any
|
||||||
local records = archetype.records
|
local records = archetype.records
|
||||||
|
|
||||||
|
for id in records do
|
||||||
|
local observer_list = find_observers(world, EcsArchetypeDelete, id)
|
||||||
|
if not observer_list then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
for _, observer in observer_list do
|
||||||
|
if query_match(observer.query, archetype) then
|
||||||
|
observer.callback(archetype)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for id in records do
|
for id in records do
|
||||||
local idr = component_index[id]
|
local idr = component_index[id]
|
||||||
idr.cache[archetype_id] = nil :: any
|
idr.cache[archetype_id] = nil :: any
|
||||||
|
@ -1064,23 +1121,30 @@ do
|
||||||
local idr = component_index[delete]
|
local idr = component_index[delete]
|
||||||
|
|
||||||
if idr then
|
if idr then
|
||||||
local children = {}
|
|
||||||
for archetype_id in idr.cache do
|
|
||||||
local idr_archetype = archetypes[archetype_id]
|
|
||||||
|
|
||||||
for i, child in idr_archetype.entities do
|
|
||||||
table.insert(children, child)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local flags = idr.flags
|
local flags = idr.flags
|
||||||
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
|
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
|
||||||
for _, child in children do
|
for archetype_id in idr.cache do
|
||||||
-- Cascade deletion to children
|
local idr_archetype = archetypes[archetype_id]
|
||||||
world_delete(world, child)
|
|
||||||
|
local entities = idr_archetype.entities
|
||||||
|
local n = #entities
|
||||||
|
for i = n, 1, -1 do
|
||||||
|
world_delete(world, entities[i])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for _, child in children do
|
for archetype_id in idr.cache do
|
||||||
world_remove(world, child, delete)
|
local idr_archetype = archetypes[archetype_id]
|
||||||
|
local entities = idr_archetype.entities
|
||||||
|
local n = #entities
|
||||||
|
for i = n, 1, -1 do
|
||||||
|
world_remove(world, entities[i], delete)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for archetype_id in idr.cache do
|
||||||
|
local idr_archetype = archetypes[archetype_id]
|
||||||
|
archetype_destroy(world, idr_archetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1167,7 +1231,7 @@ local EMPTY_QUERY = {
|
||||||
|
|
||||||
setmetatable(EMPTY_QUERY, EMPTY_QUERY)
|
setmetatable(EMPTY_QUERY, EMPTY_QUERY)
|
||||||
|
|
||||||
local function query_iter_init(query): () -> (number, ...any)
|
local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
local world_query_iter_next
|
local world_query_iter_next
|
||||||
|
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
|
@ -1246,7 +1310,325 @@ local function query_iter_init(query): () -> (number, ...any)
|
||||||
i = #entities
|
i = #entities
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
local records = archetype.records
|
records = archetype.records
|
||||||
|
a = columns[records[A].column]
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = i
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return entityId, a[row]
|
||||||
|
end
|
||||||
|
elseif not C then
|
||||||
|
function world_query_iter_next(): any
|
||||||
|
local entityId = entities[i]
|
||||||
|
while entityId == nil do
|
||||||
|
lastArchetype += 1
|
||||||
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
entityId = entities[i]
|
||||||
|
columns = archetype.columns
|
||||||
|
records = archetype.records
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = i
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return entityId, a[row], b[row]
|
||||||
|
end
|
||||||
|
elseif not D then
|
||||||
|
function world_query_iter_next(): any
|
||||||
|
local entityId = entities[i]
|
||||||
|
while entityId == nil do
|
||||||
|
lastArchetype += 1
|
||||||
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
entityId = entities[i]
|
||||||
|
columns = archetype.columns
|
||||||
|
records = archetype.records
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = i
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return entityId, a[row], b[row], c[row]
|
||||||
|
end
|
||||||
|
elseif not E then
|
||||||
|
function world_query_iter_next(): any
|
||||||
|
local entityId = entities[i]
|
||||||
|
while entityId == nil do
|
||||||
|
lastArchetype += 1
|
||||||
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
entityId = entities[i]
|
||||||
|
columns = archetype.columns
|
||||||
|
records = archetype.records
|
||||||
|
a = columns[records[A].column]
|
||||||
|
b = columns[records[B].column]
|
||||||
|
c = columns[records[C].column]
|
||||||
|
d = columns[records[D].column]
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = i
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return entityId, a[row], b[row], c[row], d[row]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local queryOutput = {}
|
||||||
|
function world_query_iter_next(): any
|
||||||
|
local entityId = entities[i]
|
||||||
|
while entityId == nil do
|
||||||
|
lastArchetype += 1
|
||||||
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
entityId = entities[i]
|
||||||
|
columns = archetype.columns
|
||||||
|
records = archetype.records
|
||||||
|
|
||||||
|
if 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
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = i
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
if not F then
|
||||||
|
return entityId, a[row], b[row], c[row], d[row], e[row]
|
||||||
|
elseif not G then
|
||||||
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row]
|
||||||
|
elseif not H then
|
||||||
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row]
|
||||||
|
elseif not I then
|
||||||
|
return entityId, a[row], b[row], c[row], d[row], e[row], f[row], g[row], h[row]
|
||||||
|
end
|
||||||
|
|
||||||
|
local records = archetype.records
|
||||||
|
for j, id in ids do
|
||||||
|
queryOutput[j] = columns[records[id].column][row]
|
||||||
|
end
|
||||||
|
|
||||||
|
return entityId, unpack(queryOutput)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
query.next = world_query_iter_next
|
||||||
|
return world_query_iter_next
|
||||||
|
end
|
||||||
|
|
||||||
|
local function query_iter(query): () -> (number, ...any)
|
||||||
|
local query_next = query.next
|
||||||
|
if not query_next then
|
||||||
|
query_next = query_iter_init(query)
|
||||||
|
end
|
||||||
|
return query_next
|
||||||
|
end
|
||||||
|
|
||||||
|
local function query_without(query: QueryInner, ...: i53)
|
||||||
|
local without = { ... }
|
||||||
|
query.filter_without = without
|
||||||
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
|
local archetype = compatible_archetypes[i]
|
||||||
|
local records = archetype.records
|
||||||
|
local matches = true
|
||||||
|
|
||||||
|
for _, id in without do
|
||||||
|
if records[id] then
|
||||||
|
matches = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if matches then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local last = #compatible_archetypes
|
||||||
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
return query :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
local function query_with(query: QueryInner, ...: i53)
|
||||||
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
|
local with = { ... }
|
||||||
|
query.filter_with = with
|
||||||
|
|
||||||
|
for i = #compatible_archetypes, 1, -1 do
|
||||||
|
local archetype = compatible_archetypes[i]
|
||||||
|
local records = archetype.records
|
||||||
|
local matches = true
|
||||||
|
|
||||||
|
for _, id in with do
|
||||||
|
if not records[id] then
|
||||||
|
matches = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if matches then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local last = #compatible_archetypes
|
||||||
|
if last ~= i then
|
||||||
|
compatible_archetypes[i] = compatible_archetypes[last]
|
||||||
|
end
|
||||||
|
compatible_archetypes[last] = nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
return query :: any
|
||||||
|
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 query_archetypes(query)
|
||||||
|
return query.compatible_archetypes
|
||||||
|
end
|
||||||
|
|
||||||
|
local function query_cached(query: QueryInner)
|
||||||
|
local archetypes = query.compatible_archetypes
|
||||||
|
local world = query.world :: World
|
||||||
|
-- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively
|
||||||
|
-- because the event will be emitted for all components of that Archetype.
|
||||||
|
local first = query.ids[1]
|
||||||
|
local observerable = world.observerable
|
||||||
|
local on_create_action = observerable[EcsArchetypeCreate]
|
||||||
|
if not on_create_action then
|
||||||
|
on_create_action = {}
|
||||||
|
observerable[EcsArchetypeCreate] = on_create_action
|
||||||
|
end
|
||||||
|
local query_cache_on_create = on_create_action[first]
|
||||||
|
if not query_cache_on_create then
|
||||||
|
query_cache_on_create = {}
|
||||||
|
on_create_action[first] = query_cache_on_create
|
||||||
|
end
|
||||||
|
|
||||||
|
local on_delete_action = observerable[EcsArchetypeDelete]
|
||||||
|
if not on_delete_action then
|
||||||
|
on_delete_action = {}
|
||||||
|
observerable[EcsArchetypeDelete] = on_delete_action
|
||||||
|
end
|
||||||
|
local query_cache_on_delete = on_delete_action[first]
|
||||||
|
if not query_cache_on_delete then
|
||||||
|
query_cache_on_delete = {}
|
||||||
|
on_delete_action[first] = query_cache_on_delete
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_create_callback(archetype)
|
||||||
|
table.insert(archetypes, archetype)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_delete_callback(archetype)
|
||||||
|
local i = table.find(archetypes, archetype) :: number
|
||||||
|
local n = #archetypes
|
||||||
|
archetypes[i] = archetypes[n]
|
||||||
|
archetypes[n] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local with = query.filter_with
|
||||||
|
local ids = query.ids
|
||||||
|
if with then
|
||||||
|
table.move(ids, 1, #ids, #with, with)
|
||||||
|
else
|
||||||
|
query.filter_with = ids
|
||||||
|
end
|
||||||
|
|
||||||
|
local observer_for_create = { query = query, callback = on_create_callback }
|
||||||
|
local observer_for_delete = { query = query, callback = on_delete_callback }
|
||||||
|
|
||||||
|
table.insert(query_cache_on_create, observer_for_create)
|
||||||
|
table.insert(query_cache_on_delete, observer_for_delete)
|
||||||
|
|
||||||
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
|
local lastArchetype = 1
|
||||||
|
|
||||||
|
local A, B, C, D, E, F, G, H, I = unpack(ids)
|
||||||
|
local a: Column, b: Column, c: Column, d: Column
|
||||||
|
local e: Column, f: Column, g: Column, h: Column
|
||||||
|
|
||||||
|
local world_query_iter_next
|
||||||
|
local columns: { Column }
|
||||||
|
local entities: { i53 }
|
||||||
|
local i: number
|
||||||
|
local archetype: Archetype
|
||||||
|
local records: { ArchetypeRecord }
|
||||||
|
|
||||||
|
if not B then
|
||||||
|
function world_query_iter_next(): any
|
||||||
|
local entityId = entities[i]
|
||||||
|
while entityId == nil do
|
||||||
|
lastArchetype += 1
|
||||||
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
entities = archetype.entities
|
||||||
|
i = #entities
|
||||||
|
entityId = entities[i]
|
||||||
|
columns = archetype.columns
|
||||||
|
records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1402,85 +1784,71 @@ local function query_iter_init(query): () -> (number, ...any)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
query.next = world_query_iter_next
|
local function cached_query_iter()
|
||||||
return world_query_iter_next
|
lastArchetype = 1
|
||||||
end
|
archetype = compatible_archetypes[lastArchetype]
|
||||||
|
if not archetype then
|
||||||
local function query_iter(query): () -> (number, ...any)
|
return NOOP
|
||||||
local query_next = query.next
|
end
|
||||||
if not query_next then
|
entities = archetype.entities
|
||||||
query_next = query_iter_init(query)
|
i = #entities
|
||||||
end
|
records = archetype.records
|
||||||
return query_next
|
columns = archetype.columns
|
||||||
end
|
if not B then
|
||||||
|
a = columns[records[A].column]
|
||||||
local function query_without(query: { compatible_archetypes: { Archetype } }, ...)
|
elseif not C then
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
a = columns[records[A].column]
|
||||||
local N = select("#", ...)
|
b = columns[records[B].column]
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
elseif not D then
|
||||||
local archetype = compatible_archetypes[i]
|
a = columns[records[A].column]
|
||||||
local records = archetype.records
|
b = columns[records[B].column]
|
||||||
local shouldRemove = false
|
c = columns[records[C].column]
|
||||||
|
elseif not E then
|
||||||
for j = 1, N do
|
a = columns[records[A].column]
|
||||||
local id = select(j, ...)
|
b = columns[records[B].column]
|
||||||
if records[id] then
|
c = columns[records[C].column]
|
||||||
shouldRemove = true
|
d = columns[records[D].column]
|
||||||
break
|
elseif not F then
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
return world_query_iter_next
|
||||||
local last = #compatible_archetypes
|
|
||||||
if last ~= i then
|
|
||||||
compatible_archetypes[i] = compatible_archetypes[last]
|
|
||||||
end
|
|
||||||
compatible_archetypes[last] = nil :: any
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #compatible_archetypes == 0 then
|
local cached_query = query :: any
|
||||||
return EMPTY_QUERY
|
cached_query.archetypes = query_archetypes
|
||||||
end
|
cached_query.__iter = cached_query_iter
|
||||||
|
cached_query.iter = cached_query_iter
|
||||||
return query :: any
|
setmetatable(cached_query, cached_query)
|
||||||
end
|
return cached_query
|
||||||
|
|
||||||
local function query_with(query: { compatible_archetypes: { Archetype } }, ...)
|
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
|
||||||
local N = select("#", ...)
|
|
||||||
for i = #compatible_archetypes, 1, -1 do
|
|
||||||
local archetype = compatible_archetypes[i]
|
|
||||||
local records = archetype.records
|
|
||||||
local shouldRemove = false
|
|
||||||
|
|
||||||
for j = 1, N do
|
|
||||||
local id = select(j, ...)
|
|
||||||
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 :: any
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #compatible_archetypes == 0 then
|
|
||||||
return EMPTY_QUERY
|
|
||||||
end
|
|
||||||
return query :: any
|
|
||||||
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 query_archetypes(query)
|
|
||||||
return query.compatible_archetypes
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local Query = {}
|
local Query = {}
|
||||||
|
@ -1490,6 +1858,7 @@ Query.iter = query_iter_init
|
||||||
Query.without = query_without
|
Query.without = query_without
|
||||||
Query.with = query_with
|
Query.with = query_with
|
||||||
Query.archetypes = query_archetypes
|
Query.archetypes = query_archetypes
|
||||||
|
Query.cached = query_cached
|
||||||
|
|
||||||
local function world_query(world: World, ...)
|
local function world_query(world: World, ...)
|
||||||
local compatible_archetypes = {}
|
local compatible_archetypes = {}
|
||||||
|
@ -1502,10 +1871,16 @@ local function world_query(world: World, ...)
|
||||||
local idr: IdRecord?
|
local idr: IdRecord?
|
||||||
local componentIndex = world.componentIndex
|
local componentIndex = world.componentIndex
|
||||||
|
|
||||||
|
local q = setmetatable({
|
||||||
|
ids = ids,
|
||||||
|
compatible_archetypes = compatible_archetypes,
|
||||||
|
world = world,
|
||||||
|
}, Query)
|
||||||
|
|
||||||
for _, id in ids do
|
for _, id in ids do
|
||||||
local map = componentIndex[id]
|
local map = componentIndex[id]
|
||||||
if not map then
|
if not map then
|
||||||
return EMPTY_QUERY
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
if idr == nil or map.size < idr.size then
|
if idr == nil or map.size < idr.size then
|
||||||
|
@ -1514,7 +1889,7 @@ local function world_query(world: World, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not idr then
|
if not idr then
|
||||||
return EMPTY_QUERY
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
for archetype_id in idr.cache do
|
for archetype_id in idr.cache do
|
||||||
|
@ -1542,15 +1917,6 @@ local function world_query(world: World, ...)
|
||||||
compatible_archetypes[length] = compatibleArchetype
|
compatible_archetypes[length] = compatibleArchetype
|
||||||
end
|
end
|
||||||
|
|
||||||
if length == 0 then
|
|
||||||
return EMPTY_QUERY
|
|
||||||
end
|
|
||||||
|
|
||||||
local q = setmetatable({
|
|
||||||
compatible_archetypes = compatible_archetypes,
|
|
||||||
ids = ids,
|
|
||||||
}, Query) :: any
|
|
||||||
|
|
||||||
return q
|
return q
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1736,6 +2102,7 @@ function World.new()
|
||||||
nextComponentId = 0 :: number,
|
nextComponentId = 0 :: number,
|
||||||
nextEntityId = 0 :: number,
|
nextEntityId = 0 :: number,
|
||||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
||||||
|
observerable = {},
|
||||||
}, World) :: any
|
}, World) :: any
|
||||||
|
|
||||||
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
self.ROOT_ARCHETYPE = archetype_create(self, {}, "")
|
||||||
|
@ -1781,7 +2148,7 @@ type function ecs_entity_t(entity)
|
||||||
return entity:components()[2]:readproperty(types.singleton("__T"))
|
return entity:components()[2]:readproperty(types.singleton("__T"))
|
||||||
end
|
end
|
||||||
|
|
||||||
export type function Pair(first, second)
|
type function Pair(first, second)
|
||||||
local thing = first:components()[2]
|
local thing = first:components()[2]
|
||||||
|
|
||||||
if thing:readproperty(types.singleton("__T")):is("nil") then
|
if thing:readproperty(types.singleton("__T")):is("nil") then
|
||||||
|
@ -1797,15 +2164,38 @@ export type Entity<T = unknown> = number & { __T: T }
|
||||||
|
|
||||||
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||||
|
|
||||||
type Query<T...> = typeof(setmetatable({}, {
|
export type Query<T...> = typeof(setmetatable({}, {
|
||||||
__iter = (nil :: any) :: Iter<T...>,
|
__iter = (nil :: any) :: Iter<T...>,
|
||||||
})) & {
|
})) & {
|
||||||
iter: Iter<T...>,
|
iter: Iter<T...>,
|
||||||
with: (self: Query<T...>, ...i53) -> Query<T...>,
|
with: (self: Query<T...>, ...Id) -> Query<T...>,
|
||||||
without: (self: Query<T...>, ...i53) -> Query<T...>,
|
without: (self: Query<T...>, ...Id) -> Query<T...>,
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueryInner = {
|
||||||
|
compatible_archetypes: { Archetype },
|
||||||
|
filter_with: { i53 }?,
|
||||||
|
filter_without: { i53 }?,
|
||||||
|
ids: { i53 },
|
||||||
|
world: {}, -- Downcasted to be serializable by the analyzer
|
||||||
|
next: () -> Item<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
type Observer = {
|
||||||
|
callback: (archetype: Archetype) -> (),
|
||||||
|
query: QueryInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
type function ecs_partial_t(ty)
|
||||||
|
local output = types.newtable()
|
||||||
|
for k, v in ty:properties() do
|
||||||
|
output:setproperty(k, types.unionof(v.write, types.singleton(nil)))
|
||||||
|
end
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
archetypeIndex: { [string]: Archetype },
|
archetypeIndex: { [string]: Archetype },
|
||||||
archetypes: Archetypes,
|
archetypes: Archetypes,
|
||||||
|
@ -1816,6 +2206,8 @@ export type World = {
|
||||||
nextComponentId: number,
|
nextComponentId: number,
|
||||||
nextEntityId: number,
|
nextEntityId: number,
|
||||||
nextArchetypeId: number,
|
nextArchetypeId: number,
|
||||||
|
|
||||||
|
observerable: { [i53]: { [i53]: { { query: Query<i53> } } } },
|
||||||
} & {
|
} & {
|
||||||
--- Creates a new entity
|
--- Creates a new entity
|
||||||
entity: (self: World) -> Entity,
|
entity: (self: World) -> Entity,
|
||||||
|
|
|
@ -63,6 +63,10 @@ local function debug_world_inspect(world: World)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function name(world, e)
|
||||||
|
return world:get(e, jecs.Name)
|
||||||
|
end
|
||||||
|
|
||||||
TEST("archetype", function()
|
TEST("archetype", function()
|
||||||
local archetype_append_to_records = jecs.archetype_append_to_records
|
local archetype_append_to_records = jecs.archetype_append_to_records
|
||||||
local id_record_ensure = jecs.id_record_ensure
|
local id_record_ensure = jecs.id_record_ensure
|
||||||
|
@ -359,6 +363,39 @@ TEST("world:add()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:query()", function()
|
TEST("world:query()", function()
|
||||||
|
do CASE "cached"
|
||||||
|
local world = world_new()
|
||||||
|
local Foo = world:component()
|
||||||
|
local Bar = world:component()
|
||||||
|
local Baz = world:component()
|
||||||
|
local e = world:entity()
|
||||||
|
local q = world:query(Foo, Bar):without(Baz):cached()
|
||||||
|
world:set(e, Foo, true)
|
||||||
|
world:set(e, Bar, false)
|
||||||
|
local i = 0
|
||||||
|
|
||||||
|
for _, e in q:iter() do
|
||||||
|
i=1
|
||||||
|
end
|
||||||
|
CHECK(i == 1)
|
||||||
|
for _, e in q:iter() do
|
||||||
|
i=2
|
||||||
|
end
|
||||||
|
CHECK(i == 2)
|
||||||
|
for _, e in q do
|
||||||
|
i=3
|
||||||
|
end
|
||||||
|
CHECK(i == 3)
|
||||||
|
for _, e in q do
|
||||||
|
i=4
|
||||||
|
end
|
||||||
|
CHECK(i == 4)
|
||||||
|
|
||||||
|
CHECK(#q:archetypes() == 1)
|
||||||
|
CHECK(not table.find(q:archetypes(), world.archetypes[table.concat({Foo, Bar, Baz}, "_")]))
|
||||||
|
world:delete(Foo)
|
||||||
|
CHECK(#q:archetypes() == 0)
|
||||||
|
end
|
||||||
do CASE("multiple iter")
|
do CASE("multiple iter")
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
@ -814,40 +851,6 @@ TEST("world:query()", function()
|
||||||
CHECK(withoutCount == 0)
|
CHECK(withoutCount == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
|
||||||
CASE("Empty Query")
|
|
||||||
do
|
|
||||||
local world = jecs.World.new()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e1, A)
|
|
||||||
|
|
||||||
local query = world:query(B)
|
|
||||||
CHECK(query:without() == query)
|
|
||||||
CHECK(query:with() == query)
|
|
||||||
-- They always return the same EMPTY_LIST
|
|
||||||
CHECK(query:archetypes() == world:query(B):archetypes())
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
local world = jecs.World.new()
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e1, A)
|
|
||||||
|
|
||||||
local count = 0
|
|
||||||
for id in world:query(B) do
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(count == 0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
do
|
||||||
CASE("without")
|
CASE("without")
|
||||||
do
|
do
|
||||||
|
|
Loading…
Reference in a new issue