Replicate your states with ReplicaService! (Networking system)


Mad Studio cross-promo:

Check out ProfileService for DataStore management!
If you’re curious about how Madwork is written, give MadLife - code_guidebook a read!


Madwork - ReplicaService

ReplicaService on GitHub

ReplicaService is a selective state replication system. ReplicaService helps you make server code which changes and replicates any state to select clients.

It’s documented:
ReplicaService wiki

It’s open source:
Roblox library

It’s a perfect fit for ProfileService:
ReplicaService + ProfileService example included in the package

Assume that a state (Wikipedia) is any kind of data that has a present version and may also change at any time in the future, as many times as necessary. The data about a player which you load up during gameplay or save to the DataStore is a state. The color of a part, text shown on a users screen and furniture placed in a player owned house are all states - ReplicaService helps you make server-side code to control and replicate any state to all clients at once or only a select few.

A state (in layman’s terms, a lua table that may contain almost anything) is wrapped with a Replica - like the name implies, it creates a replica (identical copy) of the wrapped state on the client-side of users you want to see that state. You may define clients who will see that replica, call mutator functions on the Replica to change the state (will change contents of the wrapped table) and make the clients listen to those changes or simply read the state whenever necessary. Furthermore, a Replica can be parented to another Replica (with a few exceptions discussed later), unloaded for select clients and, of course, destroyed.

What’s good about ReplicaService:

  • Just replication, whatever you need replicated - The goal of ReplicaService is to streamline custom Roblox object replication from server to client. ReplicaService avoids being redundant and tackles as few concerns as possible.

  • Chunks & player houses - Selective replication allows you to make a “custom StreamingEnabled implementation” with full server-side control - load in nearby chunks, load in interiors and furniture only when the player enters those areas!

  • “It don’t go brrrrr” - ReplicaService is completely event-based and only tells the client the data that changes - it keeps the network usage low and conserves computer resources.

  • Go big, go small - Use custom mutators for minimal bandwith and gain access to client-side listeners that react to bulk changes instead of individual values. Use built-in mutators for rapid implementations while still keeping your network use very low.


ReplicaService is part of the Madwork framework
Developed by loleris

Minimal implementation example:

-- ( `Script` ReplicaTest.server.lua)
local ReplicaService = require(game.ServerScriptService.ReplicaService)

local test_replica = ReplicaService.NewReplica({
    ClassToken = ReplicaService.NewClassToken("TestReplica"),
    Data = {Value = 0},
    Replication = "All",
})

while wait(1) do
    test_replica:SetValue({"Value"}, test_replica.Data.Value + 1)
end

-- ( `LocalScript` ReplicaTest.client.lua)
local ReplicaController = require(game.ReplicatedStorage.ReplicaController)

ReplicaController.ReplicaOfClassCreated("TestReplica", function(replica)
    print("TestReplica received! Value:", replica.Data.Value)

    replica:ListenToChange({"Value"}, function(new_value)
        print("Value changed:", new_value)
    end)
end)

ReplicaController.RequestData() -- This function should only be called once
--   in the entire codebase! Read the documentation for more info.
369 Likes

Thanks for the great open-source resource! This seems like it would be very helpful rather than devoting too much time to creating such a nice system myself! I was actually thinking about how to do something similar for my own projects. I will probably look into using this in the future.

12 Likes

I was recently looking for something similar to this, thanks! How would you compare ReplicaService to something like Rodux?

8 Likes

I’d dare to say that Rodux is more like a workflow, while ReplicaService is a networking solution - they’re two different things! It might even be possible to combine Rodux with ReplicaService… Can’t say for sure, though.

21 Likes

Woah, this is amazing, I havent seen something like this before (Maybe its just cause I just started browsing community resources and only 1 year with roblox development) however, I am amazed how many good resources madstudio has been able to create, I hope to see more from you in the future!

6 Likes

Definitely going to be experimenting with this in an upcoming game and will probably end up creating a tutorial on using it practically.

17 Likes

Would this work with mass replicating physics properties from tons of constraints like hingeconstraints, ropeconstraints etc?

My game needs to use tons of remote events from the client to the server and throttles regularly.

8 Likes

If you want to send massive amounts of data there’s nothing that will magically make that data disappear. You can effectively send up to 50 KB/s constantly with casual spikes through RemoteEvents for each individual player - anything more than that can be unstable. For reference, 1 CFrame value, for example, might take up to 50 bytes to replicate (Assuming it uses 32-bit floats) plus any overhead the RemoteEvent itself imposes. Updating a single CFrame 10 frames per second would use around 500 to 600 bytes, so you could effectively only have 100 CFrame streams like that in a single Roblox game.

Overall Roblox was not designed for this kind of data streaming - the best approach is to leave physics updates to the Roblox physics engine.

You’ll have to test your game yourself and see what works.

8 Likes

Its not the physics updates i have to replicate to the server, its the properties of the constraints.

In roblox, if you dont replicate the property changes made on the client to the server, stuff like this will happen.



Thats why i have to update every single property such as “Length” and “Angularvelocity” to the server.

5 Likes

If you’re not able to configure your models properly with BasePart Network Ownership or somehow make the vehicle rigs more stable by using the proper joints / avoiding excessive lose parts and joints, then nothing else will help you as much.

Character flings should be handled on the client-side.

6 Likes

The network owner ship and everything is set correctly and renderstep checking is going to be gamebreaking for my game as you can fly with planes and drive with cars which can reach extreme velocity’s.

5 Likes

I’m really intrigued by the idea of creating a more adaptive StreamingEnabled system. Can you go into more detail (or if you had a moment, provide a code snippet example) of how to properly use ReplicaService for a task like this? There’s a ton of API documentation on this which is great, but initially it’s a little tough to see where the best starting point is and what the optimal logic method would be for a large and helpful new system like this.

2 Likes

Although the API is lengthy, most of it are just variations of mutators and listeners. For StreamingEnabled-like implementations you’ll need to use the Replica:ReplicateFor() and Replica:DestroyFor() server-side methods.

The functionality is straightforward - From a client’s perspective, the same replica can be created, destroyed, created again - the server decides whether the replica is loaded on any particular client.

The only difference from real StreamingEnabled is that you have to tell ReplicaService when to replicate and destroy Replicas when the player enters a region yourself. That being said… Don’t try to reinvent StreamingEnabled - ProfileService is going to be better for map chunks and finite “dimensions” (like MeepCity, AdoptMe) the player is teleported to.

9 Likes

Saw this thread and instantly started looking for a tutorial from you. I’ll most likely start using this after seeing a tutorial or two, this sounds like something I would need for a project I’m currently working on.

3 Likes

So if I understand correctly we can use ReplicaService as an easy way to replicate data back and forth as well as Remote /Event/Function?

  • ReplicaService is Server side?
  • ReplicaController is Client side?

and these are all standalone? I don’t need to import any thing else like the RateLimiter, ScriptSignal and Maid?

2 Likes

As shown in the “Setting up” section of the wiki,
image
you will need a few modules server-side and client-side to run ReplicaService. ReplicaController is the main client-side module. If you miss any modules, the system will not run and will let you know about missing dependencies.

You may group these modules into folders as dependancies will be searched for recursively inside the entirety of ReplicatedStorage and ServerScriptService.

9 Likes

Hi I’m confused why ListenToChange is only available on client, I want to use it on server when SetValue is called as well as I want to generalise what I’m calling on the client to the server, here is my use-case: on the client

ReplicaController.ReplicaOfClassCreated("PlayerProfile", function(replica)
	local data = replica.Data;
	
	local function updateCash(newCash: number)
		activeLobbyManager.store:dispatch(UpdateCash(newCash));
	end;
	
	local function updateOwned(newOwned)
		activeLobbyManager.store:dispatch(UpdateOwned(newOwned));
	end;
	
	updateCash(data.cash);
	updateOwned(data.owned);
	
	replica:ListenToChange("cash", updateCash);
	replica:ListenToChange("owned", updateOwned);
end);

I want the server to recognise the calls to ListenToChange to dispatch to the store on the server as well. (The server would just take the stuff in the body of the function since replica of class created cant be used on server.)

3 Likes

I decided to opt out server-side replica change listeners because a lot of the time server states are going to be passive / have no rendering hooks for GUI’s & parts. Additionally, it was designed to work with ProfileService which accepts direct writing to the table instead of using setter functions - you’re supposed to wrap Profile.Data with a Replica when using them together.

I also though that object.Replica:ListenToChange() would look pretty weird instead of something like object.CoinsChanged:Connect() on the server-side.

However, the overhead of checking for listeners might be negligible, so I’ll look into it. I would appreciate more examples of use cases where the server would need lots of change listening. I’ll try to evaluate that overhead and make a decision quickly.

5 Likes

I would assume that object.CoinsChanged:Connect() would require you to fire that event consecutively every time you change the data in ProfileService as from memory I believe there is no event that detects change in data on ProfileService that you can listen to. object.Replica:ListenToChange() does look kinda weird, however it can be a function call all based on preference:

function activeLobbyManager:hook(replica)
-- the body of the ReplicaController.ReplicaOfClassCreated listener mentioned above
end;

and then the server and client would just plug into that function when created. This would allow really easy management on server & client especially if you want to store player data in Rodux as you don’t have to pass the player data argument everytime as Rodux has more general uses, I can include player data & i can include other state stuff like managing UI.

This is my main use case, I can’t think of many other examples other than Rodux (unless you have another state management module). However, you could probably incorporate other calls to specific modules.

However, take the example in the other post I made, lets say in updateOwned I wanted to update it to Rodux store and imagine I wanted to call a module or function only available where the listeners are located, in my case, lobby manager. Imagine I wanted to fire a function or module only exclusive inside that specific module, this would be another use case other than Rodux.

3 Likes

Another problem with Replicas is that they usually are a clone of select members of a custom object (An object will have members that can’t or shouldn’t be replicated) and in many cases accessing data would be desired by indexing object.Value (Remember that custom objects also usually have methods as members!) instead of object.Replica.Data.Value. In other words, ReplicaService breaks the single source of truth paradigm in practical implementations and, in my opinion, can lead to nasty coding patterns when used as a signal provider server-side - I’m influenced by the fact that these features are something my future team would use.

If you’d still want this functionality, I could probably write a clean enough wrapper for the server-side to enable listeners for Replicas?

Just to be clear, my proposed alternative is to wrap replica mutator calls inside setter methods that also trigger signals created by the developer.

3 Likes