Late, but I can’t get it to work at all and I have the same issue. If I use SetValue on Data (which I bound to a profile in ProfileService) it simply doesn’t propagate the ListenToChange. Moreover, it actually changes the ProfileService data… ReplicaOfClassCreated is firing properly however.
It seems that there is no support for remote function-like behavior in ReplicaService. Would this be because the server is able to provide live, updated information to client(s) without them needing to request it, therefore making remote functions obsolete? Just curious.
Hey! This looks super cool, but I have a few questions.
- Can the client only read the data, or can it write as well?
- Is there an example of using this with profileservice?
This seems epic, might use it on future projects.
Hey,
Thanks for the Awesome module.
Im trying to listen to changes on the Replica in my server script.
If i do :ListenToRaw my listener gets fired, but when i do :ListenToChange it wont get fired.
My guess is that my path is wrong. Listening on the Server Side seems to work cause :ListenToRaw works so i guess i required the ReplicaServiceListeners module correctly.
I get
23:10:13.397 SetValue ▼ {
[1] = "Taps"
} 42 - Server - DataManager:46
as output on the :ListenToRaw.
My listeners are
DataReplica:ListenToRaw(function(action_name, path_array, params)
print(action_name, path_array, params)
end)
DataReplica:ListenToChange({"Taps"},function(new_value, old_value)
print(new_value, old_value)
end)
is my path incorrect? i tried “{Taps}” and {“Taps”} and {[1] = “Taps”} as the path, but none worked
Some questions about this.
Im already using ProfileService, and replicating the data with ValueBases. This is pretty annoying since I have both the profile data AND the values to worry about, so I was looking into this module.
ReplicaService doesnt create deep copies of tables, so I was wondering if you NEED to change data like in the example?
function PlayerProfile:GiveCash(cash_amount)
if self:IsActive() == false then
return
end
self.Replica:SetValue({"Cash"}, self.Replica.Data.Cash + cash_amount)
end
Would it be okay to do it like this instead?
function PlayerProfile:GiveCash(cash_amount)
if self:IsActive() == false then
return
end
self.Profile.Data.Cash += cash_amount
end
EDIT: It seems like this does work, but listening to changes does not.
Which way of checking data on the server would be advised? Checking Profile.Data or Replica.Data? I know its technically the same thing, but is there a different performance cost to either, or something?
If we cant yield in listener functions, would using task.spawn with a yielding function in the listener functions work?
And also, would replicating a big table of data be an issue? Or only editing large amounts of data at once?
Simple answer: Don’t use this module.
??? lmao what do you mean?? Why not?
There you go, why you don’t have to use RemoteEvents
and ReplicaService instead.
Hi, I’ve been using ReplicaService for a while and I think it’s an amazing module.
I’ve been wondering if there’s a recommended way to go about representing the same object on both the client and the server? I’ve historically just made a separate class for each, e.g. PartyServer and PartyClient.
Each PartyServer has its own replica and that replica being received by the client leads to the creation of a PartyClient. It’s manageable, but it feels like a lot of code repetition. In my mind, it’s somewhat justifiable - after all, the function of the PartyClient is really only to forward changes in a readable way (Party.PartyMemberAdded is easier to read than Party.Replica:ListenToArrayInsert …).
Still, it’s preferable that the interface of PartyServer is at least partially reflected in the PartyClient. PartyServer has setter methods, but all getters, signals, etc should be the same (I think…). Is there a way to go around solving this problem that you’d recommend?
Many thanks,
Zairky
You could create a shared class for all the shared methods and then create whatever client/server specific code you need by checking RunService:IsClient()
or RunService:IsServer()
respectively.
Thanks for the quick reply!
I’ve trialed this in my code before and I feel there’s a messy pattern of continually having:
if IS_SERVER then
-- Server sided stuff
else
-- Client sided stuff
end
littered all around the code. I think this makes code harder to read, but that’s entirely down to personal preference and not a fault of the approach. However, I also imagine it isn’t good practice to expose the server-sided methods to the client (as it allows insight into server-sided code vulnerabilities which are otherwise hidden).
Still, I’m unsure what’s recommended as standard practice - if anything is at all. I have a suspicion that there’s a way to take advantage of the nature of the relationships between the server and client sided objects, but I’m waiting for someone smarter than me to tell me what that is!
Yeah, it’s definitely a situation that’s up to you on how to approach it. One approach that I like is how Knit initializes its framework by deleting the server-side code from the client on startup so its removed from the bytecode entirely on their end. You do end up writing separate classes still but at least you only require a single file with reflected API.
Im experiencing an issue where a server replica does not get received by the client.
Both the client and server uses modules, also using ProfileService.
Server code:
local function playerAdded(player)
local profile = profileStore:LoadProfileAsync("Player_"..player.UserId) -- get profile
if profile ~= nil then
profile:AddUserId(player.UserId) -- GDPR
profile:Reconcile() -- add missing stuff
profile:ListenToRelease(function() -- loaded on another server
profiles[player].Replica:Destroy()
profiles[player] = nil
player:Kick("Your data might have been loaded on another server, please rejoin")
end)
if player:IsDescendantOf(players) == true then -- loaded and player didnt leave
HandleData(player, profile) -- this just changes profile.Data before its sent to the client
-- create profile object with replica
local profileObject = {
Profile = profile,
Replica = replicaService.NewReplica({
ClassToken = profileToken,
Tags = {Player = player},
Data = profile.Data,
Replication = player,
}),
Player = player,
}
profiles[player] = profileObject
else -- left before loaded
profile:Release()
end
else -- could not load
player:Kick("Could not load data, please rejoin")
end
end
Client code:
local dataUtil = {}
local plr = game.Players.LocalPlayer
print(0)
local controller = require(game.ReplicatedStorage.UtilityModules.ReplicaService.ReplicaController)
print(.5)
controller.ReplicaOfClassCreated("PlayerProfile", function(replica)
print(3)
if replica.Tags.Player == plr then
print("Data has been received!")
end
end)
print(1)
controller.RequestData()
print(2)
return dataUtil
You can see I already added debug prints to the client side, and 0, 0.5, 1 and 2 get printed, but 3 does not. ([ReplicaController]: Initial data received
does not get printed either)
What am I doing wrong here?
I tried making a repro for the issue but was unsuccessful.
EDIT: Looks like this was caused by mistakenly adding a ReplicaRemoteEvents folder into replicatedStorage beforehand, causing the module to make a duplicate. Whoops
Hey, I’m unsure as to how I would create a centralized module that connects Replica
creation listeners. I want to get data and listen to data changes at any time and from any LocalScript
. If anyone has any guidance on this topic, please let me know
Is it possible to replicate instances?
Is there anyway to set the value for only a player or a group of players? Or to stop listening for changes?
You serialize it? You must have basic data storing knowledge before getting into this type of stuff.
I have basic data storing knowledge. I’m not just walking around with my head cut off, buddy.
I’m asking a simple question. Is there a way or function in this module, because I have already tried to find it in the API, and have failed.
Well, it would be sort of useless to include if serializers already exist.