Created
February 17, 2023 18:17
-
-
Save MrChickenRocket/e8edef3f4cbefada233d2f7b22ed19bd to your computer and use it in GitHub Desktop.
Knit styled replicated table service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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!)