Skip to content

Commit a65fdb0

Browse files
authored
Merge pull request #169 from TaloDev/develop
Release 0.50.0
2 parents cf984d4 + 55a2031 commit a65fdb0

File tree

24 files changed

+540
-90
lines changed

24 files changed

+540
-90
lines changed

.github/workflows/claude-code-review.yml

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ jobs:
2121
runs-on: ubuntu-latest
2222
permissions:
2323
contents: read
24-
pull-requests: read
25-
issues: read
24+
pull-requests: write
2625
id-token: write
2726

2827
steps:
@@ -37,22 +36,67 @@ jobs:
3736
with:
3837
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
3938
prompt: |
40-
Please review this pull request and provide feedback on:
41-
- Code quality and best practices
42-
- Potential bugs or issues
43-
- Performance considerations
44-
- Security concerns
45-
- Backwards compatibility
46-
47-
For each of the points above, do not point out what works well, only what could be improved (if anything).
48-
Be constructive and helpful in your feedback but do not repeat yourself - only summarise potential issues.
49-
Test coverage is currently not a priority.
50-
Prefix section headers with emojis and use dividers for better readability.
51-
52-
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
53-
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
54-
55-
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
56-
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
57-
claude_args: '--model sonnet --allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
58-
use_sticky_comment: true
39+
You are reviewing PR #${{ github.event.pull_request.number }} in the repository ${{ github.repository }}.
40+
41+
Review this pull request and provide feedback focused only on improvements needed (not what works well):
42+
43+
**Categories to check:**
44+
1. Code quality and best practices
45+
2. Potential bugs or issues
46+
3. Performance considerations
47+
4. Security concerns
48+
5. Backwards compatibility
49+
50+
**Process:**
51+
- The PR number is: ${{ github.event.pull_request.number }}
52+
- View the PR using: `gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }}`
53+
- Read CLAUDE.md to understand best practices
54+
- View the PR diff using: `gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }}`
55+
- If an issue spans multiple categories, list it only once in the most relevant section
56+
- Prioritize by severity: 🔴 Critical → 🟡 Major → 🔵 Minor
57+
- Focus only on changes introduced in this PR, not pre-existing code issues
58+
- Test coverage is currently not a priority
59+
60+
**Review Workflow (Follow these steps):**
61+
1. **Analysis Phase**: Review the PR diff and identify potential issues
62+
2. **Validation Phase**: For each issue you find, verify it by:
63+
- Re-reading the relevant code carefully
64+
- Checking if your suggested fix is actually different from the current code
65+
- Confirming the issue violates documented standards (check CLAUDE.md)
66+
- Ensuring your criticism is actionable and specific
67+
3. **Draft Phase**: Write your review only after validating all issues
68+
4. **Quality Check**: Before posting, remove any issues where:
69+
- Your "before" and "after" code snippets are identical
70+
- You're uncertain or use phrases like "appears", "might", "should verify"
71+
- The issue is theoretical without clear impact
72+
5. **Post Phase**: Only post the review if you have concrete, validated feedback
73+
74+
**Edge Case Policy:**
75+
Only flag edge cases that meet ALL of these criteria:
76+
1. Realistic: Could happen in normal usage or common error scenarios
77+
2. Impactful: Would cause bugs, security issues, or data problems (not just "it's not perfect")
78+
3. Actionable: Can be fixed with reasonable effort in this PR's scope
79+
80+
Ignore theoretical issues that require multiple unlikely conditions or malicious input patterns.
81+
Use the "would this bother a pragmatic senior developer?" test.
82+
83+
Maximum chain of assumptions: 2 levels deep. Skip exotic input combinations that violate documented assumptions.
84+
85+
**Feedback style:**
86+
- Provide specific code examples or line references showing the issue
87+
- Suggest concrete fixes with code snippets where helpful
88+
- Keep total feedback under 500 words
89+
- Use section headers with emojis and horizontal dividers (---)
90+
- If no improvements needed in a category, simply state "No issues found"
91+
- Use neutral language; focus on the code, not the author
92+
- If the PR looks good overall, say so clearly rather than forcing criticism
93+
94+
**Comment Management (IMPORTANT):**
95+
Post your review using this command, which will edit your last comment if one exists, or create a new one:
96+
```bash
97+
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --edit-last --create-if-none --body "<review>"
98+
```
99+
100+
Ensure proper escaping of quotes and special characters in the comment body. Use single quotes around the body and escape any single quotes inside with '\''
101+
claude_args: |
102+
--allowedTools "Read,Bash(gh pr:*),Grep,Glob"

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files.exclude": {
44
"**/*.meta": true
55
},
6-
"dotnet.defaultSolution": "unity.sln",
6+
"dotnet.defaultSolution": "unity.slnx",
77
"editor.tabSize": 4,
88
"editor.indentSize": "tabSize"
99
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace TaloGameServices
99
public class BaseAPI
1010
{
1111
// automatically updated with a pre-commit hook
12-
private const string ClientVersion = "0.49.0";
12+
private const string ClientVersion = "0.50.0";
1313

1414
protected string baseUrl;
1515

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/EventsAPI.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ public async Task Flush()
8080
{
8181
Talo.IdentityCheck();
8282

83-
var eventsToSend = queue.ToArray();
84-
if (eventsToSend.Length == 0)
83+
if (lockFlushes)
8584
{
85+
flushAttemptedDuringLock = true;
8686
return;
8787
}
8888

89-
if (lockFlushes)
89+
var eventsToSend = queue.ToArray();
90+
if (eventsToSend.Length == 0)
9091
{
91-
flushAttemptedDuringLock = true;
9292
return;
9393
}
9494

@@ -103,14 +103,16 @@ public async Task Flush()
103103

104104
await Call(uri, "POST", content);
105105
OnFlushed.Invoke();
106-
107-
eventsToFlush.Clear();
108-
lockFlushes = false;
109106
}
110107
catch (Exception ex)
111108
{
112109
Debug.LogError(ex.Message);
113110
}
111+
finally
112+
{
113+
eventsToFlush.Clear();
114+
lockFlushes = false;
115+
}
114116

115117
if (flushAttemptedDuringLock)
116118
{

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public enum HealthCheckStatus
1414
public class HealthCheckAPI : BaseAPI
1515
{
1616
private HealthCheckStatus lastHealthCheckStatus = HealthCheckStatus.UNKNOWN;
17+
private float nextPingTime;
1718

1819
public HealthCheckAPI() : base("v1/health-check") { }
1920

@@ -24,6 +25,12 @@ public HealthCheckStatus GetLastStatus()
2425

2526
public async Task<bool> Ping()
2627
{
28+
var bustCache = lastHealthCheckStatus == HealthCheckStatus.UNKNOWN || Time.realtimeSinceStartup >= nextPingTime;
29+
if (!bustCache)
30+
{
31+
return lastHealthCheckStatus == HealthCheckStatus.OK;
32+
}
33+
2734
var uri = new Uri(baseUrl);
2835

2936
bool success;
@@ -57,6 +64,8 @@ public async Task<bool> Ping()
5764
}
5865
}
5966

67+
nextPingTime = Time.realtimeSinceStartup + Talo.Settings.debounceTimerSeconds;
68+
6069
return success;
6170
}
6271
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public async Task<LeaderboardEntriesResponse> GetEntriesForCurrentPlayer(string
110110
var json = await Call(uri, "POST", Prop.SanitiseJson(content));
111111

112112
var res = JsonUtility.FromJson<LeaderboardEntryResponse>(json);
113-
_entriesManager.UpsertEntry(internalName, res.entry);
113+
_entriesManager.UpsertEntry(internalName, res.entry, true);
114114

115115
return (res.entry, res.updated);
116116
}

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 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: 41 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,43 @@ 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+
var currentSave = savesManager.CurrentSave;
200+
if (currentSave != null)
201+
{
202+
await UpdateSave(currentSave.id);
203+
}
204+
break;
205+
}
206+
}
207+
208+
public void DebounceUpdate()
209+
{
210+
Debounce(DebouncedOperation.Update);
211+
}
212+
189213
public async Task<GameSave> UpdateCurrentSave(string newName = "")
190214
{
191-
return await UpdateSave(savesManager.CurrentSave.id, newName);
215+
var currentSave = savesManager.CurrentSave;
216+
if (currentSave == null)
217+
{
218+
throw new Exception("No save is currently loaded");
219+
}
220+
221+
// if the save is being renamed, sync it immediately
222+
if (!string.IsNullOrEmpty(newName))
223+
{
224+
return await UpdateSave(currentSave.id, newName);
225+
}
226+
227+
// else, update the save locally and queue it for syncing
228+
currentSave.content = contentManager.Content;
229+
DebounceUpdate();
230+
return currentSave;
192231
}
193232

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

0 commit comments

Comments
 (0)