mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
Replace demo with advanced examples
This commit is contained in:
parent
553cb89b10
commit
8e06781be6
21 changed files with 438 additions and 771 deletions
6
demo/.gitignore
vendored
6
demo/.gitignore
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
# Project place file
|
|
||||||
/example.rbxlx
|
|
||||||
|
|
||||||
# Roblox Studio lock files
|
|
||||||
/*.rbxlx.lock
|
|
||||||
/*.rbxl.lock
|
|
||||||
|
|
@ -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
|
|
||||||
```
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 {}
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
19
examples/networking/types.luau
Executable 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 {}
|
||||||
|
|
@ -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
39
modules/remotes.luau
Executable 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>
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue