Replicate your states with ReplicaService! (Networking system)

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.

how do i change the replica data client side?

local CharacterReplicaClassToken = ReplicaService.NewClassToken("Character")

local player -- A Player instance
local character -- A Model instance
local replica = ReplicaService.NewReplica({
  ClassToken = CharacterReplicaClassToken,
  Tags = {Player = player, Character = character, Appearance = "Ninja"},
  Replication = "All",
})

https://madstudioroblox.github.io/ReplicaService/api/#replicatags

Replication - (Default: {} not replicated to anyone) Pass "All" to replicate to everyone in the game and everyone who will join the game later. Pass {Player = true, Player = true, ...} dictionary or Player instance for selective replication.
https://madstudioroblox.github.io/ReplicaService/api/#replicaservicenewreplica

Methods [ScriptConnection]:

		ScriptConnection:Disconnect() -- Disconnect listener from signal

Treat it like any other lua connection.

local NewKeySignal = Replica:ListenToNewKey(path, listener)
NewKeySignal:Disconnect()

bruh moment

1 Like

yes bruh moment

3000 thousand characters

1 Like

all in good fun :stuck_out_tongue_closed_eyes:

i’ve been trying to implement this along with profileservice for a good week now and i feel like i’m only now getting the hang of it

it doesn’t help that there’s not a lot of case examples from the community using this in specific scenarios like there is with profileservice

@loleris hasn’t replied to this thread for about a year now, either because he’s busy or the replies aren’t worth addressing, so maybe we can try to find answers together :smiley:

2 Likes

Yes, I actually already figured it out. It was a kind of hacky way, though. This is better.

No it isn’t. Bytecode doesn’t get deallocated in this context and thus is retrievable by exploiters and technically anybody on Windows via task manager memory dump.

3 Likes