Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 65 additions & 26 deletions lib/init.luau
Original file line number Diff line number Diff line change
@@ -1,74 +1,111 @@
local RunService = game:GetService 'RunService'
local RunService = game:GetService "RunService"
local is_client = RunService:IsClient()

local codec = require '@self/utils/codec'
local event = require '@self/event'
local func = require '@self/func'
local remote_index = require '@self/remote_index'
local state = require '@self/state'
local codec = require "@self/utils/codec"
local event = require "@self/event"
local func = require "@self/func"
local remote_index = require "@self/remote_index"
local state = require "@self/state"

export type state_type<T> = state.state_type<T>
export type event_type<T...> = event.event_type<T...>
export type func_type<T..., R...> = func.func_type<T..., R...>

-- defs
type remote_desc = {
id: number,
id: number?,
rbxclass: string?,
codec: remote_index.codec_impl?,
wrap: (Instance, any) -> any,
single: boolean?,
}

-- helpers
export type defnet = {
codec: typeof(codec),

remotes: <T>(T, Instance?) -> T,

step_sync: (Player?) -> (),
state: <T>(T?) -> state.state_type<T>,
event: typeof(event.define),
func: typeof(func.define),
}

local function sorted_keys(tab: { [string]: any }): { string }
local keys = {}

for k in tab do
table.insert(keys, k)
end

table.sort(keys)

return keys
end
local function deterministic_iter<V>(tabl: { string: V }): () -> (string, V)

local function deterministic_iter(tabl: { [string]: any })
local keys = sorted_keys(tabl)
local count = 0

return function()
count += 1

local key = keys[count]
return key, key and tabl[key]

if key == nil then
return nil
end

return key, tabl[key]
end
end
local function ensure_instance(parent: Instance, class: string, name: string)
if is_client then return parent:WaitForChild(name) end

local function ensure_instance(parent: Instance, class: string, name: string): Instance
if is_client then
return parent:WaitForChild(name)
end

local instance = Instance.new(class)

instance.Name = name
instance.Parent = parent

return instance
end

-- functions
local function define_remotes<T>(remotes_def: T, root: Instance?): T
assert(type(remotes_def) == 'table', 'refine type')
local parent: Instance = root or ensure_instance(script, 'Folder', 'rbx')
assert(type(remotes_def) == "table", "refine type")

local remotes_table = remotes_def :: any
local parent: Instance = root or ensure_instance(script, "Folder", "rbx")

for name, desc in deterministic_iter(remotes_table) do
if type(desc.rbxclass) ~= "string" then
local folder = ensure_instance(parent, "Folder", name)

for name, desc in deterministic_iter<<remote_desc>>(remotes_def) do
if type(desc.rbxclass) ~= 'string' then
local folder = ensure_instance(parent, 'Folder', name)
remotes_def[name] = define_remotes(desc, folder)
remotes_table[name] = define_remotes(desc, folder)

continue
end

desc.id = remote_index.reserve_id(name)

local rbx = ensure_instance(parent, desc.rbxclass, name)

if desc.single then remote_index.ensure_single(parent) end
if desc.codec then remote_index.set_codec(rbx, desc.codec) end
if desc.single then
remote_index.ensure_single(parent)
end

if desc.codec then
remote_index.set_codec(rbx, desc.codec)
end

remotes_def[name] = desc.wrap(rbx, desc)
remotes_table[name] = desc.wrap(rbx, desc)
end

return table.freeze(remotes_def) :: any
return table.freeze(remotes_table) :: T
end

-- module
return table.freeze {
local defnet: defnet = table.freeze {
codec = codec,

remotes = define_remotes,
Expand All @@ -77,3 +114,5 @@ return table.freeze {
event = event.define,
func = func.define,
}

return defnet
143 changes: 107 additions & 36 deletions lib/state.luau
Original file line number Diff line number Diff line change
@@ -1,91 +1,162 @@
local Players = game:GetService 'Players'
local RunService = game:GetService 'RunService'
local Players = game:GetService "Players"
local RunService = game:GetService "RunService"

local codec = require './utils/codec'
local diff = require './utils/diff'
local diff_codec = require './utils/diff_codec'
local remote_index = require './remote_index'
local sync = require './utils/sync'
local codec = require "./utils/codec"
local diff = require "./utils/diff"
local diff_codec = require "./utils/diff_codec"
local remote_index = require "./remote_index"
local sync = require "./utils/sync"

local is_client = RunService:IsClient()

local server_pools: { [Player]: codec.pool } = {}
local registry: { sync.syncer } = {}

export type client_state<T> = {
value: T,
get: () -> T,
changed: ((T, T?) -> ()) -> RBXScriptConnection,
}

-- defs
export type remote_desc<T> = { rbxclass: 'RemoteEvent', default: T?, id: number, single: true }
export type state_type<T> = {
changed: (() -> ()) -> (),
codec: codec.codec_impl<T>?,
client: client_state<T>,

changed: (() -> ()) -> RBXScriptConnection,
step: (Player?) -> (),

server: { [Player]: T },
client: T,

codec: codec.codec_impl<T>?,
}

-- vars
local is_client = RunService:IsClient()
local server_pools: { [Player]: codec.pool } = {}
local registry: { sync.syncer<Player> } = {}
export type remote_desc<T> = {
id: number,
rbxclass: "RemoteEvent",
codec: codec.codec_impl<T>?,
wrap: (RemoteEvent, remote_desc<T>) -> state_type<T>,
single: true,
default: T?,
}

-- definition
local function wrap<T>(event: BindableEvent, desc: remote_desc<T>): state_type<T>
local function listen_changed(callback)
return event.Event:Connect(callback)
local function wrap<T>(remote: RemoteEvent, desc: remote_desc<T>): state_type<T>
local changed_event = Instance.new("BindableEvent")

local function changed(callback: () -> ()): RBXScriptConnection
return changed_event.Event:Connect(callback)
end

if is_client then
local self = { changed = listen_changed, client = desc.default or {} }
remote_index.on(desc.id, function(buf: buffer, unknowns: { any })
sync.apply(self.client, buf, remote_index.client_pool, unknowns)
event:Fire()
local value = if desc.default ~= nil then diff.snapshot(desc.default) else {} :: any

local client = {
value = value,

get = function(): T
return value
end,

changed = function(callback: (T, T?) -> ()): RBXScriptConnection
return changed_event.Event:Connect(callback)
end,
}

remote_index.on(desc.id, function(buf: buffer, unknowns: { any }?)
local patch = sync.apply(value, buf, remote_index.client_pool, unknowns)

changed_event:Fire(patch, value)
end)
return self :: any

return table.freeze({
client = table.freeze(client),

changed = changed,
step = function(_player: Player?) end,

server = {} :: { [Player]: T },

codec = desc.codec,
}) :: any
end

local function send(player: Player, packet)
remote_index.fire(desc.id, player, packet.buf, packet.unknowns)
event:Fire()
changed_event:Fire()
end

local syncer = sync.create {
default = diff.snapshot(desc.default),
pools = server_pools,
send = send,
}

local function step(player: Player?)
sync.step(syncer, player)
end

table.insert(registry, syncer)
return table.freeze { changed = listen_changed, step = step, server = syncer.targets } :: any

return table.freeze {
client = table.freeze({
value = if desc.default ~= nil then diff.snapshot(desc.default) else {} :: any,

get = function(): T
return if desc.default ~= nil then diff.snapshot(desc.default) else {} :: any
end,

changed = function(callback: (T, T?) -> ()): RBXScriptConnection
return changed_event.Event:Connect(callback)
end,
}),

changed = changed,
step = step,

server = syncer.targets,

codec = desc.codec,
} :: any
end

local function define<T>(default: T?): state_type<T>
return { rbxclass = 'BindableEvent', wrap = wrap, codec = diff_codec.impl, default = default, single = true } :: any
return {
rbxclass = "RemoteEvent",
wrap = wrap,
codec = diff_codec.impl,
default = default,
single = true,
} :: any
end

-- functions
local function step_sync(player: Player?)
for _, s in registry do
sync.step(s, player)
for _, syncer in registry do
sync.step(syncer, player)
end
end

-- @server init
if not is_client then
Players.PlayerAdded:Connect(function(player: Player)
server_pools[player] = codec.create_pool()
end)

for _, player in Players:GetPlayers() do
server_pools[player] = codec.create_pool()
end

Players.PlayerRemoving:Connect(function(player: Player)
server_pools[player] = nil
for _, s in registry do
sync.remove(s, player)

for _, syncer in registry do
sync.remove(syncer, player)
end
end)

RunService.Heartbeat:Connect(function()
step_sync()
end)
end

-- module
return table.freeze {
step_sync = step_sync,
define = define,
wrap = wrap,
}
}
Loading
Loading