Replace demo with advanced examples

This commit is contained in:
Ukendio 2025-11-30 08:59:04 +01:00
parent 553cb89b10
commit 8e06781be6
21 changed files with 438 additions and 771 deletions

6
demo/.gitignore vendored
View file

@ -1,6 +0,0 @@
# Project place file
/example.rbxlx
# Roblox Studio lock files
/*.rbxlx.lock
/*.rbxl.lock

View file

@ -1,15 +0,0 @@
# Demo
## Build with Rojo
To build the place, run the following commands from the root of the repository:
```bash
cd demo
rojo build -o "demo.rbxl"
```
Next, open `demo.rbxl` in Roblox Studio and start the Rojo server:
```bash
rojo serve
```

View file

@ -1,55 +0,0 @@
{
"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"
}
}
}
}

View file

@ -1,65 +0,0 @@
--!strict
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs)
local types = require("./types")
local Networked = jecs.tag()
local NetworkedPair = jecs.tag()
local InstanceMapping = jecs.component() :: jecs.Id<Instance>
jecs.meta(InstanceMapping, jecs.OnAdd, function(component)
jecs.meta(component, jecs.OnAdd, function(entity, _, instance)
if RunService:IsServer() then
instance:SetAttribute("entity_server")
else
instance:SetAttribute("entity_client")
end
end)
end)
local function networked_id(ct)
jecs.meta(ct, Networked)
return ct
end
local function networked_pair(ct)
jecs.meta(ct, NetworkedPair)
return ct
end
local function instance_mapping_id(ct)
jecs.meta(ct, InstanceMapping)
return ct
end
local Renderable = jecs.component() :: types.Id<Instance>
local Poison = jecs.component() :: types.Id<number>
local Health = jecs.component() :: types.Id<number>
local Player = jecs.component() :: types.Id<Player>
local Debuff = jecs.tag() :: types.Entity
local Lifetime = jecs.component() :: types.Id<{
duration: number,
created: number
}>
local Destroy = jecs.tag()
local components = {
Renderable = networked_id(instance_mapping_id(Renderable)),
Player = networked_id(Player),
Poison = networked_id(Poison),
Health = networked_id(Health),
Lifetime = networked_id(Lifetime),
Debuff = networked_id(Debuff),
Destroy = networked_id(Destroy),
-- We have to define that some builtin IDs can also be networked
ChildOf = networked_pair(jecs.ChildOf),
Networked = Networked,
NetworkedPair = NetworkedPair,
}
for name, component in components :: {[string]: types.Id<any> } do
jecs.meta(component, jecs.Name, name)
end
return components

View file

@ -1,10 +0,0 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs)
local schedule = require(ReplicatedStorage.schedule)
local heartbeat = schedule(world,
systems.entities_delete,
systems.replication
)
game:GetService("RunService").Heartbeat:Connect(heartbeat)

View file

@ -1,91 +0,0 @@
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jabby = require(ReplicatedStorage.Packages.jabby)
local ct = require(ReplicatedStorage.components)
local jecs = require(ReplicatedStorage.ecs)
jabby.set_check_function(function(player) return true end)
local scheduler = jabby.scheduler.create()
jabby.register({
applet = jabby.applets.scheduler,
name = "Scheduler",
configuration = {
scheduler = scheduler,
},
}::any)
local ContextActionService = game:GetService("ContextActionService")
local function create_widget(_, state: Enum.UserInputState): Enum.ContextActionResult
local client = jabby.obtain_client()
if state ~= Enum.UserInputState.Begin then
return Enum.ContextActionResult.Pass
end
client.spawn_app(client.apps.home::any, nil)
return Enum.ContextActionResult.Sink
end
local RunService = game:GetService("RunService")
local function schedule(world, ...)
local function get_entity_from_part(part: BasePart): (jecs.Entity<any>?, PVInstance?)
for id, model in world:query(ct.Renderable) do
if not part:IsDescendantOf(model) then continue end
return id, model
end
return nil, nil
end
jabby.register({
applet = jabby.applets.world,
name = "World",
configuration = {
world = world,
get_entity_from_part = get_entity_from_part,
},
}::any)
local systems = { ... }
local function systems_load(mod: ModuleScript, ...)
local fn = require(mod) :: (...any) -> ()
local system = fn(...) or fn
local system_id = scheduler:register_system({
name = mod.Name,
module = mod,
})
return {
system = system,
id = system_id
}
end
for i, mod in systems do
systems[i] = systems_load(mod, world, 0)
end
if RunService:IsClient() then
ContextActionService:BindAction(
"Open Jabby Home",
create_widget,
false,
Enum.KeyCode.F4
)
end
return function(dt: number, input: InputObject?)
for i, config in systems do
-- config.system(world, dt, input)
local system = config.system
local id = config.id
scheduler:run(id, system,
world, dt, input)
end
end
end
return schedule

View file

@ -1,13 +0,0 @@
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local types = require(ReplicatedStorage.types)
local ct = require(ReplicatedStorage.components)
local function entities_delete(world: types.World, dt: number)
for e in world:each(ct.Destroy) do
world:delete(e)
end
end
return entities_delete

View file

@ -1,14 +0,0 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)
export type World = typeof(jecs.world())
export type Entity = jecs.Entity
export type Id<T> = jecs.Id<T>
export type Snapshot = {
[string]: {
set: { jecs.Entity }?,
values: { any }?,
removed: { jecs.Entity }?
}
}
return {}

View file

@ -1,21 +0,0 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local jecs = require(ReplicatedStorage.ecs)
local schedule = require(ReplicatedStorage.schedule)
require(ReplicatedStorage.components)
local world = jecs.world()
local systems = ServerScriptService.systems
local heartbeat = schedule(world,
systems.players_added,
systems.poison_hurts,
systems.health_regen,
systems.lifetimes_expire,
systems.life_is_painful,
systems.entities_delete,
systems.replication
)
game:GetService("RunService").Heartbeat:Connect(heartbeat)

View file

@ -1,15 +0,0 @@
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, health in world:query(ct.Health) do
if math.random() < 1 / 60 / 30 then
world:set(e, ct.Health, 100)
end
end
end

View file

@ -1,19 +0,0 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
local jecs = require(ReplicatedStorage.ecs)
return function(world: types.World, dt: number)
if math.random() < (1 / 60 / 7) then
for e in world:each(ct.Health) do
local poison = world:entity()
world:add(poison, ct.Debuff)
world:add(poison, jecs.pair(jecs.ChildOf, e))
world:set(poison, ct.Poison, 10)
world:set(poison, ct.Lifetime, {
duration = 3,
created = os.clock()
})
end
end
end

View file

@ -1,12 +0,0 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
return function(world: types.World, dt: number)
for e, lifetime in world:query(ct.Lifetime) do
if os.clock() > lifetime.created + lifetime.duration then
world:add(e, ct.Destroy)
end
end
end

View file

@ -1,24 +0,0 @@
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 not character then
continue
end
if not character.Parent then
continue
end
world:set(entity, ct.Renderable, character)
end
end

View file

@ -1,18 +0,0 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
local jecs = require(ReplicatedStorage.ecs)
return function(world: types.World, dt: number)
for e, poison_tick in world:query(ct.Poison, jecs.pair(jecs.ChildOf, jecs.w)) do
local tgt = world:target(e, jecs.ChildOf)
local health = world:get(tgt, ct.Health)
if not health then
continue
end
if math.random() < 1 / 60 / 1 and health > 1 then
world:set(tgt, ct.Health, health - 1)
end
end
end

View file

@ -1,8 +0,0 @@
[package]
name = "marcus/demo"
version = "0.1.0"
registry = "https://github.com/UpliftGames/wally-index"
realm = "shared"
[dependencies]
jabby = "alicesaidhi/jabby@0.2.2"

View file

@ -1,121 +1,122 @@
local types = require("../types") --!strict
local jecs = require(game:GetService("ReplicatedStorage").ecs) local remotes = require("./remotes")
local remotes = require("../remotes") local jecs = require("@jecs")
local collect = require("../collect") local collect = require("@modules/collect")
local components = require("../components")
-- this is just an example, you need an actually populated map
local components: { [string]: jecs.Id } = {}
local client_ids: {[jecs.Entity]: jecs.Entity } = {}
local client_ids: {[jecs.Entity]: jecs.Entity<nil> } = {}
local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity)
local e = 0 local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity)
local e = 0
local ser_id = id
local deser_id = client_ids[ser_id] local ser_id = id
if deser_id then local deser_id = client_ids[ser_id]
if deser_id == 0 then if deser_id then
local new_id = world:entity() if deser_id == 0::any then
client_ids[ser_id] = new_id local new_id = world:entity()
deser_id = new_id client_ids[ser_id] = new_id
end deser_id = new_id
else end
if not world:exists(ser_id) else
or (world:contains(ser_id) and not world:get(ser_id, jecs.Name)) if not world:exists(ser_id)
then or (world:contains(ser_id) and not world:get(ser_id, jecs.Name))
deser_id = world:entity() then
else deser_id = world:entity()
if world:contains(ser_id) and world:get(ser_id, jecs.Name) then else
deser_id = ser_id if world:contains(ser_id) and world:get(ser_id, jecs.Name) then
else deser_id = ser_id
deser_id = world:entity() else
end deser_id = world:entity()
end end
client_ids[ser_id] = deser_id end
end client_ids[ser_id] = deser_id
end
e = deser_id
e = deser_id
return e
end return e
end
-- local rel_render = `e{jecs.ECS_ID(rel)}v{jecs.ECS_GENERATION(rel)}`
-- local tgt_render = `e{jecs.ECS_ID(tgt)}v{jecs.ECS_GENERATION(tgt)}` -- local rel_render = `e{jecs.ECS_ID(rel)}v{jecs.ECS_GENERATION(rel)}`
-- local function ecs_deser_pairs_str(world, token) -- local tgt_render = `e{jecs.ECS_ID(tgt)}v{jecs.ECS_GENERATION(tgt)}`
-- local tokens = string.split(token, ",") -- local function ecs_deser_pairs_str(world, token)
-- local rel = tonumber(tokens[1]) :: jecs.Entity -- local tokens = string.split(token, ",")
-- local tgt = tonumber(tokens[2]) :: jecs.Entity -- local rel = tonumber(tokens[1]) :: jecs.Entity
-- local tgt = tonumber(tokens[2]) :: jecs.Entity
-- rel = ecs_ensure_entity(world, rel)
-- tgt = ecs_ensure_entity(world, tgt) -- rel = ecs_ensure_entity(world, rel)
-- tgt = ecs_ensure_entity(world, tgt)
-- return jecs.pair(rel, tgt)
-- end -- return jecs.pair(rel, tgt)
-- end
local function ecs_deser_pairs(world, rel, tgt)
rel = ecs_ensure_entity(world, rel) local function ecs_deser_pairs(world, rel: jecs.Entity, tgt: jecs.Entity)
tgt = ecs_ensure_entity(world, tgt) rel = ecs_ensure_entity(world, rel)
tgt = ecs_ensure_entity(world, tgt)
return jecs.pair(rel, tgt)
end return jecs.pair(rel, tgt)
end
local snapshots = collect(remotes.replication.OnClientEvent)
local snapshots = collect(remotes.replication.OnClientEvent)
return function(world: jecs.World)
for entity in world:each(components.Destroy) do return function(world: jecs.World)
client_ids[entity] = nil for entity in world:each(components.Destroy) do
end client_ids[entity] = nil
for snapshot in snapshots do end
for ser_id, map in snapshot do for snapshot in snapshots do
local id = (tonumber(ser_id) :: any) :: jecs.Entity for ser_id, map in snapshot do
if jecs.IS_PAIR(id) and map.pair then local id = (tonumber(ser_id) :: any) :: jecs.Entity
id = ecs_deser_pairs(world, map.relation, map.target) if jecs.IS_PAIR(id) and map.pair == true then
elseif id then id = ecs_deser_pairs(world, map.relation, map.target)
id = ecs_ensure_entity(world, id) elseif id then
end id = ecs_ensure_entity(world, id)
-- if not id then end
-- id = ecs_deser_pairs_str(world, ser_id) -- if not id then
-- else -- id = ecs_deser_pairs_str(world, ser_id)
-- id = ecs_ensure_entity(world, id) -- else
-- end -- id = ecs_ensure_entity(world, id)
local members = world:get(id, components.NetworkedMembers) -- end
local members = world:get(id, components.NetworkedMembers)
local set = map.set
if set then local set = map.set
if jecs.is_tag(world, id) then if set then
for _, entity in set do if jecs.is_tag(world, id) then
entity = ecs_ensure_entity(world, entity) for _, entity in set do
world:add(entity, id) entity = ecs_ensure_entity(world, entity)
end world:add(entity, id)
else end
local values = map.values :: { any } else
for i, entity in set do local values = map.values :: { any }
entity = ecs_ensure_entity(world, entity) for i, entity in set do
local value = values[i] entity = ecs_ensure_entity(world, entity)
if members then local value = values[i]
for _, member in members do if members then
local data = value[member] :: {jecs.Entity} | jecs.Entity -- targets for _, member in members do
if typeof(data) == "table" then local data = value[member] :: {jecs.Entity} | jecs.Entity -- targets
for pos, tgt in data :: { jecs.Entity } do if typeof(data) == "table" then
data[pos] = ecs_ensure_entity(world, tgt) for pos, tgt in data :: { jecs.Entity } do
end data[pos] = ecs_ensure_entity(world, tgt)
else end
value[member] = ecs_ensure_entity(world, data :: any) else
end value[member] = ecs_ensure_entity(world, data :: any)
end end
end end
world:set(entity, id, value) end
end world:set(entity, id, value)
end end
end end
end
local removed = map.removed
local removed = map.removed
if removed then
for _, entity in removed do if removed then
entity = ecs_ensure_entity(world, entity) for _, entity in removed do
world:remove(entity, id) entity = ecs_ensure_entity(world, entity)
end world:remove(entity, id)
end end
end end
end end
end end
end

View file

@ -1,222 +1,224 @@
--!strict --!strict
local Players = game:GetService("Players") local Players = require("@game/Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage") local remotes = require("./remotes")
local ct = require(ReplicatedStorage.components) local jecs = require("@jecs")
local components = ct :: { [string]: jecs.Entity } local collect = require("@modules/collect")
local remotes = require(ReplicatedStorage.remotes) local ty = require("./types")
local jecs = require(ReplicatedStorage.ecs)
local collect = require(ReplicatedStorage.collect) -- this is just an example, you need an actually populated map
local ty = require(ReplicatedStorage.types) local components: { [string]: jecs.Id } = {}
local ct = components
return function(world: jecs.World)
local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }}
local networked_components = {} return function(world: jecs.World)
local networked_pairs = {} local storages = {} :: { [jecs.Id]: {[jecs.Id]: any }}
local networked_components = {}
for component in world:each(ct.Networked) do local networked_pairs = {}
local name = world:get(component, jecs.Name)
assert(name) for component in world:each(ct.Networked) do
if components[name] == nil then local name = world:get(component, jecs.Name)
error("Invalid component:"..name) assert(name)
end if components[name] == nil then
error("Invalid component:"..name)
storages[component] = {} end
table.insert(networked_components, component) storages[component] = {}
end
table.insert(networked_components, component)
for relation in world:each(ct.NetworkedPair) do end
local name = world:get(relation, jecs.Name)
assert(name) for relation in world:each(ct.NetworkedPair) do
if not components[name] then local name = world:get(relation, jecs.Name)
error("Invalid component") assert(name)
end if not components[name] then
table.insert(networked_pairs, relation) error("Invalid component")
end end
table.insert(networked_pairs, relation)
for _, component in networked_components do end
local name = world:get(component, jecs.Name)
if not name or not components[name] then for _, component in networked_components do
-- error("Invalid component") local name = world:get(component, jecs.Name)
error(`Networked Component (%id{component}%name{name})`) if not name or not components[name] then
end -- error("Invalid component")
local is_tag = jecs.is_tag(world, component) error(`Networked Component (%id{component}%name{name})`)
local storage = storages[component] end
if is_tag then local is_tag = jecs.is_tag(world, component)
world:added(component, function(entity) local storage = storages[component]
storage[entity] = true if is_tag then
end) world:added(component, function(entity)
else storage[entity] = true
world:added(component, function(entity, _, value) end)
storage[entity] = value else
end) world:added(component, function(entity, _, value)
world:changed(component, function(entity, _, value) storage[entity] = value
storage[entity] = value end)
end) world:changed(component, function(entity, _, value)
end storage[entity] = value
end)
world:removed(component, function(entity) end
storage[entity] = "jecs.Remove"
end) world:removed(component, function(entity)
end storage[entity] = "jecs.Remove"
end)
for _, relation in networked_pairs do end
world:added(relation, function(entity: jecs.Entity, id: jecs.Id, value)
local is_tag = jecs.is_tag(world, id) for _, relation in networked_pairs do
local storage = storages[id] world:added(relation, function(entity: jecs.Entity, id: jecs.Id, value)
if not storage then local is_tag = jecs.is_tag(world, id)
storage = {} local storage = storages[id]
storages[id] = storage if not storage then
end storage = {}
if is_tag then storages[id] = storage
storage[entity] = true end
else if is_tag then
storage[entity] = value storage[entity] = true
end else
end) storage[entity] = value
end
world:changed(relation, function(entity: jecs.Id, id: jecs.Id, value) end)
local is_tag = jecs.is_tag(world, id)
if is_tag then world:changed(relation, function(entity: jecs.Id, id: jecs.Id, value)
return local is_tag = jecs.is_tag(world, id)
end if is_tag then
return
local storage = storages[id] end
if not storage then
storage = {} local storage = storages[id]
storages[id] = storage if not storage then
end storage = {}
storages[id] = storage
storage[entity] = value end
end)
storage[entity] = value
world:removed(relation, function(entity, id) end)
local storage = storages[id]
if not storage then world:removed(relation, function(entity, id)
storage = {} local storage = storages[id]
storages[id] = storage if not storage then
end storage = {}
storages[id] = storage
storage[entity] = "jecs.Remove" end
end)
end storage[entity] = "jecs.Remove"
end)
-- local requested_snapshots = collect(remotes.request_snapshot.OnServerEvent) end
local players_added = collect(Players.PlayerAdded)
-- local requested_snapshots = collect(remotes.request_snapshot.OnServerEvent)
return function(_, dt: number) local players_added = collect(Players.PlayerAdded)
local snapshot_lazy: ty.snapshot
local set_ids_lazy: { jecs.Entity } return function(_, dt: number)
local snapshot_lazy: ty.snapshot
-- In the future maybe it should be requested by the player instead when they local set_ids_lazy: { jecs.Id }
-- are ready to receive the replication. Otherwise streaming could be complicated
-- with intances references being nil. -- In the future maybe it should be requested by the player instead when they
for player in players_added do -- are ready to receive the replication. Otherwise streaming could be complicated
if not snapshot_lazy then -- with intances references being nil.
snapshot_lazy, set_ids_lazy = {}, {} for player in players_added do
if not snapshot_lazy then
for component, storage in storages do snapshot_lazy, set_ids_lazy = {}::any, {}
local set_values = {}
local set_n = 0 for component, storage in storages do
local set_values = {}
local q = world:query(component) local set_n = 0
local is_tag = jecs.is_tag(world, component)
for _, archetype in q:archetypes() do local q = world:query(component)
local entities = archetype.entities local is_tag = jecs.is_tag(world, component)
local entities_len = #entities for _, archetype in q:archetypes() do
table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy) local entities = archetype.entities
if not is_tag then local entities_len = #entities
local column = archetype.columns_map[component] table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy)
table.move(column, 1, entities_len, set_n + 1, set_values) if not is_tag then
end local column = archetype.columns_map[component]
table.move(column, 1, entities_len, set_n + 1, set_values)
set_n += entities_len end
end
set_n += entities_len
local set = table.move(set_ids_lazy, 1, set_n, 1, {}) end
local map = { local set = table.move(set_ids_lazy, 1, set_n, 1, {}::any)
set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil, local map = {
} set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil,
if jecs.IS_PAIR(component) then }
map.relation = jecs.pair_first(world, component)
map.target = jecs.pair_second(world, component) if jecs.IS_PAIR(component) then
map.pair = true map.relation = jecs.pair_first(world, component)
end map.target = jecs.pair_second(world, component)
snapshot_lazy[tostring(component)] = map map.pair = true
end end
end snapshot_lazy[tostring(component)] = map
end
remotes.replication:FireClient(player, snapshot_lazy) end
end
remotes.replication:FireClient(player, snapshot_lazy)
-- accumulator += dt end
-- Purposely sending less diffs of the world because doing it at 60hz -- accumulator += dt
-- gets expensive. But this requires interpolated elements in the scene.
-- if accumulator > 1/60 then -- Purposely sending less diffs of the world because doing it at 60hz
-- accumulator = 0 -- gets expensive. But this requires interpolated elements in the scene.
-- if accumulator > 1/60 then
local snapshot = {} :: ty.snapshot -- accumulator = 0
local set_ids = {} local snapshot = {} :: ty.snapshot
local removed_ids = {}
local set_ids = {}
for component, storage in storages do local removed_ids = {}
local set_values = {} :: { any }
local set_n = 0 for component, storage in storages do
local removed_n = 0 local set_values = {} :: { any }
for e, v in storage do local set_n = 0
if v ~= "jecs.Remove" then local removed_n = 0
set_n += 1 for e, v in storage do
set_ids[set_n] = e if v ~= "jecs.Remove" then
set_values[set_n] = v or true set_n += 1
elseif world:contains(e) then set_ids[set_n] = e
removed_n += 1 set_values[set_n] = v or true
removed_ids[removed_n] = e elseif world:contains(e) then
end removed_n += 1
end removed_ids[removed_n] = e
end
table.clear(storage) end
local dirty = false table.clear(storage)
if set_n > 0 or removed_n > 0 then local dirty = false
dirty = true
end if set_n > 0 or removed_n > 0 then
dirty = true
if dirty then end
local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity }
local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity } if dirty then
local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity }
-- local ser_id: string = nil :: any local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity }
-- if jecs.IS_PAIR(component) then -- local ser_id: string = nil :: any
-- ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}`
-- else -- if jecs.IS_PAIR(component) then
-- ser_id = tostring(component) -- ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}`
-- end -- else
-- ser_id = tostring(component)
local map = { -- end
set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil, local map = {
removed = if removed_n > 0 then removed else nil, set = if set_n > 0 then set else nil,
} values = if set_n > 0 then set_values else nil,
removed = if removed_n > 0 then removed else nil,
if jecs.IS_PAIR(component) then }
map.relation = jecs.pair_first(world, component)
map.target = jecs.pair_second(world, component) if jecs.IS_PAIR(component) then
map.pair = true map.relation = jecs.pair_first(world, component)
end map.target = jecs.pair_second(world, component)
map.pair = true
snapshot[tostring(component)] = map end
end
end snapshot[tostring(component)] = map
if next(snapshot) ~= nil then end
remotes.replication:FireAllClients(snapshot) end
-- print(snapshot) if next(snapshot::any) ~= nil then
end remotes.replication:FireAllClients(snapshot)
end -- print(snapshot)
end end
end
end

View file

@ -1,5 +1,5 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = require("@game/ReplicatedStorage")
local types = require("../ReplicatedStorage/types") local types = require("./types")
type Remote<T...> = { type Remote<T...> = {
FireClient: (Remote<T...>, Player, T...) -> (), FireClient: (Remote<T...>, Player, T...) -> (),
@ -31,12 +31,6 @@ end
return { return {
input = datagram_ensure("input") :: Remote<string>, input = datagram_ensure("input") :: Remote<string>,
replication = stream_ensure("replication") :: Remote<{ replication = stream_ensure("replication") :: Remote<types.snapshot>,
[string]: {
set: { types.Entity }?,
values: { any }?,
removed: { types.Entity }?
}
}>,
} }

19
examples/networking/types.luau Executable file
View file

@ -0,0 +1,19 @@
local jecs = require("@jecs")
export type snapshot = {
[string]: {
set: { jecs.Entity }?,
values: { any }?,
removed: { jecs.Entity }?,
pair: true,
relation: jecs.Entity,
target: jecs.Entity
} | {
set: { jecs.Entity }?,
values: { any }?,
removed: { jecs.Entity }?,
pair: false,
}
}
return {}

View file

@ -1,33 +1,31 @@
local function collect(signal)
--!strict local enqueued = {}
local function collect(signal)
local enqueued = {} local i = 0
local i = 0 local connection = signal:Connect(function(...)
table.insert(enqueued, { ... })
local connection = signal:Connect(function(...) i += 1
table.insert(enqueued, { ... }) end)
i += 1
end) return function(): any
if i == 0 then
return function(): any return
if i == 0 then end
return
end i -= 1
i -= 1 local args: any = table.remove(enqueued, 1)
local args: any = table.remove(enqueued, 1) return unpack(args)
end, connection
return unpack(args) end
end, connection
end type Signal<T... = ...any> = {
Connect: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
type Signal<T... = ...any> = { ConnectParallel: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
Connect: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection, Once: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
ConnectParallel: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection, Wait: (self: Signal<T...>) -> (T...)
Once: (self: Signal<T...>, callback: (T...) -> ()) -> RBXScriptConnection, }
Wait: (self: Signal<T...>) -> (T...)
} return collect :: <T...>(Signal<T...>) -> (() -> (T...), RBXScriptConnection)
return collect :: <T...>(Signal<T...>) -> (() -> (T...), RBXScriptConnection)

39
modules/remotes.luau Executable file
View file

@ -0,0 +1,39 @@
-- A simple way to safely type remote events without hassle
local ReplicatedStorage = require("@game/ReplicatedStorage")
local jecs = require("@jecs")
local ty = require("./")
type Remote<T...> = {
FireClient: (Remote<T...>, Player, T...) -> (),
FireAllClients: (Remote<T...>, T...) -> (),
FireServer: (Remote<T...>, T...) -> (),
OnServerEvent: RBXScriptSignal<(Player, T...)>,
OnClientEvent: RBXScriptSignal<T...>
}
local function stream_ensure(name)
local remote = ReplicatedStorage:FindFirstChild(name)
if not remote then
remote = Instance.new("RemoteEvent")
remote.Name = name
remote.Parent = ReplicatedStorage
end
return remote
end
local function datagram_ensure(name)
local remote = ReplicatedStorage:FindFirstChild(name)
if not remote then
remote = Instance.new("UnreliableRemoteEvent")
remote.Name = name
remote.Parent = ReplicatedStorage
end
return remote
end
return {
input = datagram_ensure("input") :: Remote<string>,
replication = stream_ensure("replication") :: Remote<snapshot>
}