Replicating Player Data to All Clients with Replica

Hello,

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).

1 Like

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.

1 Like

Server Controller -

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).

1 Like

Nice to see you back mr. know it all. Unfortunately for you, Replica is more capable than a RemoteEvent.

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 :slight_smile:

I don’t think he knows what a wrapper is.

1 Like

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.

runtime > fancy abstractions

Ironically, you just got owned by your own side.

Shoutout to @Microwave_Toothpaste for pointing out exactly where your argument collapsed.

what? i said that as a joke about what you know since you’re crazy about micro optimizations and such

3 Likes

You vill micro optimize und you vill be happy. Ha HAAHAHAHHAHA

1 Like