mirror of
https://github.com/Ukendio/jecs.git
synced 2025-06-19 15:59:18 +00:00
Networking example
Some checks failed
Some checks failed
This commit is contained in:
parent
3ae84301f2
commit
6a8d991185
35 changed files with 769 additions and 947 deletions
|
@ -1,76 +1,55 @@
|
|||
{
|
||||
"name": "demo",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"$path": "src/ReplicatedStorage",
|
||||
"ecs": {
|
||||
"$path": "../jecs.luau"
|
||||
},
|
||||
"net": {
|
||||
"$path": "net/client.luau"
|
||||
},
|
||||
"Packages": {
|
||||
"$path": "Packages"
|
||||
}
|
||||
},
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
"$path": "src/ServerScriptService",
|
||||
"net": {
|
||||
"$path": "net/server.luau"
|
||||
}
|
||||
},
|
||||
"Workspace": {
|
||||
"$properties": {
|
||||
"FilteringEnabled": true
|
||||
},
|
||||
"Baseplate": {
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Anchored": true,
|
||||
"Color": [
|
||||
0.38823,
|
||||
0.37254,
|
||||
0.38823
|
||||
],
|
||||
"Locked": true,
|
||||
"Position": [
|
||||
0,
|
||||
-10,
|
||||
0
|
||||
],
|
||||
"Size": [
|
||||
512,
|
||||
20,
|
||||
512
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"$properties": {
|
||||
"Ambient": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"Brightness": 2,
|
||||
"GlobalShadows": true,
|
||||
"Outlines": false,
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
},
|
||||
"SoundService": {
|
||||
"$properties": {
|
||||
"RespectFilteringEnabled": true
|
||||
}
|
||||
},
|
||||
"StarterPlayer": {
|
||||
"StarterPlayerScripts": {
|
||||
"$path": "src/StarterPlayer/StarterPlayerScripts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "demo",
|
||||
"emitLegacyScripts": false,
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"$path": "src/ReplicatedStorage",
|
||||
"ecs": {
|
||||
"$path": "../jecs.luau"
|
||||
},
|
||||
"Packages": {
|
||||
"$path": "Packages"
|
||||
}
|
||||
},
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
"$path": "src/ServerScriptService"
|
||||
},
|
||||
"Workspace": {
|
||||
"$properties": {
|
||||
"FilteringEnabled": true
|
||||
},
|
||||
"Baseplate": {
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Anchored": true,
|
||||
"Color": [0.38823, 0.37254, 0.38823],
|
||||
"Locked": true,
|
||||
"Position": [0, -10, 0],
|
||||
"Size": [512, 20, 512]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Lighting": {
|
||||
"$properties": {
|
||||
"Ambient": [0, 0, 0],
|
||||
"Brightness": 2,
|
||||
"GlobalShadows": true,
|
||||
"Outlines": false,
|
||||
"Technology": "Voxel"
|
||||
}
|
||||
},
|
||||
"SoundService": {
|
||||
"$properties": {
|
||||
"RespectFilteringEnabled": true
|
||||
}
|
||||
},
|
||||
"StarterPlayer": {
|
||||
"StarterPlayerScripts": {
|
||||
"$path": "src/StarterPlayer/StarterPlayerScripts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
demo/src/ReplicatedStorage/collect.luau
Normal file
28
demo/src/ReplicatedStorage/collect.luau
Normal file
|
@ -0,0 +1,28 @@
|
|||
local function collect<T...>(
|
||||
signal: {
|
||||
Connect: (RBXScriptSignal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
|
||||
}
|
||||
): () -> (T...)
|
||||
local enqueued = {}
|
||||
|
||||
local i = 0
|
||||
|
||||
local connection = (signal :: any):Connect(function(...)
|
||||
table.insert(enqueued, { ... })
|
||||
i += 1
|
||||
end)
|
||||
|
||||
return function(): any
|
||||
if i == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
i -= 1
|
||||
|
||||
local args: any = table.remove(enqueued, 1)
|
||||
|
||||
return unpack(args)
|
||||
end, connection
|
||||
end
|
||||
|
||||
return collect
|
36
demo/src/ReplicatedStorage/components.luau
Normal file
36
demo/src/ReplicatedStorage/components.luau
Normal file
|
@ -0,0 +1,36 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local types = require("./types")
|
||||
|
||||
local Networked = jecs.tag()
|
||||
local NetworkedPair = jecs.tag()
|
||||
|
||||
local Renderable = jecs.component() :: jecs.Id<Instance>
|
||||
jecs.meta(Renderable, Networked)
|
||||
|
||||
|
||||
local Poison = jecs.component() :: jecs.Id<number>
|
||||
jecs.meta(Poison, Networked)
|
||||
|
||||
local Health = jecs.component() :: jecs.Id<number>
|
||||
jecs.meta(Health, Networked)
|
||||
|
||||
local Player = jecs.component() :: jecs.Id<Player>
|
||||
jecs.meta(Player, Networked)
|
||||
|
||||
|
||||
local components = {
|
||||
Renderable = Renderable,
|
||||
Player = Player,
|
||||
Poison = Poison,
|
||||
Health = Health,
|
||||
|
||||
Networked = Networked,
|
||||
NetworkedPair = NetworkedPair,
|
||||
}
|
||||
|
||||
for name, component in components do
|
||||
jecs.meta(component, jecs.Name, name)
|
||||
end
|
||||
|
||||
return components
|
|
@ -1,4 +0,0 @@
|
|||
_G.JECS_DEBUG = true
|
||||
_G.JECS_HI_COMPONENT_ID = 32
|
||||
require(game:GetService("ReplicatedStorage").ecs)
|
||||
return
|
13
demo/src/ReplicatedStorage/main.client.luau
Normal file
13
demo/src/ReplicatedStorage/main.client.luau
Normal file
|
@ -0,0 +1,13 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local schedule = require(ReplicatedStorage.schedule)
|
||||
local observers_add = require(ReplicatedStorage.observers_add)
|
||||
|
||||
local SYSTEM = schedule.SYSTEM
|
||||
local RUN = schedule.RUN
|
||||
require(ReplicatedStorage.components)
|
||||
local world = observers_add(jecs.world())
|
||||
|
||||
local systems = ReplicatedStorage.systems
|
||||
SYSTEM(world, systems.receive_replication)
|
||||
RUN(world)
|
190
demo/src/ReplicatedStorage/observers_add.luau
Normal file
190
demo/src/ReplicatedStorage/observers_add.luau
Normal file
|
@ -0,0 +1,190 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
|
||||
type Observer<T...> = {
|
||||
callback: (jecs.Entity) -> (),
|
||||
query: jecs.Query<T...>,
|
||||
}
|
||||
|
||||
export type PatchedWorld = jecs.World & {
|
||||
added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
|
||||
removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (),
|
||||
changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id<T>, value: T) -> ()) -> (),
|
||||
-- deleted: (PatchedWorld, () -> ()) -> () -> (),
|
||||
observer: (PatchedWorld, Observer<any>) -> (),
|
||||
monitor: (PatchedWorld, Observer<any>) -> (),
|
||||
}
|
||||
|
||||
local function observers_new(world, description)
|
||||
local query = description.query
|
||||
local callback = description.callback
|
||||
local terms = query.filter_with :: { jecs.Id }
|
||||
if not terms then
|
||||
local ids = query.ids
|
||||
query.filter_with = ids
|
||||
terms = ids
|
||||
end
|
||||
|
||||
local entity_index = world.entity_index :: any
|
||||
local function emplaced(entity: jecs.Entity)
|
||||
local r = jecs.entity_index_try_get_fast(
|
||||
entity_index, entity :: any)
|
||||
|
||||
if not r then
|
||||
return
|
||||
end
|
||||
|
||||
local archetype = r.archetype
|
||||
|
||||
if jecs.query_match(query, archetype) then
|
||||
callback(entity)
|
||||
end
|
||||
end
|
||||
|
||||
for _, term in terms do
|
||||
world:added(term, emplaced)
|
||||
world:changed(term, emplaced)
|
||||
end
|
||||
end
|
||||
|
||||
local function monitors_new(world, description)
|
||||
local query = description.query
|
||||
local callback = description.callback
|
||||
local terms = query.filter_with :: { jecs.Id }
|
||||
if not terms then
|
||||
local ids = query.ids
|
||||
query.filter_with = ids
|
||||
terms = ids
|
||||
end
|
||||
|
||||
local entity_index = world.entity_index :: any
|
||||
local function emplaced(entity: jecs.Entity)
|
||||
local r = jecs.entity_index_try_get_fast(
|
||||
entity_index, entity :: any)
|
||||
|
||||
if not r then
|
||||
return
|
||||
end
|
||||
|
||||
local archetype = r.archetype
|
||||
|
||||
if jecs.query_match(query, archetype) then
|
||||
callback(entity, jecs.OnAdd)
|
||||
end
|
||||
end
|
||||
|
||||
local function removed(entity: jecs.Entity, component: jecs.Id)
|
||||
local r = jecs.entity_index_try_get_fast(
|
||||
entity_index, entity :: any)
|
||||
|
||||
if not r then
|
||||
return
|
||||
end
|
||||
|
||||
local archetype = r.archetype
|
||||
|
||||
if jecs.query_match(query, archetype) then
|
||||
callback(entity, jecs.OnRemove)
|
||||
end
|
||||
end
|
||||
|
||||
for _, term in terms do
|
||||
world:added(term, emplaced)
|
||||
world:removed(term, removed)
|
||||
end
|
||||
end
|
||||
|
||||
local function observers_add(world: jecs.World): PatchedWorld
|
||||
local signals = {
|
||||
added = {},
|
||||
emplaced = {},
|
||||
removed = {},
|
||||
deleted = {}
|
||||
}
|
||||
|
||||
world = world :: jecs.World & {[string]: any}
|
||||
|
||||
world.added = function(_, component, fn)
|
||||
local listeners = signals.added[component]
|
||||
if not listeners then
|
||||
listeners = {}
|
||||
signals.added[component] = listeners
|
||||
|
||||
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||
local rw = jecs.pair(component, jecs.Wildcard)
|
||||
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||
local function on_add(entity: number, id: number, value: any)
|
||||
for _, listener in listeners do
|
||||
listener(entity, id, value)
|
||||
end
|
||||
end
|
||||
world:set(component, jecs.OnAdd, on_add)
|
||||
idr.hooks.on_add = on_add :: any
|
||||
idr_r.hooks.on_add = on_add :: any
|
||||
end
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
world.changed = function(_, component, fn)
|
||||
local listeners = signals.emplaced[component]
|
||||
if not listeners then
|
||||
listeners = {}
|
||||
signals.emplaced[component] = listeners
|
||||
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||
local rw = jecs.pair(component, jecs.Wildcard)
|
||||
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||
local function on_change(entity: number, id: number, value: any)
|
||||
for _, listener in listeners do
|
||||
listener(entity, id, value)
|
||||
end
|
||||
end
|
||||
world:set(component, jecs.OnChange, on_change)
|
||||
idr.hooks.on_change = on_change :: any
|
||||
idr_r.hooks.on_change = on_change :: any
|
||||
end
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
world.removed = function(_, component, fn)
|
||||
local listeners = signals.removed[component]
|
||||
if not listeners then
|
||||
listeners = {}
|
||||
signals.removed[component] = listeners
|
||||
local idr = jecs.id_record_ensure(world :: any, component :: any)
|
||||
local rw = jecs.pair(component, jecs.Wildcard)
|
||||
local idr_r = jecs.id_record_ensure(world :: any, rw :: any)
|
||||
local function on_remove(entity: number, id: number, value: any)
|
||||
for _, listener in listeners do
|
||||
listener(entity, id, value)
|
||||
end
|
||||
end
|
||||
world:set(component, jecs.OnRemove, on_remove)
|
||||
idr.hooks.on_remove = on_remove :: any
|
||||
idr_r.hooks.on_remove = on_remove :: any
|
||||
end
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
world.signals = signals
|
||||
|
||||
world.observer = observers_new
|
||||
|
||||
world.monitor = monitors_new
|
||||
|
||||
-- local world_delete = world.delete
|
||||
|
||||
-- world.deleted = function(_, fn)
|
||||
-- local listeners = signals.deleted
|
||||
-- table.insert(listeners, fn)
|
||||
-- end
|
||||
-- world.delete = function(world, entity)
|
||||
-- world_delete(world, entity)
|
||||
-- for _, fn in signals.deleted do
|
||||
-- fn(entity)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
return world :: PatchedWorld
|
||||
end
|
||||
|
||||
return observers_add
|
50
demo/src/ReplicatedStorage/remotes.luau
Normal file
50
demo/src/ReplicatedStorage/remotes.luau
Normal file
|
@ -0,0 +1,50 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local types = require("../ReplicatedStorage/types")
|
||||
|
||||
type Signal<T...> = {
|
||||
Connect: (Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection
|
||||
}
|
||||
type Remote<T...> = {
|
||||
FireClient: (Remote<T...>, T...) -> (),
|
||||
FireAllClients: (Remote<T...>, T...) -> (),
|
||||
FireServer: (Remote<T...>) -> (),
|
||||
OnServerEvent: {
|
||||
Connect: (any, fn: (Player, T...) -> () ) -> ()
|
||||
},
|
||||
OnClientEvent: {
|
||||
Connect: (any, fn: (T...) -> () ) -> ()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
local function stream_ensure(name): Remote<any>
|
||||
local remote = ReplicatedStorage:FindFirstChild(name)
|
||||
if not remote then
|
||||
remote = Instance.new("RemoteEvent")
|
||||
remote.Name = name
|
||||
remote.Parent = ReplicatedStorage
|
||||
end
|
||||
return remote :: any
|
||||
end
|
||||
|
||||
local function datagram_ensure(name): Remote<any>
|
||||
local remote = ReplicatedStorage:FindFirstChild(name)
|
||||
if not remote then
|
||||
remote = Instance.new("UnreliableRemoteEvent")
|
||||
remote.Name = name
|
||||
remote.Parent = ReplicatedStorage
|
||||
end
|
||||
return remote :: any
|
||||
end
|
||||
|
||||
return {
|
||||
input = datagram_ensure("input") :: Remote<string>,
|
||||
replication = stream_ensure("replication") :: Remote<{
|
||||
[string]: {
|
||||
set: { types.Entity }?,
|
||||
values: { any }?,
|
||||
removed: { types.Entity }?
|
||||
}
|
||||
}>,
|
||||
|
||||
}
|
136
demo/src/ReplicatedStorage/schedule.luau
Normal file
136
demo/src/ReplicatedStorage/schedule.luau
Normal file
|
@ -0,0 +1,136 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
|
||||
jabby.set_check_function(function() return true end)
|
||||
|
||||
local scheduler = jabby.scheduler.create("jabby scheduler")
|
||||
|
||||
jabby.register({
|
||||
applet = jabby.applets.scheduler,
|
||||
name = "Scheduler",
|
||||
configuration = {
|
||||
scheduler = scheduler,
|
||||
},
|
||||
})
|
||||
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
|
||||
local function create_widget(_, state: Enum.UserInputState)
|
||||
local client = jabby.obtain_client()
|
||||
if state ~= Enum.UserInputState.Begin then return end
|
||||
client.spawn_app(client.apps.home, nil)
|
||||
end
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local System = jecs.component() :: jecs.Id<{
|
||||
fn: () -> (),
|
||||
name: string,
|
||||
}>
|
||||
local DependsOn = jecs.component()
|
||||
local Phase = jecs.tag()
|
||||
local Event = jecs.component() :: jecs.Id<RBXScriptSignal>
|
||||
|
||||
local pair = jecs.pair
|
||||
|
||||
local types = require(ReplicatedStorage.types)
|
||||
|
||||
local function ECS_PHASE(world, after: types.Entity)
|
||||
local phase = world:entity()
|
||||
world:add(phase, Phase)
|
||||
if after then
|
||||
local dependency = pair(DependsOn, after)
|
||||
world:add(phase, dependency)
|
||||
end
|
||||
|
||||
return phase
|
||||
end
|
||||
|
||||
local Heartbeat = jecs.tag()
|
||||
jecs.meta(Heartbeat, Phase)
|
||||
jecs.meta(Heartbeat, Event, RunService.Heartbeat)
|
||||
|
||||
local PreSimulation = jecs.tag()
|
||||
jecs.meta(PreSimulation, Phase)
|
||||
jecs.meta(PreSimulation, Event, RunService.PreSimulation)
|
||||
|
||||
local PreAnimation = jecs.tag()
|
||||
jecs.meta(PreAnimation, Phase)
|
||||
jecs.meta(PreAnimation, Event, RunService.PreAnimation)
|
||||
|
||||
local PreRender = jecs.tag()
|
||||
jecs.meta(PreRender, Phase)
|
||||
jecs.meta(PreRender, Event, RunService.PreRender)
|
||||
|
||||
local function ECS_SYSTEM(world: types.World, mod: ModuleScript, phase: types.Entity?)
|
||||
local system = world:entity()
|
||||
local p = phase or Heartbeat
|
||||
local fn = require(mod) :: (...any) -> ()
|
||||
world:set(system, System, {
|
||||
fn = fn(world, 0) or fn,
|
||||
name = mod.Name,
|
||||
})
|
||||
|
||||
local depends_on = DependsOn :: jecs.Entity
|
||||
world:add(system, pair(depends_on, p))
|
||||
end
|
||||
local function find_systems_w_phase(world: types.World, systems, phase: types.Entity)
|
||||
local phase_name = world:get(phase, jecs.Name) :: string
|
||||
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
||||
table.insert(systems, {
|
||||
id = scheduler:register_system({
|
||||
phase = phase_name,
|
||||
name = s.name,
|
||||
}),
|
||||
fn = s.fn
|
||||
})
|
||||
end
|
||||
for after in world:query(Phase, pair(DependsOn, phase)) do
|
||||
find_systems_w_phase(world, systems, after)
|
||||
end
|
||||
return systems
|
||||
end
|
||||
|
||||
local function ECS_RUN(world: types.World)
|
||||
|
||||
jabby.register({
|
||||
applet = jabby.applets.world,
|
||||
name = "MyWorld",
|
||||
configuration = {
|
||||
world = world,
|
||||
},
|
||||
})
|
||||
|
||||
if RunService:IsClient() then
|
||||
ContextActionService:BindAction("Open Jabby Home", create_widget, false, Enum.KeyCode.F4)
|
||||
end
|
||||
|
||||
for phase, event in world:query(Event, Phase) do
|
||||
local systems = find_systems_w_phase(world, {}, phase)
|
||||
event:Connect(function(...)
|
||||
for _, system in systems do
|
||||
scheduler:run(system.id, system.fn, world, ...)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return {
|
||||
PHASE = ECS_PHASE,
|
||||
SYSTEM = ECS_SYSTEM,
|
||||
RUN = ECS_RUN,
|
||||
phases = {
|
||||
Heartbeat = Heartbeat,
|
||||
PreSimulation = PreSimulation,
|
||||
PreAnimation = PreAnimation,
|
||||
PreRender = PreRender
|
||||
},
|
||||
components = {
|
||||
System = System,
|
||||
DependsOn = DependsOn,
|
||||
Phase = Phase,
|
||||
Event = Event,
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||
local std = ReplicatedStorage.std
|
||||
local scheduler = require(std.scheduler)
|
||||
local world = require(std.world)
|
||||
|
||||
local function start(modules)
|
||||
for _, module in modules do
|
||||
require(module)
|
||||
end
|
||||
local events = scheduler.COLLECT()
|
||||
scheduler.BEGIN(events)
|
||||
jabby.set_check_function(function(player)
|
||||
return true
|
||||
end)
|
||||
if RunService:IsClient() then
|
||||
local player = game:GetService("Players").LocalPlayer
|
||||
local playergui = player:WaitForChild("PlayerGui")
|
||||
local client = jabby.obtain_client()
|
||||
UserInputService.InputBegan:Connect(function(input)
|
||||
if input.KeyCode == Enum.KeyCode.F4 then
|
||||
local home = playergui:FindFirstChild("Home")
|
||||
if home then
|
||||
home:Destroy()
|
||||
end
|
||||
client.spawn_app(client.apps.home)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return start
|
|
@ -1,40 +0,0 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
|
||||
-- original author @centau
|
||||
|
||||
local FAILURE = -1
|
||||
local RUNNING = 0
|
||||
local SUCCESS = 1
|
||||
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status <= RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status > FAILURE then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
|
||||
local bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING,
|
||||
}
|
||||
|
||||
return bt
|
|
@ -1,67 +0,0 @@
|
|||
--!nonstrict
|
||||
|
||||
--[[
|
||||
local signal = Signal.new() :: Signal.Signal<string, string>
|
||||
local events = collect(signal)
|
||||
local function system(world)
|
||||
for id, str1, str2 in events do
|
||||
--
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
--[[
|
||||
original author by @memorycode
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Michael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--]]
|
||||
|
||||
type Signal<T...> = { [any]: any }
|
||||
local function collect<T...>(event: Signal<T...>)
|
||||
local storage = {}
|
||||
local mt = {}
|
||||
local iter = function()
|
||||
local n = #storage
|
||||
return function()
|
||||
if n <= 0 then
|
||||
mt.__iter = nil
|
||||
return nil
|
||||
end
|
||||
|
||||
n -= 1
|
||||
return n + 1, unpack(table.remove(storage, 1) :: any)
|
||||
end
|
||||
end
|
||||
|
||||
local disconnect = event:Connect(function(...)
|
||||
table.insert(storage, { ... })
|
||||
mt.__iter = iter
|
||||
end)
|
||||
|
||||
setmetatable(storage, mt)
|
||||
return (storage :: any) :: () -> (number, T...), function()
|
||||
disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
return collect
|
|
@ -1,30 +0,0 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local world = require(script.Parent.world)
|
||||
|
||||
type Entity<T = nil> = jecs.Entity<T>
|
||||
local components: {
|
||||
Character: Entity<Model>,
|
||||
Mob: Entity,
|
||||
Model: Entity<Model>,
|
||||
Player: Entity,
|
||||
Target: Entity,
|
||||
Transform: Entity<{ new: CFrame, old: CFrame }>,
|
||||
Velocity: Entity<number>,
|
||||
Previous: Entity,
|
||||
} =
|
||||
{
|
||||
Character = world:component(),
|
||||
Mob = world:component(),
|
||||
Model = world:component(),
|
||||
Player = world:component(),
|
||||
Target = world:component(),
|
||||
Transform = world:component(),
|
||||
Velocity = world:component(),
|
||||
Previous = world:component(),
|
||||
}
|
||||
|
||||
for name, component in components :: {[string]: jecs.Entity} do
|
||||
world:set(component, jecs.Name, name)
|
||||
end
|
||||
|
||||
return table.freeze(components)
|
|
@ -1,19 +0,0 @@
|
|||
local function interval(s)
|
||||
local pin
|
||||
|
||||
local function throttle()
|
||||
if not pin then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - pin > s
|
||||
if elapsed then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
return elapsed
|
||||
end
|
||||
return throttle
|
||||
end
|
||||
|
||||
return interval
|
|
@ -1,14 +0,0 @@
|
|||
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,26 +0,0 @@
|
|||
local world = require(script.Parent.world)
|
||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local refs: {[any]: jecs.Entity} = {}
|
||||
|
||||
local function fini(key): () -> ()
|
||||
return function()
|
||||
refs[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function noop() end
|
||||
|
||||
local function ref(key): (jecs.Entity, () -> ())
|
||||
if not key then
|
||||
return world:entity(), noop
|
||||
end
|
||||
local e = refs[key]
|
||||
if not e then
|
||||
e = world:entity()
|
||||
refs[key] = e
|
||||
end
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return e, fini(key)
|
||||
end
|
||||
|
||||
return ref
|
|
@ -1,171 +0,0 @@
|
|||
--!native
|
||||
--!optimize 2
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local jabby = require(ReplicatedStorage.Packages.jabby)
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local pair = jecs.pair
|
||||
local Name = jecs.Name
|
||||
|
||||
type World = jecs.World
|
||||
type Entity<T = nil> = jecs.Entity<T>
|
||||
type Id<T = unknown> = jecs.Id<T>
|
||||
|
||||
type System = {
|
||||
callback: (world: World) -> (),
|
||||
id: number,
|
||||
}
|
||||
|
||||
type Systems = { System }
|
||||
|
||||
type Events = {
|
||||
RenderStepped: Systems,
|
||||
Heartbeat: Systems,
|
||||
}
|
||||
|
||||
local world = require(script.Parent.world)
|
||||
local Disabled = world:entity()
|
||||
local System = world:component() :: Id<{ callback: (any) -> (), name: string}>
|
||||
local DependsOn = world:entity()
|
||||
local Event = world:component() :: Id<RBXScriptSignal>
|
||||
local Phase = world:entity()
|
||||
|
||||
local PreRender = world:entity()
|
||||
local Heartbeat = world:entity()
|
||||
local PreAnimation = world:entity()
|
||||
local PreSimulation = world:entity()
|
||||
|
||||
local sys: System
|
||||
local dt: number
|
||||
|
||||
local jabby_scheduler = jabby.scheduler.create("Scheduler")
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
jabby.register({
|
||||
applet = jabby.applets.world,
|
||||
name = "MyWorld",
|
||||
configuration = {
|
||||
world = world,
|
||||
},
|
||||
})
|
||||
|
||||
jabby.register({
|
||||
applet = jabby.applets.scheduler,
|
||||
name = "Scheduler",
|
||||
configuration = {
|
||||
scheduler = jabby_scheduler,
|
||||
},
|
||||
})
|
||||
|
||||
if RunService:IsClient() then
|
||||
world:add(PreRender, Phase)
|
||||
world:set(PreRender, Event, (RunService :: RunService).PreRender)
|
||||
end
|
||||
|
||||
local function begin(events: { [RBXScriptSignal]: Systems })
|
||||
local connections = {}
|
||||
for event, systems in events do
|
||||
if not event then
|
||||
continue
|
||||
end
|
||||
local event_name = tostring(event)
|
||||
connections[event] = event:Connect(function(...)
|
||||
debug.profilebegin(event_name)
|
||||
for _, s in systems do
|
||||
sys = s
|
||||
a, b, c, d = ...
|
||||
|
||||
for _ in run do
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
debug.profileend()
|
||||
end)
|
||||
end
|
||||
return connections
|
||||
end
|
||||
|
||||
local function scheduler_collect_systems_under_phase_recursive(systems, phase: Entity)
|
||||
local phase_name = world:get(phase, Name)
|
||||
for _, s in world:query(System):with(pair(DependsOn, phase)) do
|
||||
table.insert(systems, {
|
||||
id = jabby_scheduler:register_system({
|
||||
name = s.name,
|
||||
phase = phase_name,
|
||||
} :: any),
|
||||
callback = s.callback,
|
||||
})
|
||||
end
|
||||
for after in world:query(Phase):with(pair(DependsOn, phase)):iter() 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(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 {
|
||||
SYSTEM = scheduler_systems_new,
|
||||
BEGIN = begin,
|
||||
PHASE = scheduler_phase_new,
|
||||
COLLECT = scheduler_collect_systems_all,
|
||||
phases = {
|
||||
Heartbeat = Heartbeat,
|
||||
PreSimulation = PreSimulation,
|
||||
PreAnimation = PreAnimation,
|
||||
PreRender = PreRender
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
|
||||
-- I like the idea of only having the world be a singleton.
|
||||
return jecs.World.new() :: jecs.World
|
67
demo/src/ReplicatedStorage/systems/receive_replication.luau
Normal file
67
demo/src/ReplicatedStorage/systems/receive_replication.luau
Normal file
|
@ -0,0 +1,67 @@
|
|||
local types = require("../types")
|
||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local remotes = require("../remotes")
|
||||
local collect = require("../collect")
|
||||
local client_ids = {}
|
||||
|
||||
local function ecs_map_get(world: types.World, id: types.Entity)
|
||||
local deserialised_id = 0
|
||||
|
||||
if not world:exists(id) or not world:contains(id) then
|
||||
deserialised_id = world:entity(id)
|
||||
client_ids[id] = deserialised_id
|
||||
else
|
||||
deserialised_id = client_ids[id]
|
||||
end
|
||||
|
||||
return deserialised_id
|
||||
end
|
||||
|
||||
local function ecs_make_alive_id(world: types.World, id: jecs.Id)
|
||||
local rel = jecs.ECS_PAIR_FIRST(id)
|
||||
local tgt = jecs.ECS_PAIR_SECOND(id)
|
||||
|
||||
ecs_map_get(world, rel)
|
||||
ecs_map_get(world, tgt)
|
||||
end
|
||||
|
||||
local snapshots = collect(remotes.replication.OnClientEvent)
|
||||
|
||||
return function(world: types.World)
|
||||
return function()
|
||||
for snapshot in snapshots do
|
||||
for key, map in snapshot do
|
||||
local id = (tonumber(key) :: any) :: jecs.Id
|
||||
if jecs.IS_PAIR(id) then
|
||||
ecs_make_alive_id(world, id)
|
||||
end
|
||||
|
||||
local set = map.set
|
||||
if set then
|
||||
if jecs.is_tag(world, id) then
|
||||
for _, entity in set do
|
||||
entity = ecs_map_get(world, entity)
|
||||
world:add(entity, id)
|
||||
end
|
||||
else
|
||||
local values = map.values :: { any }
|
||||
for i, entity in set do
|
||||
entity = ecs_map_get(world, entity)
|
||||
world:set(entity, id, values[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local removed = map.removed
|
||||
if removed then
|
||||
for i, e in removed do
|
||||
if not world:contains(e) then
|
||||
continue
|
||||
end
|
||||
world:remove(e, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,48 +0,0 @@
|
|||
local events = {}
|
||||
|
||||
local function trackers_invoke(event, component, entity, ...)
|
||||
local trackers = events[event][component]
|
||||
if not trackers then
|
||||
return
|
||||
end
|
||||
|
||||
for _, tracker in trackers do
|
||||
tracker(entity, data)
|
||||
end
|
||||
end
|
||||
|
||||
local function trackers_init(event, component, fn)
|
||||
local ob = events[event]
|
||||
|
||||
return {
|
||||
connect = function(component, fn)
|
||||
local trackers = ob[component]
|
||||
if not trackers then
|
||||
trackers = {}
|
||||
ob[component] = trackers
|
||||
end
|
||||
|
||||
table.insert(trackers, fn)
|
||||
end,
|
||||
invoke = function(component, ...)
|
||||
trackers_invoke(event, component, ...)
|
||||
end
|
||||
}
|
||||
return function(component, fn)
|
||||
local trackers = ob[component]
|
||||
if not trackers then
|
||||
trackers = {}
|
||||
ob[component] = trackers
|
||||
end
|
||||
|
||||
table.insert(trackers, fn)
|
||||
end
|
||||
end
|
||||
|
||||
local trackers = {
|
||||
emplace = trackers_init("emplace"),
|
||||
add = trackers_init("added"),
|
||||
remove = trackers_init("removed")
|
||||
}
|
||||
|
||||
return trackers
|
8
demo/src/ReplicatedStorage/types.luau
Normal file
8
demo/src/ReplicatedStorage/types.luau
Normal file
|
@ -0,0 +1,8 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local observers_add = require("../ReplicatedStorage/observers_add")
|
||||
|
||||
export type World = typeof(observers_add(jecs.world()))
|
||||
export type Entity = jecs.Entity
|
||||
export type Id<T> = jecs.Id<T>
|
||||
|
||||
return {}
|
|
@ -1,4 +1,19 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local start = require(ReplicatedStorage.start)
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local schedule = require(ReplicatedStorage.schedule)
|
||||
local observers_add = require(ReplicatedStorage.observers_add)
|
||||
|
||||
start(script.Parent:WaitForChild("systems"):GetChildren())
|
||||
local SYSTEM = schedule.SYSTEM
|
||||
local RUN = schedule.RUN
|
||||
|
||||
require(ReplicatedStorage.components)
|
||||
local world = observers_add(jecs.world())
|
||||
|
||||
local systems = ServerScriptService.systems
|
||||
|
||||
SYSTEM(world, systems.replication)
|
||||
SYSTEM(world, systems.players_added)
|
||||
SYSTEM(world, systems.poison_hurts)
|
||||
SYSTEM(world, systems.life_is_painful)
|
||||
RUN(world, 0)
|
||||
|
|
12
demo/src/ServerScriptService/systems/life_is_painful.luau
Normal file
12
demo/src/ServerScriptService/systems/life_is_painful.luau
Normal file
|
@ -0,0 +1,12 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ct = require(ReplicatedStorage.components)
|
||||
local types = require(ReplicatedStorage.types)
|
||||
|
||||
return function(world: types.World, dt: number)
|
||||
for e in world:query(ct.Player):without(ct.Health) do
|
||||
world:set(e, ct.Health, 100)
|
||||
end
|
||||
for e in world:query(ct.Player, ct.Health):without(ct.Poison) do
|
||||
world:set(e, ct.Poison, 10)
|
||||
end
|
||||
end
|
|
@ -1,88 +0,0 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
--!strict
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local blink = require(game:GetService("ServerScriptService").net)
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local __ = jecs.Wildcard
|
||||
local std = ReplicatedStorage.std
|
||||
local ref = require(std.ref)
|
||||
local interval = require(std.interval)
|
||||
|
||||
local world = require(std.world)
|
||||
local cts = require(std.components)
|
||||
|
||||
local Mob = cts.Mob
|
||||
local Transform = cts.Transform
|
||||
local Velocity = cts.Velocity
|
||||
local Player = cts.Player
|
||||
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 targets = {}
|
||||
|
||||
for _, character in characters do
|
||||
table.insert(targets, (character.PrimaryPart :: Part).Position)
|
||||
end
|
||||
|
||||
for mob, transform, v in moving_mobs do
|
||||
local cf = transform.new
|
||||
local p = cf.Position
|
||||
|
||||
local target
|
||||
local closest
|
||||
|
||||
for _, pos in targets do
|
||||
local distance = (p - pos).Magnitude
|
||||
if not target or distance < closest then
|
||||
target = pos
|
||||
closest = distance
|
||||
end
|
||||
end
|
||||
|
||||
if not target then
|
||||
continue
|
||||
end
|
||||
|
||||
local moving = CFrame.new(p + (target - p).Unit * dt * v)
|
||||
transform.new = moving
|
||||
blink.UpdateTransform.FireAll(mob, moving)
|
||||
end
|
||||
end
|
||||
|
||||
local throttle = interval(5)
|
||||
|
||||
local function spawnMobs()
|
||||
if throttle() then
|
||||
local p = Vector3.new(0, 5, 0)
|
||||
local cf = CFrame.new(p)
|
||||
local v = 5
|
||||
|
||||
local e = world:entity()
|
||||
world:set(e, Velocity, v)
|
||||
world:set(e, Transform, { new = cf })
|
||||
world:add(e, Mob)
|
||||
|
||||
blink.SpawnMob.FireAll(e, cf, v)
|
||||
end
|
||||
end
|
||||
|
||||
local scheduler = require(std.scheduler)
|
||||
|
||||
scheduler.SYSTEM(spawnMobs)
|
||||
scheduler.SYSTEM(mobsMove)
|
||||
|
||||
return 0
|
|
@ -1,40 +0,0 @@
|
|||
local Players = game:GetService("Players")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local std = ReplicatedStorage.std
|
||||
local ref = require(std.ref)
|
||||
local collect = require(std.collect)
|
||||
|
||||
local cts = require(std.components)
|
||||
local world = require(std.world)
|
||||
local Player = cts.Player
|
||||
local Character = cts.Character
|
||||
|
||||
local conn = {}
|
||||
|
||||
local function playersAdded(player: Player)
|
||||
local e = ref(player.UserId)
|
||||
world:set(e, Player, player)
|
||||
local characterAdd = player.CharacterAdded
|
||||
conn[e] = characterAdd:Connect(function(rig)
|
||||
while rig.Parent ~= workspace do
|
||||
task.wait()
|
||||
end
|
||||
world:set(e, Character, rig)
|
||||
end)
|
||||
end
|
||||
|
||||
local function playersRemoved(player: Player)
|
||||
local e = ref(player.UserId)
|
||||
world:clear(e)
|
||||
local connection = conn[e]
|
||||
connection:Disconnect()
|
||||
conn[e] = nil
|
||||
end
|
||||
|
||||
local scheduler = require(std.scheduler)
|
||||
local phases = require(std.phases)
|
||||
scheduler.SYSTEM(playersAdded, phases.PlayerAdded)
|
||||
scheduler.SYSTEM(playersRemoved, phases.PlayerRemoved)
|
||||
|
||||
return 0
|
20
demo/src/ServerScriptService/systems/players_added.luau
Normal file
20
demo/src/ServerScriptService/systems/players_added.luau
Normal file
|
@ -0,0 +1,20 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local collect = require(ReplicatedStorage.collect)
|
||||
local types = require(ReplicatedStorage.types)
|
||||
local ct = require(ReplicatedStorage.components)
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local player_added = collect(Players.PlayerAdded)
|
||||
return function(world: types.World, dt: number)
|
||||
for player in player_added do
|
||||
local entity = world:entity()
|
||||
world:set(entity, ct.Player, player)
|
||||
end
|
||||
|
||||
for entity, player in world:query(ct.Player):without(ct.Renderable) do
|
||||
local character = player.Character
|
||||
if character and character.Parent ~= nil then
|
||||
world:set(entity, ct.Renderable, character)
|
||||
end
|
||||
end
|
||||
end
|
12
demo/src/ServerScriptService/systems/poison_hurts.luau
Normal file
12
demo/src/ServerScriptService/systems/poison_hurts.luau
Normal file
|
@ -0,0 +1,12 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ct = require(ReplicatedStorage.components)
|
||||
return function(world, dt)
|
||||
for e, poison, health in world:query(ct.Poison, ct.Health) do
|
||||
local health_after_tick = health - poison * dt * 0.05
|
||||
if health_after_tick < 0 then
|
||||
world:remove(e, ct.Health)
|
||||
continue
|
||||
end
|
||||
world:set(e, ct.Health, health_after_tick)
|
||||
end
|
||||
end
|
122
demo/src/ServerScriptService/systems/replication.luau
Normal file
122
demo/src/ServerScriptService/systems/replication.luau
Normal file
|
@ -0,0 +1,122 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local types = require("../../ReplicatedStorage/types")
|
||||
local ct = require("../../ReplicatedStorage/components")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local remotes = require("../../ReplicatedStorage/remotes")
|
||||
|
||||
return function(world: types.World)
|
||||
local storages = {}
|
||||
|
||||
for component in world:query(ct.Networked) do
|
||||
local is_tag = jecs.is_tag(world, component)
|
||||
local storage = {} :: { [types.Entity]: any }
|
||||
storages[component] = storage
|
||||
|
||||
if is_tag then
|
||||
world:added(component, function(entity)
|
||||
storage[entity] = true
|
||||
end)
|
||||
else
|
||||
world:added(component, function(entity, _, value)
|
||||
storage[entity] = value
|
||||
end)
|
||||
world:changed(component, function(entity, _, value)
|
||||
storage[entity] = value
|
||||
end)
|
||||
end
|
||||
|
||||
world:removed(component, function(entity)
|
||||
storage[entity] = "jecs.Remove"
|
||||
end)
|
||||
end
|
||||
|
||||
for relation in world:query(ct.NetworkedPair) do
|
||||
world:added(relation, function(entity, id, value)
|
||||
local is_tag = jecs.is_tag(world, id)
|
||||
local storage = storages[id]
|
||||
if not storage then
|
||||
storage = {}
|
||||
storages[id] = storage
|
||||
end
|
||||
if is_tag then
|
||||
storage[entity] = true
|
||||
else
|
||||
storage[entity] = value
|
||||
end
|
||||
end)
|
||||
|
||||
world:changed(relation, function(entity, id, value)
|
||||
local is_tag = jecs.is_tag(world, id)
|
||||
if is_tag then
|
||||
return
|
||||
end
|
||||
|
||||
local storage = storages[id]
|
||||
if not storage then
|
||||
storage = {}
|
||||
storages[id] = storage
|
||||
end
|
||||
|
||||
storage[entity] = value
|
||||
end :: <T>(types.Entity, types.Id<T>, T) -> ())
|
||||
|
||||
world:removed(relation, function(entity, id)
|
||||
local storage = storages[id]
|
||||
if not storage then
|
||||
storage = {}
|
||||
storages[id] = storage
|
||||
end
|
||||
|
||||
storage[entity] = "jecs.Remove"
|
||||
end)
|
||||
end
|
||||
|
||||
return function()
|
||||
local snapshot = {} :: {
|
||||
[string]: {
|
||||
set: { types.Entity }?,
|
||||
values: { any }?,
|
||||
removed: { types.Entity }?
|
||||
}
|
||||
}
|
||||
|
||||
local set_ids = {} :: { types.Entity }
|
||||
local removed_ids = {} :: { types.Entity }
|
||||
|
||||
for component, storage in storages do
|
||||
local set_values = {}
|
||||
local set_n = 0
|
||||
local removed_n = 0
|
||||
for e, v in storage do
|
||||
if v ~= "jecs.Remove" then
|
||||
set_n += 1
|
||||
set_ids[set_n] = e
|
||||
set_values[set_n] = v or true
|
||||
elseif world:contains(e) then
|
||||
removed_n += 1
|
||||
removed_ids[removed_n] = e
|
||||
end
|
||||
end
|
||||
|
||||
table.clear(storage)
|
||||
|
||||
local dirty = false
|
||||
|
||||
if set_n > 0 or removed_n > 0 then
|
||||
dirty = true
|
||||
end
|
||||
|
||||
if dirty then
|
||||
snapshot[tostring(component)] = {
|
||||
set = if set_n > 0 then table.move(set_ids, 1, set_n, 1, {}) else nil,
|
||||
values = if set_n > 0 then set_values else nil,
|
||||
removed = if removed_n > 0 then table.move(removed_ids, 1, removed_n, 1, {} :: { types.Entity }) else nil
|
||||
} :: any
|
||||
end
|
||||
end
|
||||
|
||||
if next(snapshot) ~= nil then
|
||||
remotes.replication:FireAllClients(snapshot)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local start = require(ReplicatedStorage.start)
|
||||
|
||||
start(script.Parent:WaitForChild("systems"):GetChildren())
|
|
@ -1,67 +0,0 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
--!strict
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local __ = jecs.Wildcard
|
||||
local std = ReplicatedStorage.std
|
||||
|
||||
local world = require(std.world)
|
||||
|
||||
local Position = world:component() :: jecs.Entity<vector>
|
||||
local Previous = jecs.Rest
|
||||
local pre = jecs.pair(Position, Previous)
|
||||
|
||||
local added = world
|
||||
:query(Position)
|
||||
:without(pre)
|
||||
:cached()
|
||||
local changed = world
|
||||
:query(Position, pre)
|
||||
:cached()
|
||||
local removed = world
|
||||
:query(pre)
|
||||
:without(Position)
|
||||
:cached()
|
||||
|
||||
local children = {}
|
||||
for i = 1, 10 do
|
||||
local e = world:entity()
|
||||
world:set(e, Position, vector.create(i, i, i))
|
||||
table.insert(children, e)
|
||||
end
|
||||
local function flip()
|
||||
return math.random() > 0.5
|
||||
end
|
||||
local function system()
|
||||
for i, child in children do
|
||||
world:set(child, Position, vector.create(i,i,i))
|
||||
end
|
||||
for e, p in added:iter() do
|
||||
world:set(e, pre, p)
|
||||
end
|
||||
for i, child in children do
|
||||
if flip() then
|
||||
world:set(child, Position, vector.create(i + 1, i + 1, i + 1))
|
||||
end
|
||||
end
|
||||
for e, new, old in changed:iter() do
|
||||
if new ~= old then
|
||||
world:set(e, pre, new)
|
||||
end
|
||||
end
|
||||
|
||||
for i, child in children do
|
||||
world:remove(child, Position)
|
||||
end
|
||||
|
||||
for e in removed:iter() do
|
||||
world:remove(e, pre)
|
||||
end
|
||||
end
|
||||
local scheduler = require(std.scheduler)
|
||||
|
||||
scheduler.SYSTEM(system)
|
||||
|
||||
return 0
|
|
@ -1,90 +0,0 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
--!strict
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local __ = jecs.Wildcard
|
||||
local std = ReplicatedStorage.std
|
||||
|
||||
local world = require(std.world)
|
||||
|
||||
local Position = world:component() :: jecs.Entity<vector>
|
||||
local Previous = jecs.Rest
|
||||
local pre = jecs.pair(Position, Previous)
|
||||
|
||||
local added = world
|
||||
:query(Position)
|
||||
:without(pre)
|
||||
:cached()
|
||||
local changed = world
|
||||
:query(Position, pre)
|
||||
:cached()
|
||||
local removed = world
|
||||
:query(pre)
|
||||
:without(Position)
|
||||
:cached()
|
||||
|
||||
local children = {}
|
||||
for i = 1, 10 do
|
||||
local e = world:entity()
|
||||
world:set(e, Position, vector.create(i, i, i))
|
||||
table.insert(children, e)
|
||||
end
|
||||
local function flip()
|
||||
return math.random() > 0.5
|
||||
end
|
||||
local entity_index = world.entity_index
|
||||
local function copy(archetypes, id)
|
||||
for _, archetype in archetypes do
|
||||
|
||||
local to = jecs.archetype_traverse_add(world, pre, archetype)
|
||||
local columns = to.columns
|
||||
local records = to.records
|
||||
local old = columns[records[pre].column]
|
||||
local new = columns[records[id].column]
|
||||
|
||||
if to ~= archetype then
|
||||
for _, entity in archetype.entities do
|
||||
local r = jecs.entity_index_try_get_fast(entity_index, entity)
|
||||
jecs.entity_move(entity_index, entity, r, to)
|
||||
end
|
||||
end
|
||||
|
||||
table.move(new, 1, #new, 1, old)
|
||||
|
||||
end
|
||||
end
|
||||
local function system2()
|
||||
for i, child in children do
|
||||
world:set(child, Position, vector.create(i,i,i))
|
||||
end
|
||||
for e, p in added:iter() do
|
||||
end
|
||||
copy(added:archetypes(), Position)
|
||||
for i, child in children do
|
||||
if flip() then
|
||||
world:set(child, Position, vector.create(i + 1, i + 1, i + 1))
|
||||
end
|
||||
end
|
||||
|
||||
for e, new, old in changed:iter() do
|
||||
if new ~= old then
|
||||
end
|
||||
end
|
||||
|
||||
copy(changed:archetypes(), Position)
|
||||
|
||||
for i, child in children do
|
||||
world:remove(child, Position)
|
||||
end
|
||||
|
||||
for e in removed:iter() do
|
||||
world:remove(e, pre)
|
||||
end
|
||||
end
|
||||
local scheduler = require(std.scheduler)
|
||||
|
||||
scheduler.SYSTEM(system2)
|
||||
|
||||
return 0
|
|
@ -1,46 +0,0 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local blink = require(ReplicatedStorage.net)
|
||||
local std = ReplicatedStorage.std
|
||||
local world = require(std.world)
|
||||
local ref = require(std.ref)
|
||||
|
||||
local cts = require(std.components)
|
||||
|
||||
local Model = cts.Model
|
||||
local Transform = cts.Transform
|
||||
|
||||
local moved_models = world:query(Model, Transform):cached()
|
||||
local updated_models = {}
|
||||
local i = 0
|
||||
local function processed(n)
|
||||
i += 1
|
||||
if i > n then
|
||||
i = 0
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function move(dt: number)
|
||||
for entity, model in moved_models do
|
||||
if updated_models[entity] then
|
||||
updated_models[entity] = nil
|
||||
model.PrimaryPart.CFrame = transform
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function syncTransforms()
|
||||
for _, id, cf in blink.UpdateTransform.Iter() do
|
||||
local e = ref("server-" .. tostring(id))
|
||||
world:set(e, Transform, cf)
|
||||
moved_models[e] = true
|
||||
end
|
||||
end
|
||||
|
||||
local scheduler = require(std.scheduler)
|
||||
|
||||
scheduler.SYSTEM(move)
|
||||
scheduler.SYSTEM(syncTransforms)
|
||||
|
||||
return 0
|
|
@ -1,31 +0,0 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local blink = require(ReplicatedStorage.net)
|
||||
local std = ReplicatedStorage.std
|
||||
local ref = require(std.ref)
|
||||
local world = require(std.world)
|
||||
local cts = require(std.components)
|
||||
|
||||
local function syncMobs()
|
||||
for _, id, cf, vel in blink.SpawnMob.Iter() do
|
||||
local part = Instance.new("Part")
|
||||
part.Size = Vector3.one * 5
|
||||
part.BrickColor = BrickColor.Red()
|
||||
part.Anchored = true
|
||||
local model = Instance.new("Model")
|
||||
model.PrimaryPart = part
|
||||
part.Parent = model
|
||||
model.Parent = workspace
|
||||
|
||||
local e = ref("server-" .. tostring(id))
|
||||
world:set(e, cts.Transform, { new = cf, old = cf })
|
||||
world:set(e, cts.Velocity, vel)
|
||||
world:set(e, cts.Model, model)
|
||||
world:add(e, cts.Mob)
|
||||
end
|
||||
end
|
||||
|
||||
local scheduler = require(std.scheduler)
|
||||
scheduler.SYSTEM(syncMobs)
|
||||
|
||||
return 0
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
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
|
|
@ -2672,8 +2672,8 @@ return {
|
|||
ECS_META_RESET = ECS_META_RESET,
|
||||
|
||||
IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean,
|
||||
ECS_PAIR_FIRST = ECS_PAIR_FIRST,
|
||||
ECS_PAIR_SECOND = ECS_PAIR_SECOND,
|
||||
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>,
|
||||
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>,
|
||||
pair_first = (ecs_pair_first :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<P>,
|
||||
pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>,
|
||||
entity_index_get_alive = entity_index_get_alive,
|
||||
|
|
|
@ -3,3 +3,4 @@ wally = "upliftgames/wally@0.3.2"
|
|||
rojo = "rojo-rbx/rojo@7.4.4"
|
||||
stylua = "johnnymorganz/stylua@2.0.1"
|
||||
Blink = "1Axen/Blink@0.14.1"
|
||||
wally-package-types = "JohnnyMorganz/wally-package-types@1.4.2"
|
||||
|
|
Loading…
Reference in a new issue