Skip to content

Commit bfe637e

Browse files
committed
Delay buffered field syncing until 200ms pass without further changes
This reduces network traffic while improving UX by ensuring only the final change is sent. It prevents the field from snapping back to older values mid-interaction due to intermediate syncs being sent back by the server.
1 parent e7cb072 commit bfe637e

File tree

2 files changed

+15
-27
lines changed

2 files changed

+15
-27
lines changed

Source/Client/Syncing/Handler/SyncField.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,16 @@ public ISyncField PostApply(Action<object, object> action)
141141
return this;
142142
}
143143

144-
/// Throttles network updates for this field to reduce network chatter. An update
145-
/// is sent immediately unless another was already sent within the last 200ms. If
146-
/// additional updates occur during that cooldown period, they are not sent
147-
/// immediately; only the most recent update is queued and will be sent once the
148-
/// 200ms interval has passed. All intermediate updates are discarded.
144+
/// Throttles network updates for this field to reduce network chatter.
145+
/// An update is sent only after the field has remained unchanged for 200ms.
146+
/// If changes continue occurring within that window, the timer resets, and only
147+
/// the latest value is eventually sent. All intermediate updates are discarded.
149148
///
150-
/// The UI reflects the latest value instantly, without waiting for server
151-
/// confirmation — avoiding rollback, and re-apply behavior when the server
152-
/// responds. However, in some cases the UI may briefly display stale information:
153-
/// this can occur when the client sends an update to the host, which then sends a
154-
/// confirmation back that overwrites the current client-side value, potentially
155-
/// during an ongoing user interaction.
149+
/// The UI reflects the latest value immediately, without waiting for server
150+
/// confirmation — avoiding rollback, and reapply behavior on server response.
156151
///
157-
/// This mechanism is typically used for rapidly changing values (e.g., sliders),
158-
/// where sending every intermediate update would be inefficient and unnecessary.
152+
/// Ideal for rapidly changing fields (e.g., sliders) where sending every
153+
/// intermediate value would be inefficient and unnecessary.
159154
public ISyncField SetBufferChanges()
160155
{
161156
SyncFieldUtil.bufferedChanges[this] = new();

Source/Client/Syncing/SyncFieldUtil.cs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static void FieldWatchPostfix()
7070
if (cachedData != null)
7171
{
7272
cachedData.sent = false;
73+
cachedData.lastChangedAtMillis = Utils.MillisNow;
7374
cachedData.toSend = SnapshotValueIfNeeded(handler, newValue);
7475
continue;
7576
}
@@ -94,14 +95,13 @@ private static bool SyncPendingAndPruneFinished(BufferTarget target, BufferData
9495
return true;
9596

9697
var millisNow = Utils.MillisNow;
97-
if (!data.sent && millisNow - data.timestamp > 200)
98+
if (!data.sent && millisNow - data.lastChangedAtMillis > 200)
9899
{
99100
// If syncing fails with an exception don't try to reattempt and just give up.
100101
if (data.field.DoSyncCatch(target.target, data.toSend, target.index) is false)
101102
return true;
102103

103104
data.sent = true;
104-
data.timestamp = millisNow;
105105
}
106106

107107
return false;
@@ -219,22 +219,15 @@ public override int GetHashCode()
219219
}
220220
}
221221

222-
public class BufferData
222+
public class BufferData(SyncField field, object actualValue, object toSend)
223223
{
224-
public SyncField field;
224+
public SyncField field = field;
225225
/// This is the real field's value. If this were an unbuffered field, it'd be equivalent to `FieldData.oldValue`,
226226
/// however for buffered fields `oldValue` reflects the value prior to the last GUI update. Use this field to
227227
/// access the original value before any user interaction occurred.
228-
public object actualValue;
229-
public object toSend;
230-
public long timestamp;
228+
public object actualValue = actualValue;
229+
public object toSend = toSend;
230+
public long lastChangedAtMillis = Utils.MillisNow;
231231
public bool sent;
232-
233-
public BufferData(SyncField field, object actualValue, object toSend)
234-
{
235-
this.field = field;
236-
this.actualValue = actualValue;
237-
this.toSend = toSend;
238-
}
239232
}
240233
}

0 commit comments

Comments
 (0)