My own ReplicaService

Hello! I am currently working on a state replication system that works similarly to ReplicaService but aims to make managing the data seamless and significantly easier. It is being used in a new game networking framework that I am working on called Mince. Curious if anyone has any feedback on the actual api of it, because it doesn’t come without its fallbacks. Be advised that this api is very different from ReplicaService, as it aims to do most of the work for you.

Data Replication

-- Server
local State = Mince:GetState("MyState") -- GetState can also accept an instance as it is mutated so it works for things like components

State.CurrentTime = workspace:GetServerTimeNow()
-- Client
local State = Mince:GetState("MyState")

State:Observe("CurrentTime", function(new, old)
      print(`The current time is {new}`)
end)

It also allows for subtables and subtables of subtables to get updated like so:

-- Server
local State = Mince:GetState("MyState")

State.MyTable = {
      MySubTable = {
          MyValue = "Hello"
      } 
}

task.wait(5)

State.MyTable.MySubTable.MyValue ..= " World!"
-- Client
local State = Mince:GetState("MyState")

State:Observe("MyTable.MySubTable.MyValue", function(new, old)
      print(`Value has changed to {new}`)
      -- Output[1]: Value has changed to Hello
      -- Output[2]: Value has changed to Hello World!
end)

Data Changing

States can also help you with Client → Server → Client replication by creating a framework for changing that data.

-- Server
local State = Mince:GetState("MyState")
State.MyIndex = "Value"

State:OnRequestSet("MyIndex", function(Player, NewValue)
      if Player.Name == "intykat" then
            State.MyIndex = NewValue
      end
end)
-- Client
local State = Mince:GetState("MyState")

State:RequestSet("MyIndex", "NewValue")
print(State.MyIndex) -- "NewValue"

Indexing stuff

So if youre reading by now you probably are alarmed at the fact that data indecies seem to be constricted by things like not being able to have periods or the fact that states have methods which could be overridden. Let me go over the two instances that indecies need to overwrite or fall out of line with the rest.

Indecies with periods

Say you have a table like so:

State.SomeTable = {
    ["MyIndex.HasAPeriod"] = true
}

How would you observe this index? Well, the state Observe method (and any method that requires index as an argument) also supports table index building. So you would write:

State:Observe({"SomeTable", "MyIndex.HasAPeriod"}, fn)

Under the hood all indexes are turned into tables and serialized into a string to be identified.

Indecies that overwrite methods

What if you really wanted to use an index that overwrites a method, like the index Observe. Well you can totally do that, as there is a second way to call these methods by calling the table.

-- Server
MyState.Observe = "I overwrite you"

-- Client
MyState("Observe", "IndexToObserve", fn)

Pitfalls

Server table reading

This is a little bit complicated, but essentially the way that table updates are detected is through an empty table with a __newindex metamethod, using a proxy table for the __index method. What does this mean? It means that Table.SomeData will return SomeData but if you were to try to print out Table you would get {}. You can still iterate through data but it may be harder to debug your states values. I am thinking of implementing a State:GetRealValue() method to solve this issue.

Memory usage

For every table in a state, you are creating 3 other tables that use it. A proxy table with the data, a metatable that watches for table updates, and an empty “Main” table that is returned. Additionally, all of the data is also stored in a seperate table that saves indecies via serialized array as a lookup table for values. I really don’t think it’s an issue, but it is something to consider.

So what do you think? Is this a valid and useful module or should I do it differently. Let me know! This is going to be part of an open source framework.

3 Likes