Skip to content

Commit 0cf2a37

Browse files
committed
debounce updating saves and players
1 parent 13996d0 commit 0cf2a37

File tree

9 files changed

+170
-18
lines changed

9 files changed

+170
-18
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using UnityEngine;
5+
6+
namespace TaloGameServices
7+
{
8+
public abstract class DebouncedAPI<TOperation> : BaseAPI where TOperation : Enum
9+
{
10+
private class DebouncedOperation
11+
{
12+
public float nextUpdateTime;
13+
public bool hasPending;
14+
}
15+
16+
private readonly Dictionary<TOperation, DebouncedOperation> operations = new();
17+
18+
protected DebouncedAPI(string service) : base(service) { }
19+
20+
protected void Debounce(TOperation operation)
21+
{
22+
if (!operations.ContainsKey(operation))
23+
{
24+
operations[operation] = new DebouncedOperation();
25+
}
26+
27+
operations[operation].nextUpdateTime = Time.realtimeSinceStartup + Talo.Settings.debounceTimerSeconds;
28+
operations[operation].hasPending = true;
29+
}
30+
31+
public async Task ProcessPendingUpdates()
32+
{
33+
var keysToProcess = new List<TOperation>();
34+
35+
foreach (var kvp in operations)
36+
{
37+
if (kvp.Value.hasPending && Time.realtimeSinceStartup >= kvp.Value.nextUpdateTime)
38+
{
39+
keysToProcess.Add(kvp.Key);
40+
}
41+
}
42+
43+
foreach (var key in keysToProcess)
44+
{
45+
operations[key].hasPending = false;
46+
await ExecuteDebouncedOperation(key);
47+
}
48+
}
49+
50+
protected abstract Task ExecuteDebouncedOperation(TOperation operation);
51+
}
52+
}

Assets/Talo Game Services/Talo/Runtime/APIs/DebouncedAPI.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Talo Game Services/Talo/Runtime/APIs/PlayersAPI.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ public class MergeOptions
1010
public string postMergeIdentityService = "";
1111
}
1212

13-
public class PlayersAPI : BaseAPI
13+
public class PlayersAPI : DebouncedAPI<PlayersAPI.DebouncedOperation>
1414
{
15+
public enum DebouncedOperation
16+
{
17+
Update
18+
}
19+
1520
public event Action<Player> OnIdentified;
1621
public event Action OnIdentificationStarted;
1722
public event Action OnIdentificationFailed;
@@ -103,6 +108,21 @@ public async Task<Player> IdentifySteam(string ticket, string identity = "")
103108
return Talo.CurrentPlayer;
104109
}
105110

111+
protected override async Task ExecuteDebouncedOperation(DebouncedOperation operation)
112+
{
113+
switch (operation)
114+
{
115+
case DebouncedOperation.Update:
116+
await Update();
117+
break;
118+
}
119+
}
120+
121+
public new void DebounceUpdate()
122+
{
123+
Debounce(DebouncedOperation.Update);
124+
}
125+
106126
public async Task<Player> Update()
107127
{
108128
Talo.IdentityCheck();

Assets/Talo Game Services/Talo/Runtime/APIs/SavesAPI.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
namespace TaloGameServices
88
{
9-
public class SavesAPI : BaseAPI
9+
public class SavesAPI : DebouncedAPI<SavesAPI.DebouncedOperation>
1010
{
11+
public enum DebouncedOperation
12+
{
13+
Update
14+
}
15+
1116
internal SavesManager savesManager;
1217
internal SavesContentManager contentManager;
1318

@@ -186,9 +191,42 @@ public async Task<GameSave> CreateSave(string saveName, SaveContent content = nu
186191
return savesManager.CreateSave(save);
187192
}
188193

194+
protected override async Task ExecuteDebouncedOperation(DebouncedOperation operation)
195+
{
196+
switch (operation)
197+
{
198+
case DebouncedOperation.Update:
199+
if (savesManager.CurrentSave != null)
200+
{
201+
await UpdateSave(savesManager.CurrentSave.id);
202+
}
203+
break;
204+
}
205+
}
206+
207+
public void DebounceUpdate()
208+
{
209+
Debounce(DebouncedOperation.Update);
210+
}
211+
189212
public async Task<GameSave> UpdateCurrentSave(string newName = "")
190213
{
191-
return await UpdateSave(savesManager.CurrentSave.id, newName);
214+
var currentSave = savesManager.CurrentSave;
215+
if (currentSave == null)
216+
{
217+
throw new Exception("No save is currently loaded");
218+
}
219+
220+
// if the save is being renamed, sync it immediately
221+
if (!string.IsNullOrEmpty(newName))
222+
{
223+
return await UpdateSave(currentSave.id, newName);
224+
}
225+
226+
// else, update the save locally and queue it for syncing
227+
currentSave.content = contentManager.Content;
228+
DebounceUpdate();
229+
return currentSave;
192230
}
193231

194232
public async Task<GameSave> UpdateSave(int saveId, string newName = "")

Assets/Talo Game Services/Talo/Runtime/Entities/Player.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using UnityEngine;
22
using System.Linq;
33
using System;
4-
using System.Threading.Tasks;
54

65
namespace TaloGameServices
76
{
@@ -17,23 +16,23 @@ public override string ToString()
1716
return JsonUtility.ToJson(this);
1817
}
1918

20-
public async Task SetProp(string key, string value, bool update = true)
19+
public void SetProp(string key, string value, bool update = true)
2120
{
2221
base.SetProp(key, value);
2322

2423
if (update)
2524
{
26-
await Talo.Players.Update();
25+
Talo.Players.DebounceUpdate();
2726
}
2827
}
2928

30-
public async Task DeleteProp(string key, bool update = true)
29+
public void DeleteProp(string key, bool update = true)
3130
{
3231
base.DeleteProp(key);
3332

3433
if (update)
3534
{
36-
await Talo.Players.Update();
35+
Talo.Players.DebounceUpdate();
3736
}
3837
}
3938

Assets/Talo Game Services/Talo/Runtime/TaloManager.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private async void DoFlush()
4848
}
4949
}
5050

51-
private async void Update()
51+
private void Update()
5252
{
5353
if (Application.platform == RuntimePlatform.WebGLPlayer)
5454
{
@@ -65,10 +65,30 @@ private async void Update()
6565
tmrContinuity += Time.deltaTime;
6666
if (tmrContinuity >= 10f)
6767
{
68-
await Talo.Continuity.ProcessRequests();
68+
ProcessContinuityRequests();
6969
tmrContinuity = 0;
7070
}
7171
}
72+
73+
ProcessDebouncedUpdates();
74+
}
75+
76+
private async void ProcessContinuityRequests()
77+
{
78+
await Talo.Continuity.ProcessRequests();
79+
}
80+
81+
private async void ProcessDebouncedUpdates()
82+
{
83+
if (Talo.HasIdentity())
84+
{
85+
await Talo.Players.ProcessPendingUpdates();
86+
}
87+
88+
if (Talo.Saves.Current != null)
89+
{
90+
await Talo.Saves.ProcessPendingUpdates();
91+
}
7292
}
7393

7494
public void ResetFlushTimer()

Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/DeleteProp.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ public class DeleteHealthProp : MonoBehaviour
88
{
99
public string key;
1010

11-
public async void OnButtonClick()
11+
public void OnButtonClick()
1212
{
13-
await DeleteProp();
13+
DeleteProp();
1414
}
1515

16-
private async Task DeleteProp()
16+
private void DeleteProp()
1717
{
1818
if (string.IsNullOrEmpty(key))
1919
{
@@ -23,7 +23,7 @@ private async Task DeleteProp()
2323

2424
try
2525
{
26-
await Talo.CurrentPlayer.DeleteProp(key);
26+
Talo.CurrentPlayer.DeleteProp(key);
2727
ResponseMessage.SetText($"{key} deleted");
2828
}
2929
catch (Exception ex)

Assets/Talo Game Services/Talo/Samples/Playground/Scripts/Players/SetProp.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ public class SetProp : MonoBehaviour
77
{
88
public string key, value;
99

10-
public async void OnButtonClick()
10+
public void OnButtonClick()
1111
{
12-
await UpdateProp();
12+
UpdateProp();
1313
}
1414

15-
private async Task UpdateProp()
15+
private void UpdateProp()
1616
{
1717
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
1818
{
@@ -22,7 +22,7 @@ private async Task UpdateProp()
2222

2323
try
2424
{
25-
await Talo.CurrentPlayer.SetProp(key, value);
25+
Talo.CurrentPlayer.SetProp(key, value);
2626
ResponseMessage.SetText($"{key} set to {value}");
2727
}
2828
catch (System.Exception ex)

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ The SDK supports offline operation via `TaloSettings.offlineMode`. When offline,
4646
### Event Flushing
4747
Events are batched and flushed on application quit/pause/focus loss. On WebGL, events flush every `webGLEventFlushRate` seconds (default 30s) due to platform limitations.
4848

49+
### Debouncing
50+
Player updates and save updates are debounced to prevent excessive API calls during rapid property changes. APIs that need debouncing inherit from `DebouncedAPI<TOperation>` (a generic base class) and define a `DebouncedOperation` enum for type-safe operation keys. The base class uses a dictionary to track multiple debounced operations independently.
51+
52+
To add debouncing to an API:
53+
1. Define a public `enum DebouncedOperation` with your debounced operations
54+
2. Inherit from `DebouncedAPI<YourAPI.DebouncedOperation>`
55+
3. Call `Debounce(DebouncedOperation.YourOperation)` to queue an operation
56+
4. Implement `ExecuteDebouncedOperation(DebouncedOperation operation)` with a switch statement
57+
5. The base class's `ProcessPendingUpdates()` is called by `TaloManager.Update()` every frame
58+
59+
Example: `PlayersAPI` defines `enum DebouncedOperation { Update }` and inherits from `DebouncedAPI<PlayersAPI.DebouncedOperation>`. When `Player.SetProp()` is called, it calls `Debounce(DebouncedOperation.Update)`, which queues the update to be executed after `debounceTimerSeconds` (default: 1s). Multiple property changes within the debounce window result in a single API call.
60+
4961
## Common Development Commands
5062

5163
### Running Tests

0 commit comments

Comments
 (0)