Cool! Expect updates in the future, as it is undertested, and I am still in the process of porting it to my game’s code
I have now finished implementing Replica into my own game, and have made some quality-of-life additions (Replicant:MergeSerialized()
and Replicant:Inspect()
, documented here), as well as some important bugfixes.
If you are using the initial release in your game, make sure to update it with the most recent model (or through RoStrap)
This is a very cool module, thanks for making in public!
I’m currently halfway in to my game that uses value objects for similar reasons, but I look forward to using this in the future!
Just published some more updates! I’m getting closer and closer to being able to phase out ValueBase instances to represent replicated state in my own game.
Some key changes:
- Replica.Register now maps Replicants to actual instances (before they would just convert the instance input to a string by the instance’s name) using CollectionService and some other tricks. This means that you can now give any Instance Replicant data, regardless of what it is named (or whether it has fully replicated to other clients yet).
-
Local
is deprecated and no longer required for setting values on the client. This means that Set now has the overloaded behavior of locally setting and replicating, depending on whether the calling network side (Server or Client) has permissions to replicate this value. I decided this would be better and more intuitive overall, sinceLocal
was mostly pointless based on context. - Added a swifty new function
Replicant:Predict
which allows you to predict the next states of data before receiving server confirmation. Predictions are buffered, and, if the predictions match up (even when multiple predictions are made in a row), then the server updates will NOT override the client’s predictions, since these state changes would be redundant. If, however, the predictions are inaccurate, then the client’s state will be rubber-banded to the actual value. - Fixed a bug with visibility (didn’t think it was a bug at first, but it was) where the initial state would be replicated to clients that recently join, even if updates are not supposed to be visible to that player.
- Added a second argument to
Replicant:Serialize(key, forClient)
which will serialize a Replicant with visibility for another player in mind
A use case for :Predict()
would be a weapon system where you want it to appear like your weapon immediately damages another player, but health changes occur so rapidly that you cannot rely on overriding server updates alone (since you might have damaged a player multiple times before you ever receive confirmation from the server about the first hit).
This is a really neat idea! I tried integrating it into one of my projects and I couldn’t get the most basic example working until I spent a bit of time debugging Replica initialization. Here’s the simple example I’m testing.
--[[ Server ]] --
local testData = Replica.Map.new({
Coins = 0,
})
Replica.Register("foo", testData)
testData:Set("Coins", 111)
--[[ Client ]]--
local testData = Replica.WaitForRegistered("foo")
testData.OnUpdate:Connect(function()
local coins = testData:Get("Coins")
print(string.format("Player has %d coins!", coins))
end)
My client was not receiving the update for this Replicant mutation on the server until I adjusted this line in Replica.lua (line 347 on latest version).
Changing line 347 to check not sentInitialReplication[client] (underlined in yellow), as this stood out to me as what was preventing the client from receiving the replication. After this the example works as expected. Is this a bug as suspected? If not, what else might be going wrong in such a simple use case?
That was definitely a bug! Looks like it was working fine for me since all of the Replicants in my games were registered before any player joined (so that particular event wouldn’t fire), so I’m glad you caught it. I updated the model with your change.
After looking into Replica more in depth today, I noticed this issue is actually due to a more subtle bug, and the code pointed out previously was actually fine.
The issue is that it’s possible for the Replica module to be required on the server after players have already joined the game, in which case these players will not get initial replication, as this is only happening on the PlayerAdded event callback - which is only registered once this module is run.
To fix this I added an iteration over all existing players to sent initial replication of registered Replicants which resolved the issue. (Relevant snippet posted below).
local function sendInitReplicationToClient(client)
for key, replicant in pairs(registry) do
if replicant:VisibleToClient(client) then
baseReplicantEvent:FireClient(client, key, replicant:Serialize(key, client), replicant.config)
end
end
sentInitialReplication[client] = true
end
game.Players.PlayerAdded:Connect(function(client)
sendInitReplicationToClient(client)
end)
-- For any players already on the server at the time this module runs (as this can be required after players have joined)
-- invoke initial replication of replicants
for _, client in pairs(game.Players:GetPlayers()) do
sendInitReplicationToClient(client)
end
I’ve submitted a PR to the main repo Fix delayed require initial client replication bug. by FableRBX · Pull Request #2 · headjoe3/Replica · GitHub
Currently it doesn’t appear that there is any direct support for detecting additions/removals to Replica.Map or Replica.Array. A primary example of where this is extremely useful is when the server adds a new weapon to the player’s inventory (let’s say it’s represented via a Replica.Map) where each item has a unique string key. Upon server adding this new item to the Map there’s no direct way for the client to respond to this new item being added, as it appears there is only support for registering a callback for OnUpdate or a change to an existing key (through GetValueOnUpdateSignal). This would require listening for a change via the Map’s “OnUpdate” signal, and then having to iterate the entire Map and searching for any keys that weren’t there before just to determine that a new item has been added.
Is there any elegant way of handling this scenario in the current state of Replica that I’m not seeing?
Yep, those are definitely limitations that could probably be fixed by adding specialized functions to the Map and Array classes.
On a side note, I no longer use Replica in any of my production projects, so I will still try to maintain it when bugs are found, but I am not eating my own dog food in this scenario unfortunately. Right now I use a similar system of key-based prediction/replication of data, but it doesn’t have the same protections against directly mutating data that Replica has, and just uses a single table with a wrapper to replicate changes to the table.
I have stopped using Replica in my case because of the overhead and verbosity of code for the most part. Replica was a step in the right direction for me, but I have iterated through better solutions to data replication over time. Unfortunately, most of it is highly coupled with my game systems, so I haven’t really documented it well or made it user friendly, but I’ve bundled what I currently use together into a public Free Model on my profile.
Thanks for the heads up! We’ll most likely implement something bespoke in that case that is highly influenced by the design of Replica. It’s really nice to see a well thought out approach to data replication on the platform, you’ve definitely created a valuable resource even if it’s primarily to learn from at this point
How do the arrays work? Incrementally?
Are you sending indexes to the client, or keeping UUIDs? I would strongly recommend the latter for deletions, so just in case the client makes a small mistake, it won’t screw up everything else.
Small mistakes shouldn’t happen unless someone has a weird network connection or is exploiting, but it’s always a consideration I hope.
Would this work with DataStore2?
Have you considered making roblox-ts typings for this? I’d love to use it, but I use roblox-ts.
Have you? roblox-ts is nice and all, but not everybody uses it
I never said everybody uses it. I’m just not fluent enough in typefu to write typings for a module like this, and iirc he’s active in the roblox-ts community and think he’s written a package or two.
Oh, no that’s not what I mean at all. Personally, I wouldn’t expect everyone who has written a Lua library that I use to make roblox-ts typings, though.
Okay, that makes sense. If they’re involved in roblox-ts then it probably wouldn’t hurt as much to ask.
Sorry, I just misunderstood.
Thanks, this is very useful. I just have one question: can exploiters take advantage of this on my own game and wreak havoc? Nonetheless, this’ll come handy (especially for my game).
Exploiters can do the exact same thing they usually do: send any data they want to the server. As long as you don’t trust whatever data you receive from the client, this library should not make your game any more vulnerable than it would be if you did trust the client.
Thanks for the reply, I’ll take this to mind. I’ll also consult a few of the anti-cheat tutorials posted on here, just in case.
Update: I wrote Roblox-TS typings for Replica, for those of you that code in TypeScript: