Cleanup & refactor
This commit is contained in:
parent
d3b6212463
commit
2a6907434a
50 changed files with 937 additions and 4110 deletions
|
@ -1,135 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../jecs")
|
||||
type entity<T = nil> = jecs.Entity<T>
|
||||
type id<T = nil> = jecs.Id<T>
|
||||
|
||||
local _world = require("./world")
|
||||
local WORLD = _world.get
|
||||
|
||||
-- luau-lsp literally dies if you use the actual world type
|
||||
type jecs_world = any
|
||||
|
||||
--- `map<component_id, array<entity_id>>`
|
||||
local add_commands: { [jecs_world]: { [id]: { entity } } } = {}
|
||||
--- `map<component_id, array<entity_id, component_value>>`
|
||||
local set_commands: { [jecs_world]: { [id]: { [entity]: any } } } = {}
|
||||
--- `map<component_id, array<entity_id>>`
|
||||
local remove_commands: { [jecs_world]: { [id]: { entity } } } = {}
|
||||
--- `array<entity_id>`
|
||||
local delete_commands: { [jecs_world]: { entity } } = {}
|
||||
|
||||
_world.on_set(function(world)
|
||||
add_commands[world] = {}
|
||||
set_commands[world] = {}
|
||||
remove_commands[world] = {}
|
||||
delete_commands[world] = {}
|
||||
end)
|
||||
|
||||
export type command_buffer = {
|
||||
--- Execute all buffered commands and clear the buffer
|
||||
flush: () -> (),
|
||||
|
||||
--- Adds a component to the entity with no value
|
||||
add: (entity: entity, component: id) -> (),
|
||||
--- Assigns a value to a component on the given entity
|
||||
set: <T>(entity: entity, component: id<T>, data: T) -> (),
|
||||
--- Removes a component from the given entity
|
||||
remove: (entity: entity, component: id) -> (),
|
||||
--- Deletes an entity from the world
|
||||
delete: (entity: entity) -> (),
|
||||
}
|
||||
|
||||
local function flush()
|
||||
for world, entities in delete_commands do
|
||||
for _, entity in entities do
|
||||
world:delete(entity)
|
||||
end
|
||||
end
|
||||
|
||||
for world, commands in add_commands do
|
||||
for component, entities in commands do
|
||||
for _, entity in entities do
|
||||
if delete_commands[world][entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:add(entity, component)
|
||||
end
|
||||
end
|
||||
table.clear(add_commands[world])
|
||||
end
|
||||
|
||||
for world, commands in set_commands do
|
||||
for component, entities in commands do
|
||||
for entity, value in entities do
|
||||
if delete_commands[world][entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:set(entity, component, value)
|
||||
end
|
||||
end
|
||||
table.clear(set_commands[world])
|
||||
end
|
||||
|
||||
for world, commands in remove_commands do
|
||||
for component, entities in commands do
|
||||
for _, entity in entities do
|
||||
if delete_commands[world][entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:remove(entity, component)
|
||||
end
|
||||
end
|
||||
table.clear(remove_commands[world])
|
||||
end
|
||||
|
||||
for world in delete_commands do
|
||||
table.clear(delete_commands[world])
|
||||
end
|
||||
end
|
||||
|
||||
local function add(entity: entity, component: id)
|
||||
local world = WORLD()
|
||||
if not add_commands[world][component] then
|
||||
add_commands[world][component] = {}
|
||||
end
|
||||
|
||||
table.insert(add_commands[world][component], entity)
|
||||
end
|
||||
|
||||
local function set<T>(entity: entity, component: id<T>, data: T)
|
||||
local world = WORLD()
|
||||
if not set_commands[world][component] then
|
||||
set_commands[world][component] = {}
|
||||
end
|
||||
|
||||
set_commands[world][component][entity] = data
|
||||
end
|
||||
|
||||
local function remove(entity: entity, component: id)
|
||||
local world = WORLD()
|
||||
if not remove_commands[world][component] then
|
||||
remove_commands[world][component] = {}
|
||||
end
|
||||
|
||||
table.insert(remove_commands[world][component], entity)
|
||||
end
|
||||
|
||||
local function delete(entity: entity)
|
||||
local world = WORLD()
|
||||
table.insert(delete_commands[world], entity)
|
||||
end
|
||||
|
||||
local command_buffer: command_buffer = {
|
||||
flush = flush,
|
||||
|
||||
add = add,
|
||||
set = set,
|
||||
remove = remove,
|
||||
delete = delete,
|
||||
}
|
||||
|
||||
return command_buffer
|
|
@ -1,78 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../jecs")
|
||||
type entity<T = nil> = jecs.Entity<T>
|
||||
type id<T = nil> = jecs.Id<T>
|
||||
|
||||
local world = require("./world").get
|
||||
|
||||
type interface = {
|
||||
__index: interface,
|
||||
|
||||
new: (entity: entity) -> handle,
|
||||
|
||||
--- Checks if the entity has all of the given components
|
||||
has: (self: handle, ...id) -> boolean,
|
||||
--- Retrieves the value of up to 4 components. These values may be nil.
|
||||
get: (<A>(self: handle, id<A>) -> A?)
|
||||
& (<A, B>(self: handle, id<A>, id<B>) -> (A?, B?))
|
||||
& (<A, B, C>(self: handle, id<A>, id<B>, id<C>) -> (A?, B?, C?))
|
||||
& (<A, B, C, D>(self: handle, id<A>, id<B>, id<C>, id<D>) -> (A?, B?, C?, D?)),
|
||||
--- Adds a component to the entity with no value
|
||||
add: <T>(self: handle, id: id<T>) -> handle,
|
||||
--- Assigns a value to a component on the given entity
|
||||
set: <T>(self: handle, id: id<T>, data: T) -> handle,
|
||||
--- Removes a component from the given entity
|
||||
remove: (self: handle, id: id) -> handle,
|
||||
--- Deletes the entity and all its related components and relationships. **Does not** refer to deleting the handle
|
||||
delete: (self: handle) -> (),
|
||||
--- Gets the entitys id
|
||||
id: (self: handle) -> entity,
|
||||
}
|
||||
|
||||
export type handle = typeof(setmetatable({} :: { entity: entity, world: jecs.World }, {} :: interface))
|
||||
|
||||
local handle = {} :: interface
|
||||
handle.__index = handle
|
||||
|
||||
function handle.new(entity: entity)
|
||||
local self = {
|
||||
entity = entity,
|
||||
world = world(),
|
||||
}
|
||||
|
||||
return setmetatable(self, handle)
|
||||
end
|
||||
|
||||
function handle:has(...: id): boolean
|
||||
return self.world:has(self.entity, ...)
|
||||
end
|
||||
|
||||
handle.get = function(self: handle, a: id, b: id?, c: id?, d: id?)
|
||||
return self.world:get(self.entity, a, b :: any, c :: any, d :: any)
|
||||
end :: any
|
||||
|
||||
function handle:add<T>(id: id<T>): handle
|
||||
self.world:add(self.entity, id)
|
||||
return self
|
||||
end
|
||||
|
||||
function handle:set<T>(id: id<T>, value: T): handle
|
||||
self.world:set(self.entity, id, value)
|
||||
return self
|
||||
end
|
||||
|
||||
function handle:remove(id: id): handle
|
||||
self.world:remove(self.entity, id)
|
||||
return self
|
||||
end
|
||||
|
||||
function handle:delete()
|
||||
self.world:delete(self.entity)
|
||||
end
|
||||
|
||||
function handle:id(): entity
|
||||
return self.entity
|
||||
end
|
||||
|
||||
return handle.new
|
|
@ -1,42 +1,22 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../jecs")
|
||||
local collect = require("@self/utilities/collect")
|
||||
export type SignalLike<T...> = collect.SignalLike<any, T...>
|
||||
export type VerboseSignalLike<D, T...> = collect.SignalLike<D, T...>
|
||||
|
||||
local WORLD = require("./world")
|
||||
local ref = require("@self/utilities/ref")
|
||||
export type Ref = ref.Identity
|
||||
|
||||
local collect = require("./collect")
|
||||
export type collect_signal_like<T...> = collect.signal_like<any, T...>
|
||||
export type collect_verbose_signal_like<D, T...> = collect.signal_like<D, T...>
|
||||
local tracker = require("@self/utilities/tracker")
|
||||
export type Tracker = tracker.Identity
|
||||
export type TrackerCommands = tracker.Commands
|
||||
|
||||
local command_buffer = require("./command_buffer")
|
||||
export type command_buffer = command_buffer.command_buffer
|
||||
|
||||
local handle = require("./handle")
|
||||
export type handle = handle.handle
|
||||
|
||||
local ref = require("./ref")
|
||||
|
||||
local replicator = require("./replicator")
|
||||
export type replicator = replicator.replicator
|
||||
export type changes = replicator.changes
|
||||
|
||||
local spawner = require("./spawner")
|
||||
export type spawner<T...> = spawner.spawner<T...>
|
||||
|
||||
--- Set the world for all utilities.
|
||||
--- Should be called once per context before any utility is used.
|
||||
--- @param world jecs.World
|
||||
local function initialize(world: jecs.World)
|
||||
WORLD.set(world)
|
||||
end
|
||||
local command_buffer = require("@self/utilities/command_buffer")
|
||||
export type CommandBuffer = command_buffer.Identity
|
||||
|
||||
return {
|
||||
initialize = initialize,
|
||||
|
||||
collect = collect,
|
||||
handle = handle,
|
||||
replicator = replicator,
|
||||
ref = ref,
|
||||
tracker = tracker,
|
||||
command_buffer = command_buffer,
|
||||
spawner = spawner,
|
||||
}
|
||||
|
|
67
lib/ref.luau
67
lib/ref.luau
|
@ -1,67 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local handle = require("./handle")
|
||||
local jecs = require("../jecs")
|
||||
local WORLD = require("./world").get
|
||||
|
||||
local refs: { [jecs.World]: { [any]: jecs.Entity<any> } } = {}
|
||||
|
||||
local function serve_clearer(key: any, world: jecs.World): () -> ()
|
||||
return function()
|
||||
refs[world][key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Gets an entity the given key references to.
|
||||
--- If the key is nil, an entirely new entity is created and returned.
|
||||
--- If the key doesn't reference an entity, a new entity is made for it to reference and returned.
|
||||
--- @param key any
|
||||
--- @return handle
|
||||
local function ref(key: any): (handle.handle, () -> ()?)
|
||||
local world = WORLD()
|
||||
if not key then
|
||||
return handle(world:entity())
|
||||
end
|
||||
|
||||
if not refs[world] then
|
||||
refs[world] = {}
|
||||
end
|
||||
|
||||
local entity = refs[world][key]
|
||||
if not entity then
|
||||
entity = world:entity()
|
||||
refs[world][key] = entity
|
||||
end
|
||||
|
||||
return handle(entity), serve_clearer(key, world)
|
||||
end
|
||||
|
||||
-- For the `__call`` metamethod
|
||||
local function __call(_, key: any): (handle.handle, () -> ()?)
|
||||
return ref(key)
|
||||
end
|
||||
|
||||
local function search(key: any): (handle.handle?, () -> ()?)
|
||||
local world = WORLD()
|
||||
if not key then
|
||||
return nil
|
||||
end
|
||||
local entity = refs[world][key]
|
||||
|
||||
if not entity then
|
||||
return nil
|
||||
end
|
||||
|
||||
return handle(entity), serve_clearer(key, world)
|
||||
end
|
||||
|
||||
local metatable = {
|
||||
__call = __call,
|
||||
__index = {
|
||||
search = search,
|
||||
set_ref = ref,
|
||||
},
|
||||
}
|
||||
|
||||
local REF = setmetatable({}, metatable) :: typeof(ref) & typeof(metatable.__index)
|
||||
return REF
|
|
@ -1,247 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../jecs")
|
||||
type entity<T = nil> = jecs.Entity<T>
|
||||
type i53 = number
|
||||
|
||||
local ref = require("./ref")
|
||||
local WORLD = require("./world").get
|
||||
|
||||
--- A replicator keeps track of all entities with the passed components and their values -
|
||||
--- whenever a component is changed (add, change, remove) and the replicator listens to it, it's also changed within the contained raw data.\
|
||||
--- The developer can then calculate the difference on the server and send it to the client every time,
|
||||
--- on which the difference is then applied to the world.\
|
||||
--- Albeit it's called a replicator, it doesn't replicate the data by itself.
|
||||
--- This allows the developer to use any networking libary to replicate the changes.
|
||||
--- ```luau
|
||||
--- -- server
|
||||
--- local replicator = jecs_utils.create_replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- local difference = replicator.calculate_difference()
|
||||
--- -- There might not be any difference
|
||||
--- if not difference then
|
||||
--- return
|
||||
--- end
|
||||
--- data_replication_event.send_to_all(difference)
|
||||
--- end
|
||||
--- ```
|
||||
--- ```luau
|
||||
--- -- client
|
||||
--- local replicator = jecs_utils.replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- for _, difference in data_replication_event.poll() do
|
||||
--- replicator.apply_difference(difference)
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
export type replicator = {
|
||||
--- Gets the full data representing the entire world.
|
||||
--- Useful for initial replication to every player.
|
||||
--- ```luau
|
||||
--- local replicator = jecs_utils.replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- Players.PlayerAdded:Connect(function(player)
|
||||
--- data_replication_event.send_to(player, replicator.get_full_data())
|
||||
--- end)
|
||||
--- ```
|
||||
--- @return changes
|
||||
get_full_data: () -> changes,
|
||||
--- Calculates the difference between last sent data and currently stored data.
|
||||
--- ```luau
|
||||
--- local replicator = jecs_utils.create_replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- local difference = replicator.calculate_difference()
|
||||
--- -- There might not be any difference
|
||||
--- if not difference then
|
||||
--- return
|
||||
--- end
|
||||
--- data_replication_event.send_to_all(difference)
|
||||
--- end
|
||||
--- ```
|
||||
--- @return changes? -- There might not be any difference
|
||||
calculate_difference: () -> changes?,
|
||||
--- Applies the difference to the current data.
|
||||
--- ```luau
|
||||
--- local replicator = jecs_utils.replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- for _, difference in data_replication_event.poll() do
|
||||
--- replicator.apply_difference(difference)
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
--- @param difference changes
|
||||
apply_difference: (difference: changes) -> (),
|
||||
}
|
||||
|
||||
--- `map<component_id, array<entity_id>>`
|
||||
type changes_added = { [i53]: { i53 } }
|
||||
--- `map<component_id, array<entity_id, component_value>>`
|
||||
type changes_set = { [i53]: { [i53]: unknown } }
|
||||
--- `map<component_id, array<entity_id>>`
|
||||
type changes_removed = { [i53]: { i53 } }
|
||||
|
||||
export type changes = {
|
||||
added: changes_added,
|
||||
set: changes_set,
|
||||
removed: changes_removed,
|
||||
}
|
||||
|
||||
--- A replicator keeps track of all entities with the passed components and their values -
|
||||
--- whenever a component is changed (add, change, remove) and the replicator listens to it, it's also changed within the contained raw data.\
|
||||
--- The developer can then calculate the difference on the server and send it to the client every time,
|
||||
--- on which the difference is then applied to the world.\
|
||||
--- Albeit it's called a replicator, it doesn't replicate the data by itself.
|
||||
--- This allows the developer to use any networking libary to replicate the changes.
|
||||
--- ```luau
|
||||
--- -- server
|
||||
--- local replicator = jecs_utils.create_replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- local difference = replicator.calculate_difference()
|
||||
--- -- There might not be any difference
|
||||
--- if not difference then
|
||||
--- return
|
||||
--- end
|
||||
--- data_replication_event.send_to_all(difference)
|
||||
--- end
|
||||
--- ```
|
||||
--- ```luau
|
||||
--- -- client
|
||||
--- local replicator = jecs_utils.replicator(component_a, component_b, ...)
|
||||
---
|
||||
--- local function system()
|
||||
--- for _, difference in data_replication_event.poll() do
|
||||
--- replicator.apply_difference(difference)
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
--- @param ... entity
|
||||
--- @return replicator
|
||||
local function replicator(...: entity): replicator
|
||||
local world = WORLD()
|
||||
local components = { ... }
|
||||
|
||||
-- don't index a changes table start
|
||||
local raw_added: changes_added = {}
|
||||
local raw_set: changes_set = {}
|
||||
local raw_removed: changes_removed = {}
|
||||
|
||||
local changes_added: changes_added = {}
|
||||
local changes_set: changes_set = {}
|
||||
local changes_removed: changes_removed = {}
|
||||
-- don't index a changes table end
|
||||
|
||||
for _, component in components do
|
||||
world:set(component, jecs.OnAdd, function(entity)
|
||||
if not raw_added[component] then
|
||||
raw_added[component] = {}
|
||||
end
|
||||
if not changes_added[component] then
|
||||
changes_added[component] = {}
|
||||
end
|
||||
table.insert(raw_added[component], entity)
|
||||
table.insert(changes_added[component], entity)
|
||||
end)
|
||||
world:set(component, jecs.OnSet, function(entity, value)
|
||||
if not raw_set[component] then
|
||||
raw_set[component] = {}
|
||||
end
|
||||
if not changes_set[component] then
|
||||
changes_set[component] = {}
|
||||
end
|
||||
raw_set[component][entity] = value
|
||||
changes_set[component][entity] = value
|
||||
end)
|
||||
world:set(component, jecs.OnRemove, function(entity)
|
||||
if not raw_removed[component] then
|
||||
raw_removed[component] = {}
|
||||
end
|
||||
if not changes_removed[component] then
|
||||
changes_removed[component] = {}
|
||||
end
|
||||
table.insert(raw_removed[component], entity)
|
||||
table.insert(changes_removed[component], entity)
|
||||
end)
|
||||
end
|
||||
|
||||
local function get_full_data(): changes
|
||||
return {
|
||||
added = raw_added,
|
||||
set = raw_set,
|
||||
removed = raw_removed,
|
||||
}
|
||||
end
|
||||
|
||||
local function calculate_difference(): changes?
|
||||
local difference_added = changes_added
|
||||
local difference_set = changes_set
|
||||
local difference_removed = changes_removed
|
||||
changes_added = {}
|
||||
changes_set = {}
|
||||
changes_removed = {}
|
||||
|
||||
local added_not_empty = next(difference_added) ~= nil
|
||||
local set_not_empty = next(difference_set) ~= nil
|
||||
local removed_not_empty = next(difference_removed) ~= nil
|
||||
|
||||
if not added_not_empty and not set_not_empty and not removed_not_empty then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
added = difference_added,
|
||||
set = difference_set,
|
||||
removed = difference_removed,
|
||||
}
|
||||
end
|
||||
|
||||
local function apply_difference(difference: changes)
|
||||
for component, entities in difference.added do
|
||||
for _, entity_id in entities do
|
||||
local entity = ref(`replicated-{entity_id}`)
|
||||
|
||||
local exists = entity:has(component)
|
||||
if exists then
|
||||
continue
|
||||
end
|
||||
entity:add(component)
|
||||
end
|
||||
end
|
||||
|
||||
for component, entities in difference.set do
|
||||
for entity_id, value in entities do
|
||||
local entity = ref(`replicated-{entity_id}`)
|
||||
|
||||
local existing_value = entity:get(component)
|
||||
if existing_value == value then
|
||||
continue
|
||||
end
|
||||
entity:set(component, value)
|
||||
end
|
||||
end
|
||||
|
||||
for component, entities in difference.removed do
|
||||
for _, entity_id in entities do
|
||||
local entity = ref(`replicated-{entity_id}`)
|
||||
|
||||
local exists = entity:has(component)
|
||||
if exists then
|
||||
continue
|
||||
end
|
||||
entity:remove(component)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
get_full_data = get_full_data,
|
||||
calculate_difference = calculate_difference,
|
||||
apply_difference = apply_difference,
|
||||
}
|
||||
end
|
||||
|
||||
return replicator
|
|
@ -1,49 +0,0 @@
|
|||
--!strict
|
||||
local spawner_type = require("./spawner_type")
|
||||
local WORLD = require("./world").get
|
||||
local handle = require("./handle")
|
||||
|
||||
export type spawner<T...> = spawner_type.spawner<T...>
|
||||
|
||||
--- Creates an entity spawner.
|
||||
--- ```luau
|
||||
--- local spawner = jecs_utils.spawner(components.part, components.velocity, components.position)
|
||||
--- for _ = 1, 1000 do
|
||||
--- spawner.spawn(part_template:Clone(), Vector3.zero, Vector3.zero)
|
||||
--- end
|
||||
--- ```
|
||||
--- @param ... T... -- Components to use.
|
||||
--- @return spawner<T...>
|
||||
local function spawner(...)
|
||||
local components = { ... }
|
||||
local world = WORLD()
|
||||
|
||||
local function spawn(...)
|
||||
local passed = { ... }
|
||||
local entity = world:entity()
|
||||
|
||||
for idx, component in components do
|
||||
world:set(entity, component, passed[idx])
|
||||
end
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
local function spawn_with_handle(...)
|
||||
local passed = { ... }
|
||||
local entity = handle(world:entity())
|
||||
|
||||
for idx, component in components do
|
||||
entity:set(component, passed[idx])
|
||||
end
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
return {
|
||||
spawn = spawn,
|
||||
spawn_with_handle = spawn_with_handle,
|
||||
}
|
||||
end
|
||||
|
||||
return (spawner :: any) :: spawner_type.create_spawner
|
|
@ -1,391 +0,0 @@
|
|||
--!strict
|
||||
local jecs = require("../jecs")
|
||||
type entity<T = nil> = jecs.Entity<T>
|
||||
type id<T = nil> = jecs.Id<T>
|
||||
|
||||
local handle = require("./handle")
|
||||
|
||||
export type spawner<T...> = {
|
||||
--- Creates an entity with the given components.
|
||||
--- @param ... T...
|
||||
--- @return entity
|
||||
spawn: (T...) -> entity,
|
||||
--- Creates an entity with the given components and returns a handle to it.
|
||||
--- @param ... T...
|
||||
--- @return handle
|
||||
spawn_with_handle: (T...) -> handle.handle,
|
||||
}
|
||||
|
||||
-- Very beautiful type incoming!
|
||||
-- Sadly this has to be done, components are of different types than their values (`entity<T>` vs `T`)
|
||||
export type create_spawner =
|
||||
(<A>(id<A>) -> spawner<A>)
|
||||
& (<A, B>(id<A>, id<B>) -> spawner<A, B>)
|
||||
& (<A, B, C>(id<A>, id<B>, id<C>) -> spawner<A, B, C>)
|
||||
& (<A, B, C, D>(id<A>, id<B>, id<C>, id<D>) -> spawner<A, B, C, D>)
|
||||
& (<A, B, C, D, E>(id<A>, id<B>, id<C>, id<D>, id<E>) -> spawner<A, B, C, D, E>)
|
||||
& (<A, B, C, D, E, F>(id<A>, id<B>, id<C>, id<D>, id<E>, id<F>) -> spawner<A, B, C, D, E, F>)
|
||||
& (<A, B, C, D, E, F, G>(id<A>, id<B>, id<C>, id<D>, id<E>, id<F>, id<G>) -> spawner<A, B, C, D, E, F, G>)
|
||||
& (<A, B, C, D, E, F, G, H>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>
|
||||
) -> spawner<A, B, C, D, E, F, G, H>)
|
||||
& (<A, B, C, D, E, F, G, H, I>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>,
|
||||
id<V>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>,
|
||||
id<V>,
|
||||
id<W>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>,
|
||||
id<V>,
|
||||
id<W>,
|
||||
id<X>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>,
|
||||
id<V>,
|
||||
id<W>,
|
||||
id<X>,
|
||||
id<Y>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y>)
|
||||
& (<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z>(
|
||||
id<A>,
|
||||
id<B>,
|
||||
id<C>,
|
||||
id<D>,
|
||||
id<E>,
|
||||
id<F>,
|
||||
id<G>,
|
||||
id<H>,
|
||||
id<I>,
|
||||
id<J>,
|
||||
id<K>,
|
||||
id<L>,
|
||||
id<M>,
|
||||
id<N>,
|
||||
id<O>,
|
||||
id<P>,
|
||||
id<Q>,
|
||||
id<R>,
|
||||
id<S>,
|
||||
id<T>,
|
||||
id<U>,
|
||||
id<V>,
|
||||
id<W>,
|
||||
id<X>,
|
||||
id<Y>,
|
||||
id<Z>
|
||||
) -> spawner<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z>)
|
||||
|
||||
return {}
|
|
@ -1,6 +1,5 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
|
||||
--[[
|
||||
original author by @memorycode
|
||||
|
||||
|
@ -28,24 +27,12 @@ SOFTWARE.
|
|||
--]]
|
||||
|
||||
--- What signals passed to `collect()` should be able to be coerced into
|
||||
export type signal_like<D, T...> = { connect: confn<D, T...>, [any]: any } | { Connect: confn<D, T...>, [any]: any }
|
||||
type confn<D, T...> = (self: signal_like<D, T...>, (T...) -> ()) -> D
|
||||
export type SignalLike<D, T...> = { connect: Connector<D, T...>, [any]: any } | { Connect: Connector<D, T...>, [any]: any }
|
||||
type Connector<D, T...> = (self: SignalLike<D, T...>, (T...) -> ()) -> D
|
||||
|
||||
--- Collects all arguments fired through the given signal, and drains the collection on iteration.\
|
||||
--- Expects signals to have a `Connect` ***method***.
|
||||
--- ```luau
|
||||
--- local sig = collect(some_signal)
|
||||
---
|
||||
--- -- Imagine this as an ECS scheduler loop
|
||||
--- while task.wait() do
|
||||
--- for index, arg1 in sig do -- arg1, arg2, etc
|
||||
--- print(arg1)
|
||||
--- end
|
||||
--- end
|
||||
--- ```
|
||||
--- @param event signal<T...>
|
||||
--- @return () -> (number, T...), D -- iterator and disconnector
|
||||
local function collect<D, T...>(event: signal_like<D, T...>): (() -> (number, T...), D)
|
||||
--- Expects signals to have a `Connect` or `connect` ***method***.
|
||||
local function collect<D, T...>(event: SignalLike<D, T...>): (() -> (number, T...), D)
|
||||
local storage = {}
|
||||
local mt = {}
|
||||
local iter = function()
|
||||
|
@ -53,11 +40,11 @@ local function collect<D, T...>(event: signal_like<D, T...>): (() -> (number, T.
|
|||
return function(): (number?, T...)
|
||||
if n <= 0 then
|
||||
mt.__iter = nil
|
||||
return nil
|
||||
return nil :: any
|
||||
end
|
||||
|
||||
n -= 1
|
||||
return n + 1, unpack(table.remove(storage, 1) :: any)
|
||||
return n + 1, unpack(table.remove(storage, 1) :: any) :: any
|
||||
end
|
||||
end
|
||||
|
140
lib/utilities/command_buffer.luau
Normal file
140
lib/utilities/command_buffer.luau
Normal file
|
@ -0,0 +1,140 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../../jecs")
|
||||
type Entity<T = unknown> = jecs.Entity<T>
|
||||
type Id<T = unknown> = jecs.Id<T>
|
||||
type World = jecs.World
|
||||
|
||||
export type Identity = {
|
||||
--- Execute all commands and clear the buffer
|
||||
flush: () -> (),
|
||||
--- Peeks into the commands currently stored by the buffer
|
||||
peek: () -> Commands,
|
||||
|
||||
--- Adds a component to the entity with no value
|
||||
add: <T>(entity: Entity, component: Id<T>) -> (),
|
||||
--- Assigns a value to a component on the given entity
|
||||
set: <T>(entity: Entity, component: Id<T>, data: T) -> (),
|
||||
--- Removes a component from the given entity
|
||||
remove: <T>(entity: Entity, component: Id<T>) -> (),
|
||||
--- Deletes an entity and all it's related components and relationships
|
||||
delete: (entity: Entity) -> (),
|
||||
}
|
||||
|
||||
export type Commands = {
|
||||
add: { [Id]: { Entity } },
|
||||
set: { [Id]: { [Entity]: unknown } },
|
||||
remove: { [Id]: { Entity } },
|
||||
delete: { Entity },
|
||||
|
||||
deletion_lookup: { [Entity]: true },
|
||||
}
|
||||
|
||||
local function construct(world: World): Identity
|
||||
local add_commands: { [Id]: { Entity } } = {}
|
||||
local set_commands: { [Id]: { [Entity]: unknown } } = {}
|
||||
local remove_commands: { [Id]: { Entity } } = {}
|
||||
local delete_commands: { Entity } = {}
|
||||
-- Double memory usage for deletions but preserve order while keeping O(1) performance for lookups
|
||||
local deletion_lookup: { [Entity]: true } = {}
|
||||
|
||||
local function flush()
|
||||
for _, entity in delete_commands do
|
||||
world:delete(entity)
|
||||
end
|
||||
|
||||
for component, entities in add_commands do
|
||||
for _, entity in entities do
|
||||
if deletion_lookup[entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:add(entity, component)
|
||||
end
|
||||
end
|
||||
table.clear(add_commands)
|
||||
|
||||
for component, entities in set_commands do
|
||||
for entity, value in entities do
|
||||
if deletion_lookup[entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:set(entity, component, value)
|
||||
end
|
||||
end
|
||||
table.clear(set_commands)
|
||||
|
||||
for component, entities in remove_commands do
|
||||
for _, entity in entities do
|
||||
if deletion_lookup[entity] then
|
||||
continue
|
||||
end
|
||||
|
||||
world:remove(entity, component)
|
||||
end
|
||||
end
|
||||
table.clear(remove_commands)
|
||||
|
||||
table.clear(delete_commands)
|
||||
table.clear(deletion_lookup)
|
||||
end
|
||||
|
||||
local function peek()
|
||||
return {
|
||||
add = add_commands,
|
||||
set = set_commands,
|
||||
remove = remove_commands,
|
||||
delete = delete_commands,
|
||||
|
||||
deletion_lookup = deletion_lookup,
|
||||
}
|
||||
end
|
||||
|
||||
local function add<T>(entity: Entity, component: Id<T>)
|
||||
local cmds = add_commands[component]
|
||||
if not cmds then
|
||||
cmds = {}
|
||||
add_commands[component] = cmds
|
||||
end
|
||||
|
||||
table.insert(cmds, entity)
|
||||
end
|
||||
|
||||
local function set<T>(entity: Entity, component: Id<T>, data: T)
|
||||
local cmds = set_commands[component]
|
||||
if not cmds then
|
||||
cmds = {}
|
||||
set_commands[component] = cmds
|
||||
end
|
||||
|
||||
cmds[entity] = data
|
||||
end
|
||||
|
||||
local function remove<T>(entity: Entity, component: Id<T>)
|
||||
local cmds = remove_commands[component]
|
||||
if not cmds then
|
||||
cmds = {}
|
||||
remove_commands[component] = cmds
|
||||
end
|
||||
|
||||
table.insert(cmds, entity)
|
||||
end
|
||||
|
||||
local function delete(entity: Entity)
|
||||
table.insert(delete_commands, entity)
|
||||
deletion_lookup[entity] = true
|
||||
end
|
||||
|
||||
return {
|
||||
flush = flush,
|
||||
peek = peek,
|
||||
|
||||
add = add,
|
||||
set = set,
|
||||
remove = remove,
|
||||
delete = delete,
|
||||
}
|
||||
end
|
||||
|
||||
return construct
|
85
lib/utilities/ref.luau
Normal file
85
lib/utilities/ref.luau
Normal file
|
@ -0,0 +1,85 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../../jecs")
|
||||
type Entity<T = unknown> = jecs.Entity<T>
|
||||
|
||||
export type Identity = typeof(setmetatable(
|
||||
{},
|
||||
{} :: {
|
||||
__call: <T>(any, key: unknown) -> (Entity<T>, Cleaner),
|
||||
__index: {
|
||||
reference: <T>(key: unknown) -> (Entity<T>, Cleaner),
|
||||
find: <T>(key: unknown) -> (Entity<T>?, Cleaner?),
|
||||
},
|
||||
}
|
||||
))
|
||||
|
||||
type Cleaner = () -> ()
|
||||
|
||||
local ref_cache: { [jecs.World]: Identity } = {}
|
||||
|
||||
local function construct(world: jecs.World, skip_cache: boolean?): Identity
|
||||
if not skip_cache then
|
||||
local hit = ref_cache[world]
|
||||
if hit then
|
||||
return hit
|
||||
end
|
||||
end
|
||||
|
||||
local lookup: { [unknown]: Entity } = {}
|
||||
local cleaner_cache: { [unknown]: Cleaner } = {}
|
||||
|
||||
local function serve_cleaner(key: unknown): () -> ()
|
||||
local hit = cleaner_cache[key]
|
||||
if hit then
|
||||
return hit
|
||||
end
|
||||
|
||||
local function cleaner()
|
||||
lookup[key] = nil
|
||||
cleaner_cache[key] = nil
|
||||
end
|
||||
cleaner_cache[key] = cleaner
|
||||
|
||||
return cleaner
|
||||
end
|
||||
|
||||
local function ref<T>(key: unknown): (Entity<T>, Cleaner)
|
||||
local entity = lookup[key]
|
||||
if not entity then
|
||||
entity = world:entity()
|
||||
lookup[key] = entity
|
||||
end
|
||||
|
||||
return entity, serve_cleaner(key)
|
||||
end
|
||||
|
||||
local function find<T>(key: unknown): (Entity<T>?, Cleaner?)
|
||||
local entity = lookup[key]
|
||||
if not entity then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return entity, serve_cleaner(key)
|
||||
end
|
||||
|
||||
local function call<T>(_, key: unknown): (Entity<T>, Cleaner)
|
||||
return ref(key)
|
||||
end
|
||||
|
||||
local self = setmetatable({}, {
|
||||
__call = call,
|
||||
__index = {
|
||||
reference = ref,
|
||||
find = find,
|
||||
},
|
||||
})
|
||||
|
||||
if not skip_cache then
|
||||
ref_cache[world] = self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return construct
|
273
lib/utilities/tracker.luau
Normal file
273
lib/utilities/tracker.luau
Normal file
|
@ -0,0 +1,273 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local command_buffer = require("./command_buffer")
|
||||
local jecs = require("../../jecs")
|
||||
type Entity<T = unknown> = jecs.Entity<T>
|
||||
type Id<T = unknown> = jecs.Id<T>
|
||||
type i53 = number
|
||||
|
||||
local OnAdd = jecs.OnAdd
|
||||
local OnSet = jecs.OnSet
|
||||
local OnRemove = jecs.OnRemove
|
||||
|
||||
local construct_ref = require("./ref")
|
||||
|
||||
-- The external type differs for better DX
|
||||
export type Commands = {
|
||||
added: { [i53]: { i53 } },
|
||||
set: { [i53]: { [i53]: unknown } },
|
||||
removed: { [i53]: { i53 } },
|
||||
}
|
||||
|
||||
type Added = { [Id]: { Entity } }
|
||||
type Set = { [Id]: { [Entity]: unknown } }
|
||||
type Removed = { [Id]: { Entity } }
|
||||
type Lookup = { [Id]: { [Entity]: number } }
|
||||
type InternalCommands = {
|
||||
added: Added,
|
||||
set: Set,
|
||||
removed: Removed,
|
||||
}
|
||||
|
||||
--- Tracks operations on entities for the provided world.
|
||||
export type Identity = {
|
||||
--- Gets the current state.
|
||||
--- A state is a representation of the minimum of commands necessary to produce the current world from a clean slate.
|
||||
state: () -> Commands,
|
||||
--- Gets the currently tracked snapshot.
|
||||
--- A snapshot is a representation of the minimum of commands necessary to produce the current world back from when the last snapshot was taken.
|
||||
snapshot: () -> Commands?,
|
||||
--- Applies a set of commands to the tracked world, optionally doing it through a command buffer.
|
||||
apply: (snapshot: Commands, buf: command_buffer.Identity?) -> (),
|
||||
}
|
||||
|
||||
local function get_non_nilable<T, K>(container: {} & T, index: K): index<T, K>
|
||||
local data = container[index]
|
||||
if not data then
|
||||
data = {}
|
||||
container[index] = data
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local function insert_unique<T, V>(container: T, value: V, lookup: { [V]: number }?)
|
||||
if lookup then
|
||||
if lookup[value] then
|
||||
return
|
||||
end
|
||||
|
||||
local idx = #lookup + 1
|
||||
lookup[value] = idx;
|
||||
(container :: any)[idx] = value
|
||||
return
|
||||
end
|
||||
|
||||
if table.find(container, value) then
|
||||
return
|
||||
end
|
||||
table.insert(container, value)
|
||||
end
|
||||
|
||||
local function construct(world: jecs.World, ...: Entity<any>): Identity
|
||||
local components = { ... }
|
||||
local ref = construct_ref(world, true)
|
||||
|
||||
local state_added: Added = {}
|
||||
local state_added_lookup: Lookup = {}
|
||||
local state_set: Set = {}
|
||||
local state_removed: Removed = {}
|
||||
local state_removed_lookup: Lookup = {}
|
||||
|
||||
local snapshot_added: Added = {}
|
||||
local snapshot_set: Set = {}
|
||||
local snapshot_removed: Removed = {}
|
||||
|
||||
for _, component in components do
|
||||
world:set(component, OnAdd, function(entity: Entity)
|
||||
local snapshot = get_non_nilable(snapshot_added, component)
|
||||
insert_unique(snapshot, entity)
|
||||
|
||||
local state = get_non_nilable(state_added, component)
|
||||
local lookup = get_non_nilable(state_added_lookup, component)
|
||||
insert_unique(state, entity, lookup)
|
||||
|
||||
-- Clean up previous operations
|
||||
local set_state = state_set[component]
|
||||
if set_state and set_state[entity] then
|
||||
set_state[entity] = nil
|
||||
end
|
||||
|
||||
local removed_lookup = state_removed_lookup[component]
|
||||
if removed_lookup then
|
||||
local idx = removed_lookup[entity]
|
||||
if idx then
|
||||
removed_lookup[entity] = nil
|
||||
local removed_state = state_removed[component]
|
||||
if removed_state then
|
||||
-- Shifting around the array could be expensive, prefer `tbl[idx] = nil`
|
||||
removed_state[idx] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
world:set(component, OnSet, function(entity, value)
|
||||
local snapshot = get_non_nilable(snapshot_set, component)
|
||||
snapshot[entity] = value
|
||||
|
||||
local state = get_non_nilable(state_set, component)
|
||||
state[entity] = value
|
||||
|
||||
-- Clean up previous operations
|
||||
local added_lookup = state_added_lookup[component]
|
||||
if added_lookup then
|
||||
local idx = added_lookup[entity]
|
||||
if idx then
|
||||
added_lookup[entity] = nil
|
||||
local added_state = state_added[component]
|
||||
if added_state then
|
||||
-- Shifting around the array could get expensive, prefer `array[idx] = nil`
|
||||
added_state[idx] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local removed_lookup = state_removed_lookup[component]
|
||||
if removed_lookup then
|
||||
local idx = removed_lookup[entity]
|
||||
if idx then
|
||||
removed_lookup[entity] = nil
|
||||
local removed_state = state_removed[component]
|
||||
if removed_state then
|
||||
-- Shifting around the array could get expensive, prefer `array[idx] = nil`
|
||||
removed_state[idx] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
world:set(component, OnRemove, function(entity: Entity)
|
||||
local snapshot = get_non_nilable(snapshot_removed, component)
|
||||
insert_unique(snapshot, entity)
|
||||
|
||||
local state = get_non_nilable(state_removed, component)
|
||||
local lookup = get_non_nilable(state_removed_lookup, component)
|
||||
|
||||
-- Clean up previous operations
|
||||
local added_lookup = state_added_lookup[component]
|
||||
if added_lookup then
|
||||
local idx = added_lookup[entity]
|
||||
if idx then
|
||||
added_lookup[entity] = nil
|
||||
local added_state = state_added[component]
|
||||
if added_state then
|
||||
-- Shifting around the array could get expensive, prefer `array[idx] = nil`
|
||||
added_state[idx] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local set_state = state_set[component]
|
||||
if set_state and set_state[entity] then
|
||||
set_state[entity] = nil
|
||||
end
|
||||
|
||||
insert_unique(state, entity, lookup)
|
||||
end)
|
||||
end
|
||||
|
||||
-- We cast anything exposing `Commands` as `any` to improve the types for the end user
|
||||
local function get_state(): InternalCommands
|
||||
return {
|
||||
added = state_added,
|
||||
set = state_set,
|
||||
removed = state_removed,
|
||||
}
|
||||
end
|
||||
|
||||
local function get_snapshot(): InternalCommands?
|
||||
local diff_added = snapshot_added
|
||||
local diff_set = snapshot_set
|
||||
local diff_removed = snapshot_removed
|
||||
snapshot_added = {}
|
||||
snapshot_set = {}
|
||||
snapshot_removed = {}
|
||||
|
||||
if next(diff_added) == nil and next(diff_set) == nil and next(diff_removed) == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
added = diff_added,
|
||||
set = diff_set,
|
||||
removed = diff_removed,
|
||||
}
|
||||
end
|
||||
|
||||
local function apply_snapshot(snapshot: InternalCommands, buf: command_buffer.Identity?)
|
||||
local add
|
||||
local set
|
||||
local remove
|
||||
do
|
||||
if buf then
|
||||
add = buf.add
|
||||
set = buf.set
|
||||
remove = buf.remove
|
||||
else
|
||||
function add<T>(entity: Entity, component: Id<T>)
|
||||
world:add(entity, component)
|
||||
end
|
||||
|
||||
function set<T>(entity: Entity, component: Id<T>, data: T)
|
||||
world:set(entity, component, data)
|
||||
end
|
||||
|
||||
function remove<T>(entity: Entity, component: Id<T>)
|
||||
world:remove(entity, component)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for component, entities in snapshot.added do
|
||||
for _, id in entities do
|
||||
local entity = ref(`foreign-{id}`)
|
||||
|
||||
if world:has(entity, component) then
|
||||
continue
|
||||
end
|
||||
add(entity, component)
|
||||
end
|
||||
end
|
||||
|
||||
for component, entities in snapshot.set do
|
||||
for id, data in entities do
|
||||
local entity = ref(`foreign-{id}`)
|
||||
|
||||
if world:get(entity, component) == data then
|
||||
continue
|
||||
end
|
||||
set(entity, component, data)
|
||||
end
|
||||
end
|
||||
|
||||
for component, entities in snapshot.removed do
|
||||
for _, id in entities do
|
||||
local entity = ref(`foreign-{id}`)
|
||||
|
||||
if world:has(entity, component) then
|
||||
continue
|
||||
end
|
||||
remove(entity, component)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Public types differ for better DX
|
||||
return {
|
||||
state = get_state,
|
||||
snapshot = get_snapshot,
|
||||
apply = apply_snapshot,
|
||||
} :: any
|
||||
end
|
||||
|
||||
return construct
|
|
@ -1,29 +0,0 @@
|
|||
--!strict
|
||||
--!optimize 2
|
||||
local jecs = require("../jecs")
|
||||
|
||||
local WORLD: jecs.World
|
||||
|
||||
local listeners: { (jecs.World) -> () } = {}
|
||||
|
||||
local function get(): jecs.World
|
||||
return WORLD
|
||||
end
|
||||
|
||||
local function set(world: jecs.World)
|
||||
WORLD = world
|
||||
|
||||
for _, fn in listeners do
|
||||
fn(world)
|
||||
end
|
||||
end
|
||||
|
||||
local function on_set(fn: (jecs.World) -> ())
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
return {
|
||||
get = get,
|
||||
set = set,
|
||||
on_set = on_set,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue