ReplicatedRegistry2 | Insanely simple and easy 2-way server-client replicated table registry

[{}] ReplicatedRegistry2 (stable)

gitdiscdocs

rbx rbx

Wally

Find more of my resources in my index


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

Hope you enjoy this resource, and happy coding!

8 Likes

1.0.1

  • 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.

cool, is there dynamic buffer de/serialization here?

1 Like

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()

Read the docs for more info :slight_smile:

2 Likes

1.0.2

  • 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

1.1.0:

  • 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()
  • Removed a few redundant things
1 Like

1.2.0:

  • Filters now work on a per-changes-basis instead of a per-change-basis
  • Added server.deregister()
  • Updated docs to match 1.1.0 and up
  • Updated wally version to 1.2.0

This module looks great, the syntax is a bit weird however.

My main question is what differentiates this from Replica by loleris? or is it just your own implementation of the same systems.

2 Likes
  1. Its alot simpler (not only to use but also the sourxe code itself) and has 0 dependencies
  2. It doesnt do state management out of the box, pure table replication
  3. It uses registries instead of oop so the syntax is alot simpler
  4. 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)
  5. 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
  6. 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

1 Like

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.

1 Like

Yea its completely fine, i hope you’ll consider using this module in the future! It integrates nicely with pretty much anything

1.3.0:

  • 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.

Feels like yesterday that replicated registry 1 came out lol

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:

  1. 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.
  2. 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!

1 Like

Ahh my bad, this is 1 word i struggle with remembering how to type alot lol

Oh yeah for sure i can definitely just return nil if the server declines/hasnt registered the registree yet

1.4.0:

  • 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
  • Renamed on_recieve to on_receive

Hi! I’ve used this module recently but I have a couple issues with it at the moment.

  1. 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.
  2. 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.

1 Like

Ahh thanks!

Ohhh yea my bad, very obvious oversight in hindsight lol

1.4.1:

  • Added default.project.json file to the repo aswell as adding an include field to the wally.toml, hopefully this should fix the require issue
  • Fixed server.deregister using registrees[registrees] instead of registrees[register_key]
2 Likes