I have some player data that I want to replicate to all clients using Replica.
My first idea was to have a big table with a dictionary to sort each player’s player data, but I don’t think I can edit a replicated table’s structure after instantiating the Replica object, so that idea’s a bust.
My current idea is to have Replica objects with unique tokens for each player but this will require some extra work on the client-side that I’m not keen on starting yet, so I wanted to see if anyone who’s used this module before has any better ideas before I commit to it (it’s my first time using Replica).
Hmm.. well, ReplicaService does not let you change a Replica structure after creation. You must define all keys in the template when you call NewReplica.
A single Replica holding all players data will not work because you cannot add new root keys for players later.
Given that..
Create one Replica for each player at join. Give it a schema with all the keys you plan to store. Store them in a container table keyed by UserId. On the client, listen for new Replicas in that container. Use the UserId to fetch the Replica you need. That way you replicate all player data without schema mutation.
I don’t use ReplicaService myself, I ran over how it works and this seems to me to be your best shot.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Replica = require(...)
local Config = require(...)
local Controller = {}
Controller.__index = Controller
function Controller:setMoney(amount: number)
self.replica:Set({"money"}, amount)
end
function Controller:addMoney(amount: number)
self:setMoney(self:Data().money + amount)
end
function Controller:data()
return self.replica.Data
end
return {
new = function(userId: number,
stats: {[string]: any}?)
local self = {
replica = Replica.New {
Token = Replica.Token(tostring(userId)),
Data = stats or Config.DEFAULT_PLAYER_STATS
}
}
return setmetatable(self, Controller)
end
}
Client Handler -
local Players = game:GetService("Players")
local localPlayer = Players.LocalPlayer
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Replica = require(...)
local Config = require(...)
return {
Init = function()
local userId = localPlayer.UserId
Replica.OnNew(tostring(userId), function(replica)
replica:OnSet({"money"}, function(money)
print("money has changed")
end)
end)
end
}
Also, I would recommend creating a Manager module for the server and a Controller module for the client (It is needed to manage data in the same way as on the server, while the Handler will process new changes and call the Controller).
OK, I’m back. I went forward with the ‘creating multiple replicas for each player’ approach.
Server
Nothing really to discuss here.
local function startCharacterAppearanceReplication(player: Player): ()
local playerSlotData = PlayerDataSingleton:GetSlotData(player)
if not playerSlotData then
-- warn(`{script.Name} | Player data is loaded but cannot find it? I'm not replicating this bruh.`)
return
end
local playerCharacterAppearanceData = playerSlotData.CharacterAppearance
local newReplica = Replica.New({
Token = Replica.Token(`CharacterAppearance:{player.UserId}`),
Data = playerCharacterAppearanceData
})
Module.Replicas[player.UserId] = newReplica
newReplica:Replicate()
end
local function endCharacterAppearanceReplication(player: Player): ()
local replicaToDestroy: Replica.Replica = Module.Replicas[player.UserId]
if not replicaToDestroy then
return
end
replicaToDestroy:DontReplicate() -- Unknown if needed. Check later.
replicaToDestroy:Destroy()
end
Client
Setting up the Replica connections was easy enough.
Cleanup confused me at first, but after some research I’ve deduced that I only need to get rid of the connections, so I set up a Trove. Please tell me if I’ve misinterpretted the cleanup.
local function startListeningForPlayerReplication(player: Player): ()
local newConnectionsTrove = Trove.new()
Module.ConnectionsTroves[player.UserId] = newConnectionsTrove
newConnectionsTrove:Add(Replica.OnNew(REPLICA_KEY_PREFIX..player.UserId, function(replica: Replica.Replica)
updatePlayerCharacterAppearance(player, replica.Data)
-- Replica updates
newConnectionsTrove:Add(replica:OnSet({"Race"}, function(): ()
updatePlayerCharacterAppearance(player, replica.Data)
end))
newConnectionsTrove:Add(replica:OnSet({"RaceType"}, function(): ()
updatePlayerCharacterAppearance(player, replica.Data)
end))
newConnectionsTrove:Add(player.CharacterAdded:Connect(function(): ()
updatePlayerCharacterAppearance(player, replica.Data)
end))
end))
end
local function stopListeningForPlayerReplication(player: Player): ()
-- Cleanup connections.
local connectionsTrove = Module.ConnectionsTroves[player.UserId]
if connectionsTrove then
connectionsTrove:Destroy()
Module.ConnectionsTroves[player.UserId] = nil
end
end
Hmm, could you give me an example for this? I’m struggling to understand the use of a controller.
Edit: Forgot to reply to you, @xxxwectorxxx
If it’s not a compile time, then it’s a waste.
Hardware and users do not care how fancy your code looks.
All it cares about is for it to be optimal and run smoothly.
Also, you requiring clarification just shows that you are wrong to begin with.
Refusal to accept that optimization and decomplexification are the ultimate end goal is a refusal to evolve and adapt.