Mimic the replication behaviour of ValueObjects without using them?

I take a fondness in ValueObjects primarily because of their simplicity to work with and the fact that their values automatically replicate. Unfortunately, this develops bad habits such as relying on those ValueObjects and spending valuable memory to retain their background processes.

I’d like to get rid of the presence of ValueObjects from my games and switch to replicating data without them, but I’m not quite sure how to achieve the same or similar auto-replication behaviour that comes with ValueObjects. Would anyone happen to have any tips?

I’ve thought about using this kind of a structure:

  • ModuleScript in ReplicatedStorage, returning a table
  • Eagerly loaded on both the server and the client (trouble step - needs to be able to work with lazy-loaded modules as well)
  • Metatable wrapped around table
  • RemoteEvents fired upon changes to opposite environment
    • Server directly mutates values, client cannot write or make requests except on its own copy of the ModuleScript

I’m just a little concerned about performance or getting lost in this structure.

I appreciate any and all relevant pointers.

4 Likes

Any time data is updated from the server, fire that data to the client. Then, you can have a sort of giant table or array of the data, and replicate it when it changes. This way, it can be accessed on both the client and server.

The server and client both use this module script to request the data, and the server has the ability to change it.

Couldn’t you make it update the client data every time the data is updated?

--module
local module = {}
module.Data = 0 -- set the datatype that you want it to be
return module
--server
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local module_refrence = ReplicatedStorage.ModuleScript
local OnDataUpdate = ReplicatedStorage.RemoteEvent
local Players = game:GetService"Players"
local module = require(module_refrence)
local function UpdateData(func)
    pcall(func,module)
    OnDataUpdate:FireAllClients(module_refrence,module.Data)
end
Players.PlayerAdded:Connect(function(p)
    OnDataUpdate:FireClient(p,module_refrence,module.Data)
end)
--client
local module_refrence = game.ReplicatedStorage.ModuleScript
local OnDataUpdate = game.ReplicatedStorage.RemoteEvent
local module = require(module_refrence)
OnDataUpdate.OnClientEvent:Connect(function(ref,data)
    require(ref).Data = data
end)

Something like this works for me.

@REALTimothy0812

This is similar to the implementation I mentioned in the OP and the closest I’ve actually come to replication behaviour, but this isn’t particularly scalable nor the kind of similar/same replication I’m looking for. It’s not implicit, you have to force-replicate everything from remotes. I end up having to make an eagerly-loaded cache module to be able to support lazy-loaded modules and other scripts as well.

This isn’t always a smart solution. In the same way that you shouldn’t be saving data every time a change is made, there are cases where you should not be passing things every time a change is made either. Not even all properties for objects on Roblox replicate per change due to performance reasons. Keep in mind limitations for various API as well.

Worst case scenario, I’ll create a cache module and figure out how to go from there. That’s not quite solving my problem though because that isn’t replication and it’s easy to get lost once your project becomes large or you can’t keep track of everything.

-- Example

local Cache = {}

function Cache:NewItem(...)
    -- ...
end

function Cache:RemoveItem(...)
    -- ...
end

function Cache:SetReplicating(...)
    -- ...
end

function Cache:GetDirectory(Name)
    return self[Name] -- Or whatever
end

return setmetatable(Cache, NecessaryMT)

This may already be solving my problem or I may be overthinking it. Caching through a ModuleScript is certainly something I’ve considered (and attempted), but it’s neither similar or the same as the replication behaviour of ValueObjects. This is just standard handing off of data cross-environment, which I’m already capable of doing. I wouldn’t have created this post if that was what I was seeking.


@Halalaluyafail3

Already made note of that in the OP. Your implementation isn’t scalable for my use case because then I need to create cache behaviour for every ModuleScript. This also slightly hurts as far as readability goes. I could create a cache class or similar, but that’s basically what I explained above.

If that’s not what your code is meant to do, I apologise, though that’s how it comes off to me. I’m not particularly a fan of needing to ditch lazy-loading for a simple operation. Your client code also does the same thing - if a data change is made, it lazy-loads a module and directly mutates the target value. This can cause a problem if modules are loading where they shouldn’t be.


If at all possible, I’d like to stay free of caching behaviour unless that’s my only option. Selective replication is already fairly easy to do but I’m just unsure of what to do to mimic replication like in ValueObjects.

When the server makes changes to an object where properties replicate to the client, changes are instantaneous (or near-instantaneous, enough not to notice). Remotes still have a trip to make (in the case of RemoteFunctions to support change callbacks, a round trip) and are hindered by several factors. Remotes aren’t necessary to replicate in either scenario.

With the delay of a round trip remote, it’s going to be hard or near impossible to get the same replication as value objects. As developers, we do not have access to Roblox’s core replication processes. Without using value objects, We only have access to remote events, which will not achieve the same as value objects. Unless a feature where we can manage the replication priority of items comes into existence, remotes will not be able to achieve the same as value objects.

At the end of the day, value objects were added for a reason, and while they may not be ideal, they may be easier to use the remotes.

I wish you to best ot luck on trying to find a viable solution, but replicating the behavior of value objects with remotes is not ideal.

1 Like

I really hope I don’t have to force myself to use remotes or ValueObjects, since neither of those quite solve my issue. Alas though, the former may be my only option.

I did find some replicator services earlier, but they’re all internal and never seemed to have sported any developer-exposed functionality. I’m not quite sure how I’d catch replication changes with them but it’d be a great start.

I’m picking real hard at this problem conceptually and with a few repros. The thing I’m trying to avoid the most is creating instances that are unnecessary. I’d also like to be able to replicate without eagerly-loading almost my entire structure ahead of turn just to fit one bit of functionality.

1 Like

Somewhat of a necrobump, but just want to leave an answer in the rare instance that someone is wondering how: attributes exist now and they’re lovely! If that doesn’t work either then you can just hand replicate the data with RemoteEvents, not a big deal besides standard networking bottlenecks (region, internet speed, anything that affects delivery time).

1 Like