Getting Started
Installation (Roblox Studio)
- Create a ModuleScript named
SynchronizerinsideReplicatedStorage/Packages - Create a child ModuleScript named
Channelinside it - Copy the contents of
src/Synchronizer.luauandsrc/Channel.luau
ReplicatedStorage
โโโ Packages
โโโ Synchronizer โ Synchronizer.luau
โ โโโ Channel โ Channel.luau
โโโ Signal โ (dependency)
Via Rojo / Git
git clone https://github.com/zSkanz/Synchronizer.git
Dependencies
Synchronizer requires a Signal module as a sibling inside Packages. We recommend sleitnick/signal.
Basic Usage
Server
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Synchronizer = require(ReplicatedStorage.Packages.Synchronizer)
Players.PlayerAdded:Connect(function(player)
local channel = Synchronizer:Create(player.UserId, {
coins = 0;
level = 1;
inventory = {};
})
channel:AddListener(player)
channel:Set("coins", 100)
channel:Increase("coins", 50)
channel:InsertOnArray("inventory", "Sword")
end)
Players.PlayerRemoving:Connect(function(player)
Synchronizer:Destroy(player.UserId)
end)
Client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Synchronizer = require(ReplicatedStorage.Packages.Synchronizer)
local channel = Synchronizer:Wait(game.Players.LocalPlayer.UserId)
channel:OnChanged("coins", function(newValue, oldValue)
print("Coins:", oldValue, "โ", newValue)
end, true)
channel:OnArrayInserted("inventory", function(item, index)
print("New item:", item, "at index", index)
end)
local coins = channel:Get("coins")
local allData = channel:GetTable()
How It Works
- Server mutates data via
Set,Increase,InsertOnArray, etc. - Mutations are queued and deduplicated within the frame.
- On
RunService.Stepped, all queued actions are batched and sent to listeners. - Client receives the batch, updates its cache, and fires local signals.
Advanced Usage
Path System
Dot-separated paths navigate nested data:
channel:Set("stats.health", 80)
channel:Increase("stats.mana", 10)
channel:OnChanged("stats.health", function(newHP, oldHP)
print("HP:", oldHP, "โ", newHP)
end)
Chaining Mutations
All mutation methods return self. Everything is batched into a single network payload:
channel
:Set("coins", 500)
:Set("level", 10)
:Increase("coins", 100)
ClearSignal โ Dynamic Cleanup
Clean up all signals matching a substring:
channel:ClearSignal("hero_abc")
WaitAndCall โ Non-Yielding
Synchronizer:WaitAndCall(player.UserId, function(channel)
channel:OnChanged("coins", function(newValue)
updateCoinUI(newValue)
end, true)
end)
Configuration
local Settings = {
MAX_REQUESTS_PER_SECOND = 10; -- Rate limit for client requests
DESTROY_CLEANUP_DELAY = 5; -- Seconds before remote cleanup
}
Security
- Listener Validation โ Clients can only request data from channels they listen to.
- Rate Limiting โ Clients exceeding
MAX_REQUESTS_PER_SECONDare blocked. - Data Validation โ Incoming client data is type-checked.
- Cache Isolation โ Each client has its own independent cache table.
Synchronizer API
local channel = Synchronizer:Create(player.UserId, {
coins = 0;
level = 1;
})
nil.nil.| Property | Type | Description |
|---|---|---|
| OnChannelCreated | Signal<Channel> | Fires when a new channel is created |
| OnChannelDestroyed | Signal<Channel> | Fires when a channel is destroyed |
| OnChannelListenerAdded | Signal<Channel, Player> | Player added as listener |
| OnChannelListenerRemoved | Signal<Channel, Player> | Player removed as listener |
Channel API
Channels are created via Synchronizer:Create(). Mutation methods are server-only and return self for chaining.
Mutations
Events
forceCall = true fires immediately with current value.channel:OnChanged("coins", function(newVal, oldVal)
print("Coins:", oldVal, "โ", newVal)
end, true)
Listeners
Getters
Cleanup
| Property | Type | Description |
|---|---|---|
| OnDestroyed | Signal<Channel> | Fires when this channel is destroyed |