Skip to content

Instantly share code, notes, and snippets.

@MrChickenRocket
Created February 17, 2023 18:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrChickenRocket/e8edef3f4cbefada233d2f7b22ed19bd to your computer and use it in GitHub Desktop.
Save MrChickenRocket/e8edef3f4cbefada233d2f7b22ed19bd to your computer and use it in GitHub Desktop.
Knit styled replicated table service
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages
local Shared = ReplicatedStorage.Shared
local TableUtil = require(Packages.TableUtil)
local DiffTable = {}
------------------------------------------------------------------------------------------------
-- Helpers
local function recursiveCall(old, new, var, count, res)
--its a table, recurse
-- predicate
if type(old[var]) ~= "table" then
count = count + 1
res[var] = new[var]
return res, count
end
local newtable, num = DiffTable:DiffTable(old[var], new[var])
if num > 0 then
count = count + 1
res[var] = newtable
end
return res, count
end
local function ifChangedSetCall(old, new, var, count, res)
local a = new[var]
local b = old[var]
if a ~= b then
count = count + 1
res[var] = a
end
return res, count
end
local function nilSanitize(old, new)
for var, data in pairs(old) do
-- "_" is our signal to delete
if new[var] == nil then
new[var] = "_"
continue
end
end
end
function DiffTable:DiffTable(old, new)
local res = {}
local count = 0
nilSanitize(old, new)
for var, data in pairs(new) do
-- Sanitize
if old[var] == nil then
res[var] = "nil"
end
if type(new[var]) == "table" then
res, count = recursiveCall(old, new, var, count, res)
else
res, count = ifChangedSetCall(old, new, var, count, res)
end
end
return res, count
end
function DiffTable:MergeTable(old, diffTable)
local newTable = TableUtil.Copy(old, true)
for var, data in pairs(diffTable) do
-- If we signal a deletion then here we go
if diffTable[var] == "_" then
newTable[var] = nil
continue
end
if type(diffTable[var]) == "table" then
if type(old[var]) ~= "table" then
newTable[var] = diffTable[var]
else
newTable[var] = DiffTable:MergeTable(old[var], diffTable[var])
end
else
newTable[var] = diffTable[var]
end
end
return newTable
end
return DiffTable
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages
local Shared = ReplicatedStorage.Shared
local Knit = require(Packages.Knit)
local TableUtil = require(Packages.TableUtil)
local DeltaTable = require(Shared.DeltaTable)
local ReplicationService
local PlayerService
local ReplicationController = Knit.CreateController({
Name = script.Name,
Tables = {},
})
function ReplicationController:KnitStart() end
function ReplicationController:KnitInit()
ReplicationService = Knit.GetService("ReplicationService")
PlayerService = Knit.GetService("PlayerService")
ReplicationService.Replicate:Connect(function(record)
if self.Tables[record.name] == nil then
self.Tables[record.name] = TableUtil.Copy(record.data, true)
else
self.Tables[record.name] = DeltaTable:MergeTable(self.Tables[record.name], record.data)
end
end)
PlayerService.ClientConnected:Fire()
end
return ReplicationController
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Packages = ReplicatedStorage.Packages
local Shared = ReplicatedStorage.Shared
local Knit = require(Packages.Knit)
local TableUtil = require(Packages.TableUtil)
local DeltaTable = require(Shared.DeltaTable)
local ReplicationService = Knit.CreateService({
Name = script.Name,
Tables = setmetatable({}, { __mode = "v" }),
Client = {
Replicate = Knit.CreateSignal(),
},
})
local function Map(sourceTable, fields)
local results = {}
results.fields = {}
results.numFields = 0
results.tables = {}
results.numTables = 0
for key, value in fields do
--see if it exists in the sourceTable
if sourceTable[key] == nil then
warn("Asked for a view of a field that doesnt exist:" .. key)
continue
end
if typeof(value) == "table" then
--Recurse
local res = Map(sourceTable[key], value)
results.tables[key] = res
results.numTables += 1
else
results.fields[key] = sourceTable
results.numFields += 1
end
end
return results
end
local function Copy(data)
local result = {}
for key, value in data.fields do
local srcTable = value
local srcKey = key
local srcValue = srcTable[srcKey]
result[srcKey] = srcValue
end
for key, value in data.tables do
result[key] = Copy(value)
end
return result
end
function ReplicationService:CreateReplicatedTable(tableName: string, sourceTable: {}, fields: {}, player: Player)
local record = {}
record.sourceTable = sourceTable
record.tableName = tableName
record.player = player
if fields ~= nil then
record.dataMapping = Map(sourceTable, fields)
else
record.dataMapping = nil
end
function record:Update()
local diff = nil
if self.previous == nil then
self.previous = {}
end
if self.dataMapping then
local result = Copy(record.dataMapping)
diff = DeltaTable:DiffTable(self.previous, result)
self.previous = TableUtil.Copy(result, true)
else
diff = DeltaTable:DiffTable(self.previous, self.sourceTable)
self.previous = TableUtil.Copy(self.sourceTable, true)
end
--has data?
if next(diff) ~= nil then
--send the diff
ReplicationService.Client.Replicate:Fire(player, { id = "tableDelta", name = record.tableName, data = diff })
end
end
table.insert(self.Tables, record)
--Replicate it right away
record:Update()
return record
end
function ReplicationService.Client:GetInitialTables(player: Player)
local tables = {}
for _, record in self.Server.Tables do
print("ALSAK", record, record.player)
if record.player == player then
local replicatedView = TableUtil.Sync(record.sourceTable, record.dataMapping)
tables[record.tableName] = replicatedView
end
end
return tables
end
function ReplicationService:KnitInit()
RunService.Heartbeat:Connect(function(dt)
for _, record in self.Tables do
record:Update()
end
end)
end
return ReplicationService
@MrChickenRocket
Copy link
Author

Usage:
--Server side
_replicator = ReplicationService:CreateReplicatedTable(tableName, serverTable, template, player)

This creates a mirrored table for player called 'tableName' that will have the contents of serverTable sent once per frame (delta compressed!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment