Improved Client-Server function-based syncing

I would like to be able to fully utilize the ping that is provided by Roblox to be able to automatically replicate a certain set of important gameplay values with multiple clients and the server. As of now, it is very difficult to have information consistently sent both slow enough as to not overflow the remote queue, and fast enough as to not waste any network ticks without having sent any data. This can be very complicated and troublesome, especially if you’re trying to provide syncing for something such as someones VR hands, the aiming direction of their character in a shooter game, and their current movement inputs for something like client prediction.

It would be very useful to have something, that at the same time as physics replication, will be able to send and receive any updates to some sort of table or set of attributes without needing to manually replicate them or work around them. A simple example is I could share a table with all of the clients containing their current aiming and movement directions, and I could allow the client to make changes to only their respective table, in order to broadcast their own aiming directions. At the same time, we would need to have events that would listen to these changes, and respond. Preferably, a system much like this one:

--on the server
function SyncEvent.OnServerInvoke(
    player: Player, 
    latency: number, 
    playerInformation
)
    if playerInformation then
        --read data and make changes to it if necessary
        --if data is bad, can reject it
        syncedData[player.UserId] = playerInformation
        return syncedData --returns a global state for broadcast
    end
end)

--on the client
function SyncEvent.OnClientInvoke(
    latency: number, 
    syncedData
)
    if syncedData then
        --perform some updates locally to match the server game state
    end
    return playerInformationLocal --return  the local state in order to update the server state
end)

It is assumed that these sync events will be invoked automatically when replication is occurring, and only if both the client and server have the sync event registered.
Both of these should also be executed AFTER physics and regular replication of values such as whether or not audio is playing, as this allows any of those changes to be reverted and revised.

4 Likes

Sooo I’ve run into a similar problem, and while non trivial there is nothing necessarily stopping you from implemting this yourself. Here’s a implementation of lag compensated favor the shooter ray casting to give you an idea of what work around you can use.

This doesn’t really use much networking though, as all this does is roll back player positions and such to see if a hit was possible. Although there are much broader uses for replication, such as replicating in real-time the looking and aiming direction of a player to someone else; manually updating character positions based on latency, for example, in a racing game, putting cars further ahead than roblox would otherwise; or even something like exchanging statistical information between the player like how much money they have without requiring an event fire or value object. For most of these scenarios, the faster the update can be sent, the better. It is very difficult to time a remote event efficiently though to send and get information continouously.

1 Like

Have you tried attributes? They sound like exactly what you’re looking for

https://developer.roblox.com/en-us/articles/instance-attributes

Attributes replicate from server to client but not client to server. An example of what I’m looking for is something I’ve implemented myself with SyncEvent, and tested here:

Cursor location and head angle replicated in realtime. Interpolated on the client.



This is just a rudimentary proof of concept, no type checking or value confirmation.

1 Like

So, the thing with RemoteFunctions is that they take two steps, to return data back (even if they return nothing since the thread state is synced too)

With RemoteEvents, they take one step, so, not as long, but, they are low priority in the thread scheduler compared to, for example, property changes.

The second problem is that remotes can throttle, their calls are coalesced sometimes, so 20 remote fires over the course of a second might end up grouping up the first 19 fires into the same piece of data. This results in data sending in the correct order, and results in data sending consistently, but, it introduces “fake” lag.

I have been running into this when trying to notify the client about having network ownership over certain objects.

The big problem with remotes is that if your goal is to always send data every frame on the client, you can’t do that, almost ever. Roblox will throttle and group up events.

I would love to see something which can produce behaviour like this. Client-server shared “states” if you will.

Here are a few criteria I think would be necessary for something like this:

  1. Some data should be server owned
  2. Some data should be client owned
  3. Some data should be public to other clients
  4. Some data should be private to only one client
  5. Some data should not be linked to a client (So it would be readable by all clients, and could be made writable to all clients)
  6. All data should be modifiable and readable by the sever
  7. Some or all data should be linked to instances

I think this feature request itself is not going to be well received (something I am still learning is that if my feature requests are too long, or are too specific, they tend to get quickly rejected by people) but I absolutely agree that we should have some way to create shared client-server data that the client can modify, and is updated per frame without throttling.

5 Likes

Exactly. My temporary solution with my personal implementation of SyncEvent is to send events roughly every 1/30th of a second, which is the typical rate for the network on roblox, and I could later adjust this to be based on the average latency retrieved using my code, but for now thats how it works. I also use callback functions which both take in data (to update the server or client states) and return back their own data (to be used in state updates). Basically I’m just sending data on this interval and this generally won’t devour the event queue, but it isn’t optimal either. An approach that appropriately uses the network rate and bandwidth would be preferred, sending data as soon as it is ready instead of just “guessing”.

Yeah!

If you send data on Stepped or Heartbeat, it should behave correctly so long as you use RemoteEvents. If you wish to use RemoteFunctions, its better to do them in sync like this without an event, this will always be either receiving or sending data each frame:

while true do
	remoteFunction:InvokeServer(stuff) -- This will take two network frames
end

Remotes can send about 50 kilobytes of data per second, which, is about 0.83 kB per frame, so, if the compressed data is under that remotes generally won’t throttle afaik.

That still runs into problems sometimes though. If you send states, its always better to only send changes in your loop, otherwise you always send more and more data and you send lots of unnecessary data as the table grows.

While this may be true I haven’t implemented anything like that yet, but honestly I don’t think its necessary as of now. If you wish to update some sort of static or less variable value, just use a regular remote event. My implementation is more for continuously changing values such as a player’s aiming direction, or their vehicle location. Those are NECESSARY to be updated as fast as possible, in order to ensure the most accurate and smoothest experience. But it would also be great if it’s something incorporated into the feature. Remote events would practically be deprecated then.

Not really! What you are doing is effectively just updating some state values, I’d assume what you’re doing is changing some values every frame (e.g. aim location) and remotes are not structured like states.

You wouldn’t necessarily be watching for when a value in your state changes, you just want up to date values kind of like properties.

You wouldn’t want to do something like, for example fire a gun by updating something on the state for the gun, that would be inefficient.

The reason for this is as far as I can tell because of packetloss and not because of throtteling done by the client/server, you can actually artificially cause this behavior if you use a network emulator.
This is because roblox forces all property changes/remote call through a single ordered reliable channel which wouldn’t be too much of an issue if we were allowed to send data through an unreliable and/or unordered channel in the same way physics are already being replicated.
As a result whenever you drop a packet all the following packets will have to wait for the missing packet to be resent before being able to be processed by the reciever resulting in occasional huge pingspikes (I described the behavior here: Significant ping spikes on inbound packetloss)

Outside of packetloss though as far as my limited testing could tell remote events calls are actually being sent out in the same frame they are being done so if you are sending data at 60 HZ the server/client will actually send out data at 60 HZ and not at 30 HZ or 20 HZ despite what many people seem to think.
Of course this also means that if you are sending data at 60 HZ instead of 20 HZ you will have 3 times more of these stupid pingspikes as every single packet has a certain chance to drop depending on the quality of the connection.

1 Like

This coalescing behaviour happens in studio so packet dropping would make no sense to describe it, but packet dropping could also influence it. (Studio uses a local server, and is actually sending data through a real connection, but its local so the system is handling it. There should never be packet drop on a local connection)
However, I do not doubt that Roblox accounts for packet drops the way you’re describing, it makes a lot of sense.

The reason that some people think data is sent at 30 HZ and not 60 is probably because of RemoteFunctions. RemoteFunctions technically send data at 30 HZ, in actuality they send their invoke data one network frame, then they receive the response the next network frame, so it always takes two network frames at least. It’s still 60 HZ, its just that you can only send at 30 HZ synchronously.

(Although, its probably also because a lot of this stuff did used to be 30 HZ, and there is still a lot of outdated info floating around, especially in regards to the task scheduler. Everything in Roblox runs at 60 HZ)

A remote event call will never arrive more than a single frame late or early based on my testing (so a maximum jitter of 2 frames).
These huge ping spikes don’t occur at all unless you mess around with a network emulator.
Personally I think this behavior is too inconcistent to be attributed towards intentional throtteling though and too small to be worth worrying about.

If you send, for example, 1200 event fires at once with the timestamp of the fire as an argument you will see they all arrive on the server in chunks of multiple at a time (in a single network frame). This is what I mean when I’m talking about that coalescing behaviour, the events get clumped up.

Additionally, there is actually intentional throttling in the engine. If you send more than 50 kB/s over remotes it will be throttled back down to 50 kB/s and remaining events will be back logged in the event queue. Roblox will just refuse to send more than this amount of data for remotes.

You can read where I go into more detail here:

Are you sending 1200 event in the same frame?
In that case this would be expected behavior considering the client/server will process packets only once per frame.

This is not true as long as the throughput of the connection is big enough to process that amount of data.
As long as the connection allows it you can get above 1 MB/sec network recieve with remotes without any throtteling and with the latency being more or less unaffected.
What is being throttled when you go above 50 kb/s is physics replication, not remotes.
You can see it here: remotetest2 - Roblox (Press F9 to open the console)

When I did testing around this I did some amount of events where I sent eventCount/60 fires per frame.

The effect was that event calls over several seconds worth of frames were being clumped up into a single frame on the server.

This is true, it just comes down to compression. It used to be on the DevHub however when they redid the page for remotes some of the information about remotes was removed including this.

When I did my testing I could verify that I could only send about 0.83 kB per frame of randomized string data. It actually turned out to be a bit less (probably due to header info and extra data added from compression). If I sent slightly more than this per frame, it began throttling my events, and coalescing started happening consistently. If I sent slightly less than this per frame the data was received by the server every single frame fairly consistently.

(P.s. this testing was done at the time of writing the article I linked before)

You will get an inaccurate picture if you are sending the data from the client to the server instead of the other way around as upload speeds are normally much lower and inconsistant than download speeds.
Generally speaking you will send much more data from the server to the client anyway than the other way around.

I did this test for both. Also, I did this test in a studio environment initially. The results were pretty much identical to testing in a live server for me.

As I said above if I was doing it in studio there should be no issues regarding network speed or packet dropping, there is no remote connection its all completely local to my machine in that case so it has nothing to do with my network conditions, network speed, etc.

Studio nowadays will simulate network lag actually. If you check your ping in play mode you should notice it should be around 30-40ms, and you can switch between Client and Server modes to see replication in action. I’m glad they released this feature honestly because it’s amazing and makes debugging a million times easier. Play solo and a game server is not required. The remotes should behave the same in-game as they behave in studio, only problem being that unlike in studio, sometimes people will have a lag spike in their internet or fail to send information. Now we just have to wait for roblox to implement 2-client testing without play solo.

Studio does not simulate network lag. You have to configure the feature for it, there are two locations to do that, one is under TestService, the other is under the NetworkSettings object.

Studio does run an internal server and there is an actual connection, but its completely local, so your network conditions don’t actually matter.

If your ping shows up as 30-40ms in studio its not because studio is simulating network lag, that’s just what the estimation code is giving you (and that code is… Not very accurate to say the least). And, if what Luaction is saying is accurate about packet drops, as I understand it the only thing that should still matter in that case is packet drops, and there is no way to simulate that in studio.

(P.s. the ping measurement you’ll find in the engine is horrid, it can show up as being several minutes despite actually being under 100 ms. Whatever code is doing the estimation is even tied to FPS among other things, which means if you’re getting 20 FPS your ping appears artificially inflated)

1 Like