Replicate your states with ReplicaService! (Networking system)

In your game The Mad Murderer X how are you handling map replication? I’m assuming you are using ReplicaService.

Replicas don’t replace instances but can be used to complement them.

1 Like

Oh. How are you complementing them? Are the maps still being loaded from ServerStorage, and only certain components are Replicated such as Lighting properties, etc.?

Rigging dynamic state to certain parts of the map i.e. doors, displays, buttons, etc.

3 Likes

Oh, makes sense. For some reason I thought the entire map gets replicated, but I see it’s just little interactables almost. With that being said the doors and etc. did you Replicate them to reduce server load for animating them opening and etc.?

Hi loleris,

Is there any event triggered to the client when a Replica is destroyed? This is my only current solution and I’m not sure if it’s good for production. Also note that this is scramble code.

ReplicaController.ReplicaOfClassCreated("MarkerPositions", function(replica)
	local marker = createMarker(replica.Tags.Player)
	replica:ListenToChange({"Location"}, function(new_value)
		marker.Position = new_value
	end)

	-- Current Solution
	game.ReplicatedStorage.ReplicaRemoteEvents.Replica_ReplicaDestroy.OnClientEvent:Connect(function(replica_id)
		if replica_id == replica.Id then
			marker:Destroy()
		end
	end)

end)

Thank you! :smiley:

See Replica:AddCleanupTask()

1 Like

:man_facepalming: For some reason I thought that that function was only for the server. Thank you!

Hey, looks great!
Just want to understand what is the benefit of using the Write method with WriteLib instead of just SetValue/s without WriteLib? @loleris

Instead of sending a whole table path with :SetValue() or other built-in mutators Write() will only send the serialized id representing the function name in a WriteLib module following the function arguments - there are scenarios where this could help achieve much greater network efficiency when altering state (e.g altering 100’s of values with one function call), though in most cases there’s always a workaround to make it relatively efficient with built-in mutators as well.

WriteLibs can help create efficient state that is more unpacked / unserialized as soon as it’s replicated because in a way a WriteLib is a serialization / packing tool that can lump multiple mutations together.

Still, I actually recommend relying on built-in mutators more and make mostly 1-dimensional state (no deep tables).

3 Likes

Gotcha!
Many thanks for the detailed explanation.
I have integrated your modules into my project and so far it is great.
Keep up the great work! :slight_smile:

Could this be used for cross-server networking? Ie, global events?

Hey, @loleris
Great job with the service.
Could you elaborate on the use-cases of parenting replicas to other replicas?
In what cases should I use this feature?
What is the difference between using 2 unrelated Replicas to using nested Replicas(Parent->Child)?

Many thanks!

I came across a concern. I spent a while trying to figure out why my listener for ListenToChange wasn’t firing. Eventually I realized I had 2 listeners for the same path, one in two scripts on the client, and I disconnected the other one expecting them to be two different connections.

Apparently ReplicaService shares the same connection for the same path, regardless of the form, at least that’s how I interpret it. I’m pretty clueless as to how this works internally. The docs state that I shouldn’t need to disconnect individual connections since it’ll be handled when the Replica is destroyed, but in my case this player data replica stays alive until the player leaves.

I create a new ListenToChange listener for each time an ‘item’ is added in my player data table. This item can be removed and obtained again by the same player infinitely. Currently, I have to make sure that I add extra code so it doesn’t stack listeners and only go by the first one. This would get trickier if I needed to actually access something only in the scope of PerKey and make sure I always access the latest one.

local ReplicaReceiver = require(ReplicatedStorage.Modules.Client.ReplicaReceiver)
local DataReplica = ReplicaReceiver:Get("PlayerData", Player)

local ListenersMade = {}

local function PerKey(Key)
	if not ListenersMade[Key] then
		ListenersMade[Key] = true
		
		DataReplica:ListenToChange({"Items", "Ammunition", Key}, function(NewValue)
			if NewValue == nil then
				if Slots[Key] then
					Slots[Key].SlotObject:Destroy()
					Slots[Key] = nil
				end
			end
		end)
	end
end

DataReplica:ListenToNewKey({"Items", "Ammunition"}, function(Value, Key)
	SortSlots()
	PerKey(Key)
end)

for Key in pairs(DataReplica.Data.Items.Ammunition) do
	PerKey(Key)
end

Is there any chance this future rewrite could have separate connections that I can disconnect, like how regular RBXScriptConnections work? Or is there something I don’t quite understand about how this works? I use these server-sided a lot as well (wouldn’t be the same without them for my use case), and it would be horrid if I ran into server memory leak issues (unless if this won’t actually cause memory leak problems, but instead stacking as a result)?

Thank you, hopefully this could be cleared up on how it actually works and if there’s any preferred way to go about solving this without throwing in a solution like this every time.

5 Likes

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?