Replicate your states with ReplicaService! (Networking system)

Late, but I can’t get it to work at all and I have the same issue. If I use SetValue on Data (which I bound to a profile in ProfileService) it simply doesn’t propagate the ListenToChange. Moreover, it actually changes the ProfileService data… ReplicaOfClassCreated is firing properly however.

It seems that there is no support for remote function-like behavior in ReplicaService. Would this be because the server is able to provide live, updated information to client(s) without them needing to request it, therefore making remote functions obsolete? Just curious.

Hey! This looks super cool, but I have a few questions.

  • Can the client only read the data, or can it write as well?
  • Is there an example of using this with profileservice?

This seems epic, might use it on future projects.

Hey,
Thanks for the Awesome module.

Im trying to listen to changes on the Replica in my server script.

If i do :ListenToRaw my listener gets fired, but when i do :ListenToChange it wont get fired.

My guess is that my path is wrong. Listening on the Server Side seems to work cause :ListenToRaw works so i guess i required the ReplicaServiceListeners module correctly.
I get

23:10:13.397  SetValue  ▼  {
                    [1] = "Taps"
                 } 42  -  Server - DataManager:46

as output on the :ListenToRaw.

My listeners are

DataReplica:ListenToRaw(function(action_name, path_array, params)
                print(action_name, path_array, params)
            end)
            DataReplica:ListenToChange({"Taps"},function(new_value, old_value)
                print(new_value, old_value)
            end)

is my path incorrect? i tried “{Taps}” and {“Taps”} and {[1] = “Taps”} as the path, but none worked

Some questions about this.

Im already using ProfileService, and replicating the data with ValueBases. This is pretty annoying since I have both the profile data AND the values to worry about, so I was looking into this module.

ReplicaService doesnt create deep copies of tables, so I was wondering if you NEED to change data like in the example?

function PlayerProfile:GiveCash(cash_amount)
	if self:IsActive() == false then
		return
	end
	self.Replica:SetValue({"Cash"}, self.Replica.Data.Cash + cash_amount)
end

Would it be okay to do it like this instead?

function PlayerProfile:GiveCash(cash_amount)
	if self:IsActive() == false then
		return
	end
	self.Profile.Data.Cash += cash_amount
end

EDIT: It seems like this does work, but listening to changes does not.

Which way of checking data on the server would be advised? Checking Profile.Data or Replica.Data? I know its technically the same thing, but is there a different performance cost to either, or something?

If we cant yield in listener functions, would using task.spawn with a yielding function in the listener functions work?

And also, would replicating a big table of data be an issue? Or only editing large amounts of data at once?

Simple answer: Don’t use this module.

??? lmao what do you mean?? Why not?

1 Like

There you go, why you don’t have to use RemoteEvents and ReplicaService instead.

1 Like

Hi, I’ve been using ReplicaService for a while and I think it’s an amazing module.

I’ve been wondering if there’s a recommended way to go about representing the same object on both the client and the server? I’ve historically just made a separate class for each, e.g. PartyServer and PartyClient.

Each PartyServer has its own replica and that replica being received by the client leads to the creation of a PartyClient. It’s manageable, but it feels like a lot of code repetition. In my mind, it’s somewhat justifiable - after all, the function of the PartyClient is really only to forward changes in a readable way (Party.PartyMemberAdded is easier to read than Party.Replica:ListenToArrayInsert …).

Still, it’s preferable that the interface of PartyServer is at least partially reflected in the PartyClient. PartyServer has setter methods, but all getters, signals, etc should be the same (I think…). Is there a way to go around solving this problem that you’d recommend?

Many thanks,

Zairky

You could create a shared class for all the shared methods and then create whatever client/server specific code you need by checking RunService:IsClient() or RunService:IsServer() respectively.

Thanks for the quick reply!

I’ve trialed this in my code before and I feel there’s a messy pattern of continually having:

if IS_SERVER then
    -- Server sided stuff
else
    -- Client sided stuff
end

littered all around the code. I think this makes code harder to read, but that’s entirely down to personal preference and not a fault of the approach. However, I also imagine it isn’t good practice to expose the server-sided methods to the client (as it allows insight into server-sided code vulnerabilities which are otherwise hidden).

Still, I’m unsure what’s recommended as standard practice - if anything is at all. I have a suspicion that there’s a way to take advantage of the nature of the relationships between the server and client sided objects, but I’m waiting for someone smarter than me to tell me what that is!

Yeah, it’s definitely a situation that’s up to you on how to approach it. One approach that I like is how Knit initializes its framework by deleting the server-side code from the client on startup so its removed from the bytecode entirely on their end. You do end up writing separate classes still but at least you only require a single file with reflected API.

image

2 Likes

Im experiencing an issue where a server replica does not get received by the client.
Both the client and server uses modules, also using ProfileService.

Server code:

local function playerAdded(player)
	local profile = profileStore:LoadProfileAsync("Player_"..player.UserId) -- get profile

	if profile ~= nil then
		profile:AddUserId(player.UserId) -- GDPR

		profile:Reconcile() -- add missing stuff

		profile:ListenToRelease(function() -- loaded on another server
			profiles[player].Replica:Destroy()
			profiles[player] = nil

			player:Kick("Your data might have been loaded on another server, please rejoin")
		end)

		if player:IsDescendantOf(players) == true then -- loaded and player didnt leave
			HandleData(player, profile) -- this just changes profile.Data before its sent to the client

			-- create profile object with replica
			local profileObject = {
				Profile = profile,
				Replica = replicaService.NewReplica({
					ClassToken = profileToken,
					Tags = {Player = player},
					Data = profile.Data,
					Replication = player,
				}),
				Player = player,
			}

			profiles[player] = profileObject
		else -- left before loaded
			profile:Release()
		end
	else -- could not load
		player:Kick("Could not load data, please rejoin")
	end
end

Client code:

local dataUtil = {}

local plr = game.Players.LocalPlayer

print(0)
local controller = require(game.ReplicatedStorage.UtilityModules.ReplicaService.ReplicaController)
print(.5)

controller.ReplicaOfClassCreated("PlayerProfile", function(replica)
	print(3)
	if replica.Tags.Player == plr then
		print("Data has been received!")
	end
end)

print(1)
controller.RequestData()
print(2)

return dataUtil

You can see I already added debug prints to the client side, and 0, 0.5, 1 and 2 get printed, but 3 does not. ([ReplicaController]: Initial data received does not get printed either)

What am I doing wrong here?

I tried making a repro for the issue but was unsuccessful.

EDIT: Looks like this was caused by mistakenly adding a ReplicaRemoteEvents folder into replicatedStorage beforehand, causing the module to make a duplicate. Whoops

1 Like

Hey, I’m unsure as to how I would create a centralized module that connects Replica creation listeners. I want to get data and listen to data changes at any time and from any LocalScript. If anyone has any guidance on this topic, please let me know :grinning:

Is it possible to replicate instances?

Is there anyway to set the value for only a player or a group of players? Or to stop listening for changes?

You serialize it? You must have basic data storing knowledge before getting into this type of stuff.

I have basic data storing knowledge. I’m not just walking around with my head cut off, buddy.

I’m asking a simple question. Is there a way or function in this module, because I have already tried to find it in the API, and have failed.

2 Likes

Well, it would be sort of useless to include if serializers already exist.