ReplciatedRegistry2 is the successor to ReplicatedRegistry which has a better designed API, more flexibility and increased convenience and transparency.
It is a single module with 0 dependencies, making it very simple to insert and use in your games.
Some examples
Simple ProfileStore Example
--server
local server = ReplicatedRegistry.server
server.set_remote_instances(...)
local ProfileTemplate = {
Coins = 0,
Level = 1,
Inventory = {}
}
local ProfileStore = ProfileService.GetProfileStore(
"PlayerData",
ProfileTemplate
)
local Profiles = {}
Players.PlayerAdded:Connect(function(player)
local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId)
if profile ~= nil then
profile:AddUserId(player.UserId)
profile:Reconcile()
profile:ListenToRelease(function()
Profiles[player] = nil
server.deregister(player.UserId)
player:Kick("Profile released")
end)
if player:IsDescendantOf(Players) then
Profiles[player] = profile
server.register(
player.UserId,
profile.Data,
ReplicatedRegistry.get_filter {no_recieve = true}
)
else
profile:Release()
end
else
player:Kick("Failed to load data")
end
end)
Players.PlayerRemoving:Connect(function(player)
local profile = Profiles[player]
if profile ~= nil then
profile:Release()
Profiles[player] = nil
server.deregister(player.UserId)
end
end)
local function GetProfile(player)
return Profiles[player]
end
task.wait(5)
for _, player in Players:GetPlayers() do
local proxy = server.view_as_proxy(player.UserId)
proxy.incr({"Coins"}, 20) -- This also updates the data stored in ProfileStore since it keeps a reference
proxy.replicate {player}
end
--client
local client = ReplicatedRegistry.client
client.set_remote_instances(...)
local my_data = client.view(game.Players.LocalPlayer.UserId)
print(my_data.Coins) -- 20
Added a _auto_commit: boolean = true parameter to server.to_clients(), server.to_all_clients() and client.to_server(). This specifies if the aformentioned functions should call ReplicatedRegistry.commit_changes() automatically or not.
Changed RegistreeInterface_Server.replicate() to accept an array of Players as the 1st argument instead of only 1 player.
Nope, all it does is generate TableChanges which are deltas for tables to be sent over the network. You can make it buffered by calling ReplicatedRegistry.set_change_serde()
Added ReplicatedRegistry.meta.switch_signal_lib(s: typeof(Signal)), keep in mind the new signal library has to have the same structure as the one used by ReplicatedRegistry, which is 2 functions: connect: <T...>(tbl: {(T...) -> ()}, item: (T...) -> ()) -> ScriptConnection fire: <T...>(tbl: {(T...) -> ()}, ...: T...) -> ()
Unfortunately using a regular signal library will not work
Added the module as a Wally package, @athar-adv/replicated-registry
Changed customEvent arguments in replicator functions such as client.to_server() and server.to_clients() to _sender: Sender? which is a callback you pass that will get called to send the changes.
Removed the ReplicatedRegistry.set_change_serde() api as it is no longer necessary because you can just wrap a serde around the sender functions you pass
Added set_remote_callbacks() to client and server (kept set_remote_instances()
Removed server.to_all_clients() due to the new sender callbacks being implemented, instead you can just pass Players:GetPlayers() into server.to_clients()
Its alot simpler (not only to use but also the sourxe code itself) and has 0 dependencies
It doesnt do state management out of the box, pure table replication
It uses registries instead of oop so the syntax is alot simpler
Idk if Replica has this but ReplicatedRegistry2 allows clients to make modifications to the registry with server-authoritive rules, like being able to modify their own settings but not anything else for example (this is via Filters, callbacks.on_recieve_callback, etc)
Middleware isnt implemented out of the box and the way you implement it is via callbacks so its extremely customisable, for example you can pass a Sender callback that serdes TableChanges into buffers before sending and vice versa for ConnectReciever
You arent forced into using a proxy, the default option for table replication is just pure tables which will be diffed with an older copy to generate deltas
Couldnt name more but theres probably more :V U should check the docs to see examples of what kinda applications this module easily integrates into
Wdym by this btw, its pretty explicit
For example on the server you call server.set_remote_instances() to implement middleware as RemoteEvents and RemoteFunctions, then server.register() to register your keys, then either server.view_as_proxy() to view it as a RegistreeInterface to make replications queue per change instead of diff, or if you want replications to use deltas from diffs you can just call server.to_clients() without the _changes parameter
I’ve ran out of time to see if implementing this would be better for our game. However, I love the fact its not proxied. I’ll definitely come back to this sometime in the future to have a proper look, thank you!
as for my question about the syntax looking weird, I think im just used to pascalcase and oop-style code, apologies.
Changed proxy methods set, get, and incr to accept an array of keys for the path instead of a vararg.
Added RegistreeInterface.key_changed() to detect when a key change is done by the opposite context, aka when you call .set on the same context as the proxy, this callback isnt fired.
Yea i made a seperate module just incase ReplicatedRegistry1 had been out long enough for anyone to integrate it into their game, considering the new api is alot different :V
Hi OP, while the module looks great and is useful for me, I do have a few problems:
Naming: ‘on_recieve’ > ‘on_receive’ would be nicer for consistency and autocomplete. I was scratching my head for a while at first due to this.
client.view(): when data isn’t registered yet, it errors hard. Would be way safer if it just returned ‘nil’ so we don’t need ‘pcall()’ wrappers. Easy example: In your ProfileStore integration example, if we follow closely and add a LocalScript to view that same table, it becomes race condition where we call client.view() earlier than ProfileStore and server.register() can happen, returning a breaking Registree ‘<table_key>’ couldn’t be accessed. This way, I would have to wrap client.view() around a pcall which I believe is just another layer of extra work to do.
Haven’t explored everything yet, but the potential is huge, excited to see where it goes!
Added await variants of view and view_as_proxy on both client and server, and these variants will indefinitely yield the calling thread until a Registree with the key you wanna view is created.
Types of view and view_as_proxy are now nullable and no longer error when the Registree does not exist
Hi! I’ve used this module recently but I have a couple issues with it at the moment.
The wally package path is incorrect, it doesn’t require properly, I looked at your github, and to fix it you just need to move your init.luau from src up 1 folder.
The server deregister function doesnt deregister properly. you set
reg = registrees[registrees]
instead of
reg = registrees[register_key]
in ReplicatedRegistry.server.deregister
Other than those two things, this is a very useful module.