refactor!: design overhaul (#1)

* refactor!: use atom functions

* docs: reformat api reference

* docs: showcase atom functions

* docs: remove unused import

* refactor: finalize updates

* chore: misc edits
This commit is contained in:
richard 2024-03-25 19:53:28 -07:00 • committed by GitHub
parent 4d4979daa9
commit 33ecae2f50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 577 additions and 729 deletions

View file

@ -1,26 +1,26 @@
local atom = require(script.Parent.Parent.atom)
type Atom<T> = atom.Atom<T>
local types = require(script.Parent.Parent.types)
type Atom<T> = types.Atom<T>
type SyncPayload = types.SyncPayload
local patch = require(script.Parent.patch)
local types = require(script.Parent.types)
type ClientSyncOptions = {
type ClientOptions = {
atoms: { [string]: Atom<any> },
}
type ClientSyncer = {
sync: (self: ClientSyncer, payload: types.SyncPayload) -> (),
sync: (self: ClientSyncer, payload: SyncPayload) -> (),
}
local function client(options: ClientSyncOptions): ClientSyncer
local function client(options: ClientOptions): ClientSyncer
local atoms = options.atoms
local self = {} :: ClientSyncer
function self:sync(payload)
for key, state in payload.data do
if payload.type == "patch" then
atoms[key]:set(patch.apply(atoms[key]:get(), state))
atoms[key](patch.apply(atoms[key](), state))
else
atoms[key]:set(state)
atoms[key](state)
end
end
end

View file

@ -1,8 +1,5 @@
local client = require(script.client)
local server = require(script.server)
local types = require(script.types)
export type SyncPayload = types.SyncPayload
return {
client = client,

View file

@ -2,43 +2,48 @@ local validate = require(script.Parent.validate)
local NONE = { __none = "__none" }
local function diff(previousState: { [any]: any }, nextState: { [any]: any })
local patch = table.clone(nextState)
local warnings = {}
for key, previous in previousState do
local function diff(prevState: { [any]: any }, nextState: { [any]: any })
local patches = table.clone(nextState)
for key, previous in prevState do
local next = nextState[key]
if previous == next then
patch[key] = nil
patches[key] = nil
elseif next == nil then
patch[key] = NONE
patches[key] = NONE
elseif type(previous) == "table" and type(next) == "table" then
patch[key] = diff(previous, next)
patches[key] = diff(previous, next)
end
end
if _G.__DEV__ then
local success, result = pcall(function()
for key, value in previousState do
for key, value in prevState do
validate(value, key)
end
for key, value in nextState do
validate(value, key)
if prevState[key] ~= value then
validate(value, key)
end
end
end)
if not success then
if not success and not warnings[result] then
warnings[result] = true
warn(result)
end
end
return patch
return patches
end
local function apply(state: { [any]: any }, patchState: { [any]: any })
local function apply(state: { [any]: any }, patches: { [any]: any })
local nextState = table.clone(state)
for key, patch in patchState do
for key, patch in patches do
local current = nextState[key]
if type(patch) == "table" and patch.__none == "__none" then

View file

@ -1,69 +1,69 @@
local Players = game:GetService("Players")
local atom = require(script.Parent.Parent.atom)
type Atom<T> = atom.Atom<T>
local types = require(script.Parent.Parent.types)
type Atom<T> = types.Atom<T>
type SyncPayload = types.SyncPayload
local setInterval = require(script.Parent.Parent.utils.setInterval)
local patch = require(script.Parent.patch)
local types = require(script.Parent.types)
type ServerSyncOptions = {
type ServerOptions = {
atoms: { [string]: Atom<any> },
interval: number?,
}
type ServerSyncer = {
onSync: (self: ServerSyncer, callback: (player: Player, payload: types.SyncPayload) -> ()) -> () -> (),
flush: (self: ServerSyncer, player: Player?) -> (),
connect: (self: ServerSyncer, callback: (player: Player, payload: SyncPayload) -> ()) -> () -> (),
flush: (self: ServerSyncer, player: Player) -> (),
hydrate: (self: ServerSyncer, player: Player) -> (),
}
local function server(options: ServerSyncOptions): ServerSyncer
local function server(options: ServerOptions): ServerSyncer
local atoms = options.atoms
local interval = options.interval or 0
local self = {} :: ServerSyncer
local sync: (player: Player, payload: types.SyncPayload) -> ()
local sync: (player: Player, payload: SyncPayload) -> ()
local state: {}
local function getState()
local state = {}
for key, atom in atoms do
state[key] = atom:get()
state[key] = atom()
end
return state
end
function self:onSync(callback)
function self:connect(callback)
sync = callback
state = getState()
return setInterval(function()
self:flush()
local previous = state
state = getState()
if state ~= previous then
for _, player in Players:GetPlayers() do
callback(player, {
type = "patch",
data = patch.diff(previous, state),
})
end
end
end, interval)
end
function self:flush(player: Player?)
local current = getState()
local previous = state or current
state = current
function self:flush(player: Player)
local previous = state
state = getState()
if current ~= previous then
local payload: types.SyncPayload = {
type = "patch",
data = patch.diff(previous, current),
}
if player then
return sync(player, payload)
end
for _, player in Players:GetPlayers() do
sync(player, payload)
end
end
sync(player, {
type = "patch",
data = patch.diff(previous, state),
})
end
function self:hydrate(player)
assert(sync, "onSync must be called before onJoin")
assert(sync, "connect() must be called before hydrate()")
sync(player, {
type = "set",

View file

@ -1,6 +0,0 @@
export type SyncPayload = {
type: "patch" | "set",
data: { [any]: any },
}
return nil