Replicate your states with ReplicaService! (Networking system)

Is there anyway to set values to nil in bulk? Since the SetValues() method does not work with nil values I’m unsure how to go about removing a lot of items at once in my inventory system.

If you’re clearing large datasets consistently, consider turning them into nested replicas (replicas parented to other replicas) and create / destroy those replicas instead of editing one replica table.

If that’s not exactly what you’re looking for, then you might’ve overlooked WriteLib (Replica:Write()) functionality which has more power over built-in mutators (Replica:SetValue()).

2 Likes

I changed my inventory slots dictionary into an array instead so I can access the array mutators/listeners and it’s working great so far.

I have 2 questions:

  1. Is there a particular reason why the Replication and Parent parameters need to be mutually exclusive on a Replica?

  2. Could a ListenToChildRemoved function be potentially added to the ReplicaController?

My current use case where 1. is problematic involves having inventory Replicas parented to character Replicas. I would like an inventory Replica to be able to be parented to a character Replica while maintaining potentially different replication settings from the character Replica. The reason for this is that while I would like some data about a character to be public (replicated) to all players, such as their appearance or what item they are holding, I would like the data about the character’s inventory to be private only to the player who controls that character. In the future, I could also foresee modifying the replication settings of an inventory Replica to multiple players to facilitate trading (while still being parented to the character Replica).

The major benefit that would come out of allowing separate replication settings for child Replicas in the above case would be that the client no longer has to keep track of both character Replicas and inventory Replicas via ReplicaController.ReplicaOfClassCreated at the same time; the client could now just keep track of character Replicas alone and check whether an inventory Replica has been added to it or not. This benefit is also why I ask about 2. We currently have a way of tracking whether a child Replica has been added via Replica.ListenToChildAdded, but no way of tracking when it has been removed.

My current solution is to have each character Replica contain a Data field for the Replica Id of its corresponding inventory Replica, so that on the clientside a separate storage table of known inventory Replicas may be indexed to see if that Id has been replicated for the local client. What I don’t really like about this approach is the dependency on an external static map for clients to associate inventories with corresponding characters (or even other entities, like chests), when it seems natural that an inventory should be replicated together with whatever it’s parented to (while still maintaining separate replication settings).

I can see using WriteLib as another potential solution to my problem, but then it would lead to necessarily dealing with inventories being represented as deep nested tables within the Data field of my character Replicas, which doesn’t really seem ideal either.

Thank you for reading, and sorry for the word wall! I really appreciate you for making ReplicaService (and ProfileService!). I have been using ReplicaService for almost half a year now, and it’s been super super helpful in letting me abstract away most of the networking logic I needed to previously painstakingly deal with by hand!

  1. There aren’t that many scenarios where individual replication settings for nested replicas would win a significant amount of saved resources - Just replicate user-related state to everyone! You’re gonna save yourself from unnecessary work.

  2. Child removal signals might be added in the future

[08/26/2021]

Fixed :SetValues() built-in mutator not triggering :ListenToChange() listeners properly

Updated the modules with the task library

Updated signal module with GoodSignal coroutine recycling / Added yield-safety to the maid module.

IMPORTANT:

Moved the RateLimiter.lua module from src/ServerScriptService to src/ReplicatedStorage - When you’re updating the ReplicaService module, you’ll have to make sure the rate limiter module is in the right place. You should get proper warnings notifying about the missing module.

Update ReplicaService and ReplicaController modules from GitHub or Roblox library!

3 Likes

Ok so I am confused a little. I understand that the Instance you want to replicate to players has to be in a replicated container. So when I am replicating something I need to clone the instance and then pass that as a replica if I am understanding correctly. (Replicating completely works for me by the way). I just want to know how is this better for performance using this module rather than just using a Remote to tell clients to load in that map from ReplicatedStorage, and then hooking a maid to it?

How I am using your Replica for a map:

-- Server
local map_model = ReplicatedStorage.Map:Clone() -- If I don't clone when I destroy it completely destroys the map instance.

local Replica = ReplicaService.NewReplica({
    ClassToken = MapClassToken,
    Tags = { Model = map_model },
    Replication = "All",
})
Replica:AddCleanupTask(map_model)

-- Client
ReplicaController.RequestData() -- Initialize

ReplicaController.ReplicaOfClassCreated(MapClassToken, function(replica)
        replica.Tags.Model.Parent = workspace -- load that cloned replicated map into workspace
    end)

Is this a proper method of replicating an Instance?

1 Like

Yes it is the proper way of sending the client a reference to an instance using ReplicaService. However it would not be compatible with StreamingEnabled - the client might receive nil instead of the instance reference.

The primary superpower of ReplicaService is state replication by mutation - clients receive only changes to a state instead of receiving the whole updated state every time. Besides that, ReplicaService manages automatic replication of public replicas (Replicating to “All”) whenever a player joins and provides chronological guarantees by replicating replicas in the order they were created server-side - even for newly joined players!

Simple priblems can obviously be solved with just RemoteEvents - that’s what ReplicaService is built upon anyways. ReplicaService just “extends” RemoteEvents to create long-lasting state similar to Roblox Instances.

2 Likes

Oh ok that clears it up for sure. Thank you! So in my code when I am choosing a map. I create a new replica each time. Should I be doing that or should I be doing something more along the lines of:

self.Replica:SetValue({ "Map" }, map_instance)

If your game supports multiple maps, you would use multiple replicas. If your game supports one map you use one replica. Same pattern for the rest of replica implementations.

1 Like

I am really confused about how to use this alongside my ProfileService data system.
The only thing I am attempting to perform is to stream values from the player’s profile. Which includes a table, and a couple of number values.
I am very keen on having constant loops running on the server/client, so what is the best way to have checks or things that are alike

Calling the value directly as a remote sorta thing
or a .Changed type of thing so everytime a change occurs in a value it is replicated??

Overall I find ReplicaService very confusing, any help?

Hi
I want to use ReplicaService together with ProfileService to display a specific player’s stats on a GUI. The player can select which player’s stats they want to see, so ideally I would want to only connect to one player’s stats and then disconnect when a new player is selected, is this possible?

EDIT: nvm, I found out you can simply :Disconnect() the connection.

I’ve been really struggling with how to use ReplicaService alongside my current ProfileService set-up.
Is anyone able to help tutor or teach me how to use this within my game? If you want to contact or help me, send me a message. Thanks

I made a tutorial for this!

What are the performance difference of having one Replica with a lot of nested tables vs a Replica for each table?

This is kind of like the less-or-more-remote-events question.

What I’m thinking is it will save a little on network to have more Replicas (that way you don’t have to be so specific when replicating the path), but maybe takes up more resources/memory? Or maybe there’s more overhead when creating the Replicas?

What I would really like is a better understanding on how the computer and memory works in regards to creating objects.

Sorry if my question is unclear.

For the most part you’ll have to test the impact yourself. Theoretically, since replicas are passive, the performance impact is the same regardless of replica count and will only depend on the mutators used and parameters passed.

Custom “Write” mutators are indexed by integers and can have small network footprints for large state changes.

You should not neglect the convenience of built-in mutators for non ultra massive implementations. Built-in mutators have to replicate the value path every time they’re called, but in most cases this impact is negligible.

1 Like

So beside for the increase in network performance when using more replicas (or custom mutators) is creating more replicas going to use up more memory, or is it such a small amount I shouldn’t worry about the memory usage of 1 passive replica vs 100 passive replicas?

Theoretically as in the more replicas = more memory used up but the difference is negligible that it shouldn’t be thought about?

You can use thousands of replicas without noticing much impact in your memory.

2 Likes

When I try ReplicaService on a live server and leave and connect again I get the error: Token for replica class “replica name here” was already created.

Replication works as expected in Game and I destroy the replica when the profile is released.

profile:ListenToRelease(function()				
	PLAYER_REPLICA[player]:Destroy()
	PLAYER_REPLICA[player] = nil
		
	Profiles[player] = nil
	player:Kick()
end)

I create my replica like this:

PLAYER_REPLICA[player] = ReplicaService.NewReplica({
	ClassToken = ReplicaService.NewClassToken("PlayerReplica_"..player.UserId),
	Data = profile.Data,
	Replication = player,
})

I can see that ListenToRelease fire when I use a Print there.
Any help on this issue would be greatly appreciated.

1 Like

@RobloxWobot @loleris

It seems like the backend of the ReplicaService is causing the Replica:Destroy() function to not properly removing the Replica from the CreatedClassTokens table. I found a fix which is going to the DestroyReplicaAndDescendantsRecursive function or line 373 in the ReplicaService module script and I added this line of code which solves the issue of erroring that this class token has been created upon calling ReplicaService.NewReplica() with the same name for the Replica ClassToken, and destroying the replica and creating the same replica with the same ClassToken name.

This is the forked version of the DestroyReplicaAndDescendantsRecursive function:

local function DestroyReplicaAndDescendantsRecursive(replica, not_first_in_stack)
	-- Scan children replicas:
	for _, child in ipairs(replica.Children) do
		DestroyReplicaAndDescendantsRecursive(child, true)
	end

	local id = replica.Id
	-- Clear replica entry:
	Replicas[id] = nil
	-- Cleanup:
	replica._maid:Cleanup()
	-- Remove _creation_data entry:
	replica._creation_data[tostring(id)] = nil
	-- Clear from children table of top parent replica:
	if not_first_in_stack ~= true then -- ehhhh... Yeah.
		if replica.Parent ~= nil then
			local children = replica.Parent.Children
			table.remove(children, table.find(children, replica))
		else
			TopLevelReplicas[id] = nil
		end
	end
	CreatedClassTokens[replica.Class] = nil	
	-- Swap metatables:
	setmetatable(replica, LockReplicaMethods)
end

Only difference is this line:

CreatedClassTokens[replica.Class] = nil	

Hope this fixes an issue.

3 Likes