Skip to content

Commit cf710b2

Browse files
authored
Merge pull request #19 from kwan3854/v2.0.5
Switches to UUID request IDs and simplifies chunk management
2 parents 2253d64 + 733f18c commit cf710b2

File tree

12 files changed

+81
-69
lines changed

12 files changed

+81
-69
lines changed

Packages/WebviewRpc/Runtime/ChunkAssembler.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ private class ChunkSet
1616
public int TotalChunks { get; set; }
1717
public int OriginalSize { get; set; }
1818
public DateTime LastActivity { get; set; }
19-
public string RequestId { get; set; }
2019
}
2120

22-
private readonly Dictionary<string, ChunkSet> _chunkSets = new();
21+
private readonly Dictionary<string, ChunkSet> _chunkSetsByRequest = new();
2322
private readonly object _lock = new object();
2423

2524
/// <summary>
@@ -46,31 +45,31 @@ public byte[] TryAssemble(RpcEnvelope envelope, out List<string> timedOutRequest
4645
lock (_lock)
4746
{
4847
var chunkInfo = envelope.ChunkInfo;
48+
var requestId = envelope.RequestId;
4949

50-
// Check if we've reached the maximum number of chunk sets
51-
if (!_chunkSets.ContainsKey(chunkInfo.ChunkSetId) &&
52-
_chunkSets.Count >= WebViewRpcConfiguration.MaxConcurrentChunkSets)
50+
// Check if we've reached the maximum number of concurrent requests
51+
if (!_chunkSetsByRequest.ContainsKey(requestId) &&
52+
_chunkSetsByRequest.Count >= WebViewRpcConfiguration.MaxConcurrentChunkSets)
5353
{
54-
// Remove the oldest chunk set
55-
var oldestKey = _chunkSets
54+
// Remove the oldest request
55+
var oldestKey = _chunkSetsByRequest
5656
.OrderBy(kvp => kvp.Value.LastActivity)
5757
.First()
5858
.Key;
59-
_chunkSets.Remove(oldestKey);
59+
_chunkSetsByRequest.Remove(oldestKey);
6060
Debug.LogWarning(
61-
$"Maximum chunk sets reached ({WebViewRpcConfiguration.MaxConcurrentChunkSets}). Removed oldest chunk set {oldestKey}");
61+
$"Maximum concurrent requests reached ({WebViewRpcConfiguration.MaxConcurrentChunkSets}). Removed oldest request {oldestKey}");
6262
}
6363

64-
if (!_chunkSets.TryGetValue(chunkInfo.ChunkSetId, out var chunkSet))
64+
if (!_chunkSetsByRequest.TryGetValue(requestId, out var chunkSet))
6565
{
6666
chunkSet = new ChunkSet
6767
{
6868
TotalChunks = chunkInfo.TotalChunks,
6969
OriginalSize = chunkInfo.OriginalSize,
70-
LastActivity = DateTime.UtcNow,
71-
RequestId = envelope.RequestId
70+
LastActivity = DateTime.UtcNow
7271
};
73-
_chunkSets[chunkInfo.ChunkSetId] = chunkSet;
72+
_chunkSetsByRequest[requestId] = chunkSet;
7473
}
7574

7675
// Add chunk
@@ -88,7 +87,7 @@ public byte[] TryAssemble(RpcEnvelope envelope, out List<string> timedOutRequest
8887
{
8988
if (!chunkSet.Chunks.TryGetValue(i, out var chunk))
9089
{
91-
Debug.LogError($"Missing chunk {i} in set {chunkInfo.ChunkSetId}");
90+
Debug.LogError($"Missing chunk {i} for request {requestId}");
9291
return null;
9392
}
9493

@@ -97,7 +96,7 @@ public byte[] TryAssemble(RpcEnvelope envelope, out List<string> timedOutRequest
9796
}
9897

9998
// Clean up
100-
_chunkSets.Remove(chunkInfo.ChunkSetId);
99+
_chunkSetsByRequest.Remove(requestId);
101100

102101
// Verify size
103102
if (offset != chunkSet.OriginalSize)
@@ -109,18 +108,18 @@ public byte[] TryAssemble(RpcEnvelope envelope, out List<string> timedOutRequest
109108
return result;
110109
}
111110

112-
// Cleanup old chunk sets (older than configured timeout)
111+
// Cleanup old requests (older than configured timeout)
113112
var cutoff = DateTime.UtcNow.AddSeconds(-WebViewRpcConfiguration.ChunkTimeoutSeconds);
114-
var toRemove = _chunkSets
113+
var toRemove = _chunkSetsByRequest
115114
.Where(kvp => kvp.Value.LastActivity < cutoff)
116115
.ToList();
117116

118117
foreach (var kvp in toRemove)
119118
{
120-
timedOutRequestIds.Add(kvp.Value.RequestId);
121-
_chunkSets.Remove(kvp.Key);
119+
timedOutRequestIds.Add(kvp.Key);
120+
_chunkSetsByRequest.Remove(kvp.Key);
122121
Debug.LogWarning(
123-
$"Removed incomplete chunk set {kvp.Key} for request {kvp.Value.RequestId} due to timeout");
122+
$"Removed incomplete chunks for request {kvp.Key} due to timeout");
124123
}
125124

126125
return null; // Not all chunks received yet

Packages/WebviewRpc/Runtime/IWebViewBridge.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public interface IWebViewBridge : IDisposable
1212
/// <summary>
1313
/// C# to WebView send string
1414
/// </summary>
15-
void SendMessageToWeb(string message);
15+
public void SendMessageToWeb(string message);
1616

1717
/// <summary>
1818
/// JS to C# receive string
1919
/// </summary>
20-
event Action<string> OnMessageReceived;
20+
public event Action<string> OnMessageReceived;
2121
}
2222
}

Packages/WebviewRpc/Runtime/WebViewRpcClient.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ namespace WebViewRPC
1212
/// </summary>
1313
public class WebViewRpcClient : IDisposable
1414
{
15+
private bool _disposed;
16+
1517
private readonly IWebViewBridge _bridge;
1618
private readonly Dictionary<string, UniTaskCompletionSource<RpcEnvelope>> _pendingRequests = new();
1719
private readonly ChunkAssembler _chunkAssembler = new();
18-
private int _requestIdCounter = 1;
20+
private readonly CancellationTokenSource _cancellationTokenSource = new();
1921
private readonly object _pendingRequestsLock = new object();
20-
private bool _disposed;
21-
private CancellationTokenSource _cancellationTokenSource = new();
22+
2223

2324
public WebViewRpcClient(IWebViewBridge bridge)
2425
{
@@ -39,7 +40,7 @@ public async UniTask<TResponse> CallMethod<TResponse>(string method, IMessage re
3940
throw new ObjectDisposedException(nameof(WebViewRpcClient), "Cannot call method on disposed client");
4041
}
4142

42-
var requestId = Interlocked.Increment(ref _requestIdCounter).ToString();
43+
var requestId = Guid.NewGuid().ToString("N");
4344
var requestBytes = request.ToByteArray();
4445

4546
var tcs = new UniTaskCompletionSource<RpcEnvelope>();
@@ -99,7 +100,6 @@ private async UniTask SendChunkedMessage(string requestId, string method, byte[]
99100
// Check disposed state
100101
if (_disposed || _cancellationTokenSource.Token.IsCancellationRequested) return;
101102

102-
var chunkSetId = $"{requestId}_{Guid.NewGuid():N}";
103103
int effectivePayloadSize = WebViewRpcConfiguration.GetEffectivePayloadSize();
104104
var totalChunks = (int)Math.Ceiling((double)data.Length / effectivePayloadSize);
105105

@@ -121,7 +121,7 @@ private async UniTask SendChunkedMessage(string requestId, string method, byte[]
121121
Payload = ByteString.CopyFrom(chunkData),
122122
ChunkInfo = new ChunkInfo
123123
{
124-
ChunkSetId = chunkSetId,
124+
ChunkSetId = "", // Not used anymore, but required by protobuf
125125
ChunkIndex = i,
126126
TotalChunks = totalChunks,
127127
OriginalSize = data.Length

Packages/WebviewRpc/Runtime/WebViewRpcServer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ namespace WebViewRPC
99
{
1010
public class WebViewRpcServer : IDisposable
1111
{
12+
private bool _disposed;
13+
1214
private readonly IWebViewBridge _bridge;
1315
private readonly ChunkAssembler _chunkAssembler = new();
14-
private bool _disposed;
15-
private CancellationTokenSource _cancellationTokenSource = new();
16+
private readonly CancellationTokenSource _cancellationTokenSource = new();
1617

1718
/// <summary>
1819
/// You can add multiple services to the server.
@@ -202,7 +203,6 @@ private async UniTask SendChunkedMessage(string requestId, string method, byte[]
202203
// Check disposed state before starting
203204
if (_disposed || cancellationToken.IsCancellationRequested) return;
204205

205-
var chunkSetId = $"{requestId}_{Guid.NewGuid():N}";
206206
int effectivePayloadSize = WebViewRpcConfiguration.GetEffectivePayloadSize();
207207
var totalChunks = (int)Math.Ceiling((double)data.Length / effectivePayloadSize);
208208

@@ -224,7 +224,7 @@ private async UniTask SendChunkedMessage(string requestId, string method, byte[]
224224
Payload = ByteString.CopyFrom(chunkData),
225225
ChunkInfo = new ChunkInfo
226226
{
227-
ChunkSetId = chunkSetId,
227+
ChunkSetId = "",
228228
ChunkIndex = i,
229229
TotalChunks = totalChunks,
230230
OriginalSize = data.Length

Packages/WebviewRpc/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "com.kwanjoong.webviewrpc",
3-
"version": "2.0.4",
3+
"version": "2.0.5",
44
"displayName": "Webview RPC",
55
"description": "The webview Remote Procedure Call bridge.",
66
"unity": "2022.3",
@@ -20,7 +20,7 @@
2020
"url": "https://github.com/kwan3854/Unity-WebviewRpc.git"
2121
},
2222
"dependencies": {
23-
23+
"com.cysharp.unitask": "2.5.10"
2424
},
2525
"license": "MIT"
2626
}

Packages/packages-lock.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"version": "file:WebviewRpc",
1919
"depth": 0,
2020
"source": "embedded",
21-
"dependencies": {}
21+
"dependencies": {
22+
"com.cysharp.unitask": "2.5.10"
23+
}
2224
},
2325
"com.unity.ai.navigation": {
2426
"version": "2.0.5",

webview_rpc_runtime_library/CHANGELOG.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [2.0.9] - 2025-01-22
8+
## [2.0.11] - 2025-07-01
9+
### Fixed
10+
- Corrected release dates in CHANGELOG.md
11+
12+
## [2.0.10] - 2025-07-01
13+
### Changed
14+
- **BREAKING**: RequestId generation changed from incremental integer to UUID/GUID for true uniqueness across multiple WebView instances
15+
- Simplified chunk management by removing ChunkSetId dependency and using RequestId directly
16+
- Improved performance by eliminating unnecessary string concatenation operations
17+
- ChunkAssembler now manages chunks by RequestId instead of ChunkSetId
18+
19+
### Fixed
20+
- Fixed potential RequestId collisions when multiple WebView instances are used simultaneously
21+
22+
## [2.0.9] - 2025-06-30
923

1024
### Added
1125
- `maxConcurrentChunkSets` configuration option (default: 100, min: 10, max: 1000)
@@ -22,7 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2236
- ChunkAssembler now returns timed out request IDs for proper cleanup
2337
- Both Unity and JavaScript implementations now handle chunk timeouts consistently
2438

25-
## [2.0.8] - 2025-01-22
39+
## [2.0.8] - 2025-06-30
2640

2741
### Added
2842
- Proper dispose pattern for JavaScript components
@@ -41,7 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4155
- Memory leaks from unremoved event listeners
4256
- Potential resource leaks when page navigates away
4357

44-
## [2.0.7] - 2025-01-22
58+
## [2.0.7] - 2025-06-30
4559

4660
### Fixed
4761
- Improved error handling: errors now take precedence over payload
@@ -52,7 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5266
- Error responses can now include payload data if available
5367
- Clearer error messages when methods return null without setting an error
5468

55-
## [2.0.6] - 2025-01-22
69+
## [2.0.6] - 2025-06-30
5670

5771
### Added
5872
- Smart chunk size calculation that accounts for RPC envelope and Base64 encoding overhead

webview_rpc_runtime_library/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "app-webview-rpc",
3-
"version": "2.0.9",
3+
"version": "2.0.11",
44
"type": "module",
55
"description": "WebView RPC provides an abstraction layer that allows communication between the App (e.g. Unity C#) and WebView (HTML, JS) using protobuf, similar to gRPC.",
66
"main": "./dist/cjs/index.js",

webview_rpc_runtime_library/src/core/chunk_assembler.js

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { WebViewRpcConfiguration } from './webview_rpc_configuration.js';
55
*/
66
export class ChunkAssembler {
77
constructor() {
8-
this._chunkSets = new Map();
8+
this._chunkSetsByRequest = new Map();
99
}
1010

1111
/**
@@ -22,39 +22,38 @@ export class ChunkAssembler {
2222
}
2323

2424
const chunkInfo = envelope.chunkInfo;
25-
const chunkSetId = chunkInfo.chunkSetId;
25+
const requestId = envelope.requestId;
2626

27-
// Check if we've reached the maximum number of chunk sets
28-
if (!this._chunkSets.has(chunkSetId) &&
29-
this._chunkSets.size >= WebViewRpcConfiguration.maxConcurrentChunkSets) {
30-
// Remove the oldest chunk set
27+
// Check if we've reached the maximum number of concurrent requests
28+
if (!this._chunkSetsByRequest.has(requestId) &&
29+
this._chunkSetsByRequest.size >= WebViewRpcConfiguration.maxConcurrentChunkSets) {
30+
// Remove the oldest request
3131
let oldestKey = null;
3232
let oldestTime = Date.now();
3333

34-
for (const [key, value] of this._chunkSets) {
34+
for (const [key, value] of this._chunkSetsByRequest) {
3535
if (value.lastActivity < oldestTime) {
3636
oldestTime = value.lastActivity;
3737
oldestKey = key;
3838
}
3939
}
4040

4141
if (oldestKey) {
42-
this._chunkSets.delete(oldestKey);
43-
console.warn(`Maximum chunk sets reached (${WebViewRpcConfiguration.maxConcurrentChunkSets}). Removed oldest chunk set ${oldestKey}`);
42+
this._chunkSetsByRequest.delete(oldestKey);
43+
console.warn(`Maximum concurrent requests reached (${WebViewRpcConfiguration.maxConcurrentChunkSets}). Removed oldest request ${oldestKey}`);
4444
}
4545
}
4646

47-
if (!this._chunkSets.has(chunkSetId)) {
48-
this._chunkSets.set(chunkSetId, {
47+
if (!this._chunkSetsByRequest.has(requestId)) {
48+
this._chunkSetsByRequest.set(requestId, {
4949
chunks: new Map(),
5050
totalChunks: chunkInfo.totalChunks,
5151
originalSize: chunkInfo.originalSize,
52-
lastActivity: Date.now(),
53-
requestId: envelope.requestId
52+
lastActivity: Date.now()
5453
});
5554
}
5655

57-
const chunkSet = this._chunkSets.get(chunkSetId);
56+
const chunkSet = this._chunkSetsByRequest.get(requestId);
5857

5958
// Add chunk
6059
const chunkIndex = Number(chunkInfo.chunkIndex);
@@ -70,7 +69,7 @@ export class ChunkAssembler {
7069
for (let i = 1; i <= chunkSet.totalChunks; i++) {
7170
const chunk = chunkSet.chunks.get(i);
7271
if (!chunk) {
73-
console.error(`Missing chunk ${i} in set ${chunkSetId}`);
72+
console.error(`Missing chunk ${i} for request ${requestId}`);
7473
console.error(`Available chunks:`, Array.from(chunkSet.chunks.keys()));
7574
return { data: null, timedOutRequestIds };
7675
}
@@ -80,7 +79,7 @@ export class ChunkAssembler {
8079
}
8180

8281
// Clean up
83-
this._chunkSets.delete(chunkSetId);
82+
this._chunkSetsByRequest.delete(requestId);
8483

8584
// Verify size
8685
if (offset !== chunkSet.originalSize) {
@@ -91,20 +90,20 @@ export class ChunkAssembler {
9190
return { data: result, timedOutRequestIds };
9291
}
9392

94-
// Cleanup old chunk sets (older than configured timeout)
93+
// Cleanup old requests (older than configured timeout)
9594
const cutoff = Date.now() - (WebViewRpcConfiguration.chunkTimeoutSeconds * 1000);
9695
const toRemove = [];
9796

98-
for (const [key, value] of this._chunkSets) {
97+
for (const [key, value] of this._chunkSetsByRequest) {
9998
if (value.lastActivity < cutoff) {
100-
toRemove.push({ key, requestId: value.requestId });
99+
toRemove.push(key);
101100
}
102101
}
103102

104-
for (const { key, requestId } of toRemove) {
103+
for (const requestId of toRemove) {
105104
timedOutRequestIds.push(requestId);
106-
this._chunkSets.delete(key);
107-
console.warn(`Removed incomplete chunk set ${key} for request ${requestId} due to timeout`);
105+
this._chunkSetsByRequest.delete(requestId);
106+
console.warn(`Removed incomplete chunks for request ${requestId} due to timeout`);
108107
}
109108

110109
return { data: null, timedOutRequestIds }; // Not all chunks received yet

0 commit comments

Comments
 (0)