From 74988ad8af68c6d01c42e663a4c40850611076b1 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 5 Nov 2024 16:40:32 -0500 Subject: [PATCH 1/2] First pass --- docs/room/client-side-prediction.md | 72 +++++++++++++++++++++++++++++ docs/room/datastore.md | 2 + sidebars.ts | 1 + 3 files changed, 75 insertions(+) create mode 100644 docs/room/client-side-prediction.md diff --git a/docs/room/client-side-prediction.md b/docs/room/client-side-prediction.md new file mode 100644 index 0000000..d24eddc --- /dev/null +++ b/docs/room/client-side-prediction.md @@ -0,0 +1,72 @@ +--- +layout: docs +title: Client-Side Prediction +--- +# Client-Side Prediction + +:::note +This is an advanced topic! Normcore is designed to be used without having to learn anything at all about this system. But it can be interesting. +::: + +Network messages to and from a room server carry **network latency** (also called **network lag**). A total delay of 100 milliseconds for example to send a message to the server and receive its response is not unusual. + +This is why it makes sense (for the vast majority of situations) to optimistically change a network property on the client instantly instead of waiting for the server to confirm it, which is exactly what Normcore does. + +This concept is called **client-side prediction** or **anticipation**. It ensures that the game feels responsive to the user by hiding the network latency from them. + +In this page we outline the details of Normcore's client-side prediction system. + +## Mis-predictions and rollback +When we optimistically change a network property on the local client and send that to the server, but the server rejects it (rejections are rare but possible in a few scenarios explored below), we have predicted the wrong outcome on the local client. This is called a **mis-prediction**. + +Normcore resolve a mis-prediction by setting the network property's value on the local client to the one provided by the server, therefore resolving the conflict (in favor of the server, always). This operation is called a **rollback**. + +:::note +Rollback happens instantly. This can be jarring, depending on the visual representation of the property. + +That's why some properties could use a smoothing layer or delay on top of the raw value before they're displayed to the player. + +This filtering is left up to the user because it's very situational. In fact most properties in a typical game will have a fixed owner that doesn't change throughout, and won't risk running into any mis-predictions at all. +::: + +## Sources of mis-predictions + +### Contention +If two clients modify a property on an unowned property, the updates are applied as soon as they arrive on the server. In other words, the last client wins. + +So an optimistic change might lead to a mis-predicion if a different client changed the value just after the local client did. + +### Ownership change +A different client might have claimed ownership on the target. The server might have already processed their claim, but this information hasn't yet reached our local client. So our local client, until it receives that information, might still attempt to change the property optimistically. + +## Details +Client-side prediction varies depending on the type of network property. The differences are explored below. + +### Time coherency +It's important to note that due to network latency, the updates that the local client receives from the server are in the past relative to the current local optimistic predictions. + +This is why a rollback not only implies a value change, but also a jump in time. + +### Unreliable properties +Changes are applied optimistically. + +Other clients' changes are applied unconditionally, which implicitly performs a rollback. + +### Reliable properties +Changes are applied optimistically. + +The optimistic prediction is sustained until the server rejects it (triggering a rollback). Other clients' changes are ignored while waiting on the server's response (since they're in the past relative to the local prediction). This behavior is different from unreliable properties. + +:::note +Modifying this property continuously will continuously await for server confirmation. So it won't have the opportunity to resolve mis-predictions until local modifications cease. +::: + +### Collections +Transactional collections don't apply changes optimistically, they always wait for server confirmation instead. + +Non-transactional collections apply changes optimistically and resolve mis-predictions in a manner tailored to each type of collection. + +You can read more about this topic in the [Collections](collections.md) documentation. + +### Other +The `ownerIDSelf` property (in `RealtimeView`, `RealtimeComponent`, and `RealtimeModel`) is internally implemented by a reliable network property. diff --git a/docs/room/datastore.md b/docs/room/datastore.md index 76fe277..534e38b 100644 --- a/docs/room/datastore.md +++ b/docs/room/datastore.md @@ -21,6 +21,8 @@ However, it is possible that the server will reject an update due to either owne Normcore is also able to detect simultaneous updates from multiple clients and respond appropriately. If Client A makes a change and then receives an update from Client B that occurred before Client A made the change locally, Normcore will continue to reflect Client A's value locally as it knows that the value is more recent. If Client A's value is rejected by the server, the datastore will roll back to Client B's value and will fire a change event to notify that the value was updated. +You can read more about this system in the [Client-Side prediction](client-side-prediction.md) documentation. + ## Delta updates The datastore keeps track of all changes that have been applied by the local client. Periodically, Room will instruct the datastore to serialize all the outstanding changes to send in an update to the server. This ensures that only the smallest amount of data needs to be serialized and transmitted to the server. diff --git a/sidebars.ts b/sidebars.ts index 6d49e54..40102da 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -121,6 +121,7 @@ const sidebars: SidebarsConfig = { 'room/ownership-and-lifetime-flags', 'room/room-server-options', 'room/offline-mode', + 'room/client-side-prediction', 'room/common-questions', ] }, From 0d5ecced7c567b3ef1dad95471c37f77755fad45 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 5 Nov 2024 18:13:14 -0500 Subject: [PATCH 2/2] Scott's feedback --- docs/room/client-side-prediction.md | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/room/client-side-prediction.md b/docs/room/client-side-prediction.md index d24eddc..484da59 100644 --- a/docs/room/client-side-prediction.md +++ b/docs/room/client-side-prediction.md @@ -5,36 +5,38 @@ title: Client-Side Prediction # Client-Side Prediction :::note -This is an advanced topic! Normcore is designed to be used without having to learn anything at all about this system. But it can be interesting. +This is an advanced topic! Normcore is designed to be used without having to learn anything at all about this system, but it is helpful to know about. ::: -Network messages to and from a room server carry **network latency** (also called **network lag**). A total delay of 100 milliseconds for example to send a message to the server and receive its response is not unusual. +Network messages have **network latency** (also called **network lag**), which is the time messages spend in transit between the client and the room server. For example, a delay of 100 milliseconds to send a message to the server and receive a response is not unusual. This round-trip delay is known as the **ping**. -This is why it makes sense (for the vast majority of situations) to optimistically change a network property on the client instantly instead of waiting for the server to confirm it, which is exactly what Normcore does. +Since the server can accept or reject changes to network properties based on factors like ownership, when a client makes a change a property, the client can't know what the property value will be until the server responds. There are two common approaches to this problem: -This concept is called **client-side prediction** or **anticipation**. It ensures that the game feels responsive to the user by hiding the network latency from them. +1. Use the previous property value until the client knows if the change was accepted. This makes variable changes by this client appear delayed (usually by the ping), and leads to a bad user experience, especially for things like player controllers, where players expect low latency for responsive-feeling controls. -In this page we outline the details of Normcore's client-side prediction system. +2. Use the changed property value, optimistically expecting it is likely the server will accept the change. This is known as **client-side prediction** or **anticipation**. This makes variable changes by the client appear instant, hiding the network latency and helping to make the game feel responsive. However, the case where the server rejects the change must be handled. -## Mis-predictions and rollback -When we optimistically change a network property on the local client and send that to the server, but the server rejects it (rejections are rare but possible in a few scenarios explored below), we have predicted the wrong outcome on the local client. This is called a **mis-prediction**. +Normcore implements client-side prediction for you, making your game more responsive. In this page we outline the details of Normcore's client-side prediction system. -Normcore resolve a mis-prediction by setting the network property's value on the local client to the one provided by the server, therefore resolving the conflict (in favor of the server, always). This operation is called a **rollback**. +## Mispredictions and rollback +When we optimistically change a network property on the local client and send that to the server, but the server rejects it (rejections are rare but possible in a few scenarios explored below), we have predicted the wrong outcome on the local client. This is called a **misprediction**. + +Normcore resolves a misprediction by setting the network property's value on the local client to the one provided by the server, therefore resolving the conflict (in favor of the server, always). This operation is called a **rollback**. :::note -Rollback happens instantly. This can be jarring, depending on the visual representation of the property. +Rollbacks happen instantly, without smoothing. This can be jarring, depending on the visual representation of the property. That's why some properties could use a smoothing layer or delay on top of the raw value before they're displayed to the player. -This filtering is left up to the user because it's very situational. In fact most properties in a typical game will have a fixed owner that doesn't change throughout, and won't risk running into any mis-predictions at all. +This filtering is left up to the user because it's very situational. In fact, most properties in a typical game will have a fixed owner that doesn't change throughout, and won't risk running into any mispredictions at all. ::: -## Sources of mis-predictions +## Sources of mispredictions ### Contention If two clients modify a property on an unowned property, the updates are applied as soon as they arrive on the server. In other words, the last client wins. -So an optimistic change might lead to a mis-predicion if a different client changed the value just after the local client did. +So an optimistic change might lead to a mispredicion if a different client changed the value just after the local client did. ### Ownership change A different client might have claimed ownership on the target. The server might have already processed their claim, but this information hasn't yet reached our local client. So our local client, until it receives that information, might still attempt to change the property optimistically. @@ -58,13 +60,13 @@ Changes are applied optimistically. The optimistic prediction is sustained until the server rejects it (triggering a rollback). Other clients' changes are ignored while waiting on the server's response (since they're in the past relative to the local prediction). This behavior is different from unreliable properties. :::note -Modifying this property continuously will continuously await for server confirmation. So it won't have the opportunity to resolve mis-predictions until local modifications cease. +Modifying this property continuously will continuously await for server confirmation, so it won't have the opportunity to resolve mispredictions until local modifications cease. ::: ### Collections Transactional collections don't apply changes optimistically, they always wait for server confirmation instead. -Non-transactional collections apply changes optimistically and resolve mis-predictions in a manner tailored to each type of collection. +Non-transactional collections apply changes optimistically and resolve mispredictions in a manner tailored to each type of collection. You can read more about this topic in the [Collections](collections.md) documentation.