Initial commit

This commit is contained in:
Ukendio 2024-07-29 00:32:23 +02:00
parent 87e986b6aa
commit 0a1a7c1955
8 changed files with 378 additions and 5 deletions

71
example.project.json Normal file
View file

@ -0,0 +1,71 @@
{
"name": "example",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"Shared": {
"$path": "example/src/shared"
},
"ecs": {
"$path": "src"
}
},
"ServerScriptService": {
"Server": {
"$path": "example/src/server"
}
},
"StarterPlayer": {
"StarterPlayerScripts": {
"Client": {
"$path": "example/src/client"
}
}
},
"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
}
}
}
}

6
example/.gitignore vendored Normal file
View file

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

17
example/README.md Normal file
View file

@ -0,0 +1,17 @@
# example
Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
## Getting Started
To build the place from scratch, use:
```bash
rojo build -o "example.rbxlx"
```
Next, open `example.rbxlx` in Roblox Studio and start the Rojo server:
```bash
rojo serve
```
For more help, check out [the Rojo documentation](https://rojo.space/docs).

View file

@ -0,0 +1 @@
print("Hello world, from client!")

View file

@ -0,0 +1 @@
print("Hello world, from server!")

View file

@ -0,0 +1,277 @@
--!optimize 2
--!native
local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.WorldShim
type Entity<T = any> = jecs.Entity<T>
local function LOOP_ERROR(str)
-- We don't want to interrupt the loop when we error
task.spawn(error, str)
end
local Scheduler: (World, ...ModuleScript) -> (number) -> ()
do
local systems
local N
local w
local dt
local systemsNames
local function run(system)
local name = systemsNames[system]
return function()
debug.profilebegin(name)
debug.setmemorycategory(name)
system(w, dt)
debug.profileend()
end
end
local function loop(sinceLastFrame)
debug.profilebegin("loop()")
for i = N, 1, -1 do
local system = systems[i]
dt = sinceLastFrame
local didNotYield, why = xpcall(function()
for _ in run(system) do end
end, debug.traceback)
if didNotYield then
continue
end
if string.find(why, "thread is not yieldable") then
N -= 1
table.remove(systems, i)
LOOP_ERROR("Not allowed to yield in the systems."
.. "\n"
.. "System: "
.. debug.info(system, "n")
.. " has been ejected"
)
continue
end
LOOP_ERROR(why)
end
debug.profileend()
debug.resetmemorycategory()
end
function Scheduler(world, ...)
systems = { ... }
systemsNames = {}
N = #systems
w = world
for i, system in systems do
systems[i] = require(system)
systemsNames[system] = debug.info(system, "n")
end
return loop
end
end
type Tracker<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
}
local ChangeTracker: <T>(world: any, component: Entity<T>) -> Tracker<T>
do
local world: World
local T
local PreviousT
local addedComponents
local removedComponents
local isTrivial
local added
local removed
local function changes_added()
added = true
local q = world:query(T):without(PreviousT)
return function()
local id, data = q:next()
if not id then
return nil
end
if isTrivial == nil then
isTrivial = typeof(data) ~= "table"
end
if not isTrivial then
data = table.clone(data)
end
addedComponents[id] = data
return id, data
end
end
local function shallowEq(a, b)
for k, v in a do
if b[k] ~= v then
return false
end
end
return true
end
local function changes_changed()
local q = world:query(T, PreviousT)
return function()
local id, new, old = q:next()
while true do
if not id then
return nil
end
if not isTrivial then
if not shallowEq(new, old) then
break
end
elseif new ~= old then
break
end
id, new, old = q:next()
end
addedComponents[id] = new
return id, old, new
end
end
local function changes_removed()
removed = true
local q = world:query(PreviousT):without(T)
return function()
local id = q:next()
if id then
table.insert(removedComponents, id)
end
return id
end
end
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}
local function track(fn)
added = true
removed = true
fn(changes)
if not added then
for _ in changes_added() do
end
end
if not removed then
for _ in changes_removed() do
end
end
for e, data in addedComponents do
world:set(e, PreviousT, if isTrivial then data else table.clone(data))
end
for _, e in removedComponents do
world:remove(e, PreviousT)
end
end
local tracker = { track = track }
function ChangeTracker<T>(worldToTrack: World, component: Entity<T>): Tracker<T>
world = worldToTrack
T = component
-- We just use jecs.Rest because people will probably not use it anyways
PreviousT = jecs.pair(jecs.Rest, T)
addedComponents = {}
removedComponents = {}
return tracker
end
end
local Allocator: <T>(World, Entity<T>, (T) -> ()) -> { alloc: (Entity) -> (), free: (Entity) -> (), deinit: () -> () }
do
local arena: {}
local world
local cleanup
local id
local function alloc(entity: Entity)
table.insert(arena, entity)
end
local function deinit()
for _, e in arena do
cleanup(world:get(e, id), e)
world:clear(e)
end
end
local function free(entity: Entity)
local e = table.remove(arena, table.find(arena, entity))
if e then
cleanup(world:get(e, id), e)
world:clear(e)
end
end
local handle = {
alloc = alloc,
deinit = deinit,
free = free,
}
setmetatable(handle, handle)
function Allocator<T>(w: World, T: Entity<T>, fn: (T) -> ())
arena = {}
world = w
cleanup = fn
id = T
return handle
end
end
local world = jecs.World.new()
local Model = world:component() :: Entity<Model>
local ModelAllocator = Allocator(world,
Model, function(model) model:Destroy() end)
local e = world:entity()
world:set(e, Model, Instance.new("Model"))
ModelAllocator.alloc(e)
ModelAllocator.free(e)
return {
Scheduler = Scheduler,
ChangeTracker = ChangeTracker,
Allocator = Allocator
}

View file

@ -1207,7 +1207,7 @@ return {
Component = EcsComponent, Component = EcsComponent,
Wildcard = EcsWildcard :: Entity, Wildcard = EcsWildcard :: Entity,
w = EcsWildcard :: Entity, w = EcsWildcard :: Entity,
Rest = EcsRest, Rest = EcsRest :: Entity,
pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number, pair = (ECS_PAIR :: any) :: <R, T>(pred: Entity, obj: Entity) -> number,

View file

@ -594,10 +594,10 @@ do
end end
if is_trivial == nil then if is_trivial == nil then
isTrivial = typeof(data) ~= "table" is_trivial = typeof(data) ~= "table"
end end
if not isTrivial then if not is_trivial then
data = table.clone(data) data = table.clone(data)
end end
@ -635,7 +635,7 @@ do
return nil return nil
end end
if isTrivial and new ~= old then if is_trivial and new ~= old then
elseif diff(new, old) then elseif diff(new, old) then
break break
end end
@ -685,7 +685,7 @@ do
end end
for e, data in add do for e, data in add do
world:set(e, PreviousT, if isTrivial then data else table.clone(data)) world:set(e, PreviousT, if is_trivial then data else table.clone(data))
end end
end end