Replicate your states with ReplicaService! (Networking system)

Sometimes replica service says this:
image

I found the line:

image

Is there any particular reason why replication gets skipped if I do this in the WriteLib?

	RemoveItem = function(replica: ProfileReplica, index: number): ()
		replica:ArrayRemove({"Items"}, index)

		task.spawn(function()
			if runService:IsServer() then
					local playerConfig = dataManager.GetPlayerConfig(replica.Tags.Player) -- this is a secondary replica

					if not playerConfig then -- player config has to be loaded
						return
					end
					
					playerConfig:ArrayRemove({"ItemData"}, index) -- this works on the server, but doesnt replicate to client :(
				end)
			end
		end)
	end

This DOES work however if I wrap the second ArrayRemove in task.defer. Why is this the case? And even if it does work, is it safe to do?

I’ve noticed I get these errors on the client side only when the client joins the game. The game still runs fine after this but I can’t seem to figure out what’s causing it.

Anyone have any idea?

P.S. I got like 4 Replicas that load on the client when they join the game. Maybe it’s intially an overload?

WriteLib functions shouldn’t yield internally as ReplicaService raises an internal flag that lets ReplicaService know that any mutators called before a WriteLib function ends are part of that WriteLib function and should not be individually replicated.

Calling mutators client-side should throw an error while the flag is not raised.

1 Like

Is there any recommended practice for selectively replicating objects that ‘should’ exist as sub-replicas? For example, if I have a PlayerData object, I might want it to contain:

  • PlayerData
    • Player Inventory
      • Player Equipped Tracker (PET)
      • Player Item Manager (PIM)
    • Player Settings

To my mind, it makes sense that for each player in the game, the contents of PET should replicate to all players. A quick example of this is if we have an ‘Inspect Player’ UI. This UI benefits from being able to directly query the other player’s PD → Inventory → PET :GetEquippedItems().

However, the contents of a player’s PIM are ideally going to be hidden. It doesn’t seem like good practice to replicate every player’s entire inventory (especially when, for argument’s sake, we’ll say we allow hundreds of items to be stored per player).

I’ve muddled over this for some time but I’ve not come up with an elegant solution. My best bet on a good approach is to not actually nest these objects (and their corresponding Replicas) explicitly. Instead, they all act as top level replicas with their own replication settings but references to each are still kept by this PlayerData class.

I’d appreciate any insight into this topic.

Many thanks,

Zairky

In my case I’ve split playerdata into 2 different replicas. “privateReplica” which only replicates to that player and “publicReplica” which replicates to everybody. I also have other data that has no replication inside playerdata just used for server purposes. You definelty dont want to replicate every players inventory to everyone if they dont need to know. For inspecting players, I would use a remote function that would fetch nesssecary data on inspect

This makes sense to me as the way to go about this type of problem. I would prefer to have child Replicas follow their parent’s replication by default but be overrideable. Still, in the absence of such a feature, your solution seems to be the best approach.

Hey, I’ve encountered a weird problem.

function Manager:FolderInsert(player, folder, key) -- performs table.insert(folder, key)
	local profile = self:GetProfile(player)
	local replica = Replicas[player]	
	
	if profile and replica then	
		table.insert(profile.Data[folder], key)
		replica:ArrayInsert({folder}, key)
	end
end

In my DataManager module I used ArrayInsert to update the replica when I inserted an element into a regular table. I don’t know if it’s intended behavior, but ArrayInsert also inserted the same element into the profile’s table resulting in two identical elements being in the table. Anyone had this problem?

1 Like

How is the replica created?
Might be because both the replica and the profile use the same table reference.

Lua can behave a bit strange with tables and pass by reference/pass by value…

Yea it is exactly what you said here, I managed to change some table logic stuff in my code and it works now.

1 Like

I think it would be worth adding :GetReplicas() as a supported function to ReplicaService and potentially ReplicaController, although the latter I’m not sure about. I think the use case is narrow but important.

When trying to find data leaks, being able to inspect what Replicas are still active is really useful. It’s easy enough to think we’ve cleaned up objects or otherwise but it’s helpful nonetheless to actually inspect and verify cleanup methods work.

I wrote this inspection function:

local function displayParties()
	local PartyHandler = Store:GetPartyHandler()
	local parties = PartyHandler:GetParties()
	print("--\\ All Parties //--")
	for _,party in pairs(parties) do
		party:Identify()
	end
	print("_Inspecting Replicas_")
	for _,replica in pairs(ReplicaService._replicas) do
		if replica.Class == CU.ReplicaTokens.Party.Party then
			print(replica:Identify())
		end
	end
	print("----- Complete -----")
end

To ensure all the data from parties was being adequately wiped. It’s obviously not recommended procedure to inspect a declared private variable. Given this is only for debugging, I understand it might be better to not give users access through an API as it might wrongly suggest they should use this to edit Replicas.

Is there a way to get if a replica by a name already exists on the server, i have a replica used for a tycoon, and a replica is created at tycoon creation, stuck between having to delete and recreate it each time to tycoon is remade (each rebirth), or try and find old and then continue using that one

With profile service, is it customary to utilize one replica for each player profile (as demonstrated in the example), multiple non nested replicas (inventory data, other table data), or are multiple nested replicas to reconstruct a player profile typically used?

1 Like

Question Loleris if you don’t mind. I am currently having an issue with my datastore, I call datatable very often to set my player’s kills, deaths and gold, but sometimes it does not update at all when using setvalue with replicaservice and sometimes it does, but never yields any errors whatsoever. This would be a big help to me if you could give me any insight in the right direction, and I can provide images or examples of what is going on if need be.

How can I check when a replica is updated? ive tried ListenToChange but cant seem to get it working, my data is nested example:

['Stats'] = {
  test = 0,
  inventory = {},
}

how can i check when stats / inventory is changed?

-- Server
Data.Stats.test += 1
.Replica:SetValue({"Stats"}, Data.Stats)
-- Client
replica:ListenToChange({"Stats"}, function(new_value, old_value)
print(new_value) -- { test = 1, inventory = {} }
end)
1 Like

hi, sorry for my lack of knowledge of datastores. i am trying to get player to access data from the client but I don’t know how am I supposed to… is it even possible?

That’s… sort of the main purpose of this resource. It makes getting server states from the client very easy. A ‘state’ could also mean the player’s data! You can find more information on the resource for ProfileService.

Is there a way I could use this as a replacement for remote functions?