Skip to content

Commit 007b51d

Browse files
authored
Merge pull request #149 from eesast/interface-fixes
Stabilize browser playback after truncated replay loads
2 parents c164774 + b9e20ba commit 007b51d

13 files changed

Lines changed: 471 additions & 106 deletions

File tree

interface/Unity/Unity-Live/Packages/com.eesast.interface.shared/Runtime/Scripts/Core/CoreParam.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,14 @@ private static void DestroyAll<TKey>(Dictionary<TKey, GameObject> objects)
187187
{
188188
if (kvp.Value != null)
189189
{
190-
UnityEngine.Object.DestroyImmediate(kvp.Value);
190+
if (Application.isPlaying)
191+
{
192+
UnityEngine.Object.Destroy(kvp.Value);
193+
}
194+
else
195+
{
196+
UnityEngine.Object.DestroyImmediate(kvp.Value);
197+
}
191198
}
192199
}
193200

interface/Unity/Unity-Live/Packages/com.eesast.interface.shared/Runtime/Scripts/Render/RenderManager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ namespace THUAI9.Unity.Render
1212
{
1313
public class RenderManager : SingletonMono<RenderManager>
1414
{
15-
[Header("瀵瑰眬鏃堕棿")]
15+
[Header("对局时间")]
1616
public Text gameTimeText;
1717

18-
[Header("闃熶紞淇℃伅")]
18+
[Header("队伍信息")]
1919
public Text[] teamScoreTexts = new Text[4];
2020

21-
[Header("璋冭瘯淇℃伅")]
21+
[Header("调试信息")]
2222
public Text fpsText;
2323
public bool autoBindSceneReferences = true;
2424
public bool showWorldLabels = true;

interface/Unity/Unity-Playback/Assets/Editor/WebGLBuildScript.cs

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,32 @@ private static void PatchIndexHtml(string output)
1818
if (!File.Exists(indexPath)) return;
1919

2020
string html = File.ReadAllText(indexPath, Encoding.UTF8);
21-
const string helper = @" var thuai9Mode = 'playback';
21+
string buildStamp = File.GetLastWriteTimeUtc(Path.Combine(output, "Build", "playback.framework.js")).Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture);
22+
string helper = @" var thuai9Mode = 'playback';
23+
var thuai9BuildStamp = '__THUAI9_BUILD_STAMP__';
2224
var thuai9Query = new URLSearchParams(window.location.search);
25+
function thuai9CacheBust(url) {
26+
return url + (url.indexOf('?') >= 0 ? '&' : '?') + 'v=' + encodeURIComponent(thuai9BuildStamp);
27+
}
2328
function thuai9SetUnityInstance(unityInstance) {
2429
window.unityInstance = unityInstance;
2530
window.THUAIGameInstance = unityInstance;
2631
window.gameInstance = unityInstance;
2732
}
33+
function thuai9RunLater(callback) {
34+
if (typeof window.setTimeout === 'function') { window.setTimeout(callback, 0); return; }
35+
callback();
36+
}
37+
function thuai9DispatchLater(eventName, detail) {
38+
thuai9RunLater(function () {
39+
window.dispatchEvent(new CustomEvent(eventName, { detail: detail }));
40+
});
41+
}
2842
function thuai9RunWhenReady(callback) {
29-
if (window.THUAI9Unity) { callback(window.THUAI9Unity); return; }
30-
window.addEventListener('thuai9-unity-ready', function () { callback(window.THUAI9Unity); }, { once: true });
43+
if (window.THUAI9Unity) { thuai9RunLater(function () { callback(window.THUAI9Unity); }); return; }
44+
window.addEventListener('thuai9-unity-ready', function () {
45+
thuai9RunLater(function () { callback(window.THUAI9Unity); });
46+
}, { once: true });
3147
}
3248
function thuai9Bool(value) {
3349
return value === '1' || value === 'true' || value === 'yes' || value === 'on';
@@ -77,15 +93,46 @@ function waitThenApply() {
7793
applyPostLoad();
7894
}
7995
80-
";
96+
".Replace("__THUAI9_BUILD_STAMP__", buildStamp);
8197
const string instanceHook = @" thuai9SetUnityInstance(unityInstance);
82-
window.dispatchEvent(new CustomEvent('thuai9-unity-instance-created', { detail: { mode: thuai9Mode } }));
98+
thuai9DispatchLater('thuai9-unity-instance-created', { mode: thuai9Mode });
8399
thuai9RunWhenReady(thuai9ApplyPlaybackOptions);
84100
";
85101

86-
if (!html.Contains("var thuai9Mode ="))
102+
const string buildUrlLine = " var buildUrl = \"Build\";";
103+
const string helperStart = " var thuai9Mode = 'playback';";
104+
105+
if (html.Contains(helperStart))
106+
{
107+
int start = html.IndexOf(helperStart, System.StringComparison.Ordinal);
108+
int end = html.IndexOf(buildUrlLine, start, System.StringComparison.Ordinal);
109+
if (start >= 0 && end > start)
110+
{
111+
html = html.Remove(start, end - start).Insert(start, helper);
112+
}
113+
}
114+
else if (html.Contains(buildUrlLine))
115+
{
116+
html = html.Replace(buildUrlLine, helper + buildUrlLine);
117+
}
118+
119+
html = html.Replace(
120+
" var loaderUrl = buildUrl + \"/playback.loader.js\";",
121+
" var loaderUrl = thuai9CacheBust(buildUrl + \"/playback.loader.js\");");
122+
html = html.Replace(
123+
" dataUrl: buildUrl + \"/playback.data\",",
124+
" dataUrl: thuai9CacheBust(buildUrl + \"/playback.data\"),");
125+
html = html.Replace(
126+
" frameworkUrl: buildUrl + \"/playback.framework.js\",",
127+
" frameworkUrl: thuai9CacheBust(buildUrl + \"/playback.framework.js\"),");
128+
html = html.Replace(
129+
" codeUrl: buildUrl + \"/playback.wasm\",",
130+
" codeUrl: thuai9CacheBust(buildUrl + \"/playback.wasm\"),");
131+
132+
const string oldInstanceEvent = " window.dispatchEvent(new CustomEvent('thuai9-unity-instance-created', { detail: { mode: thuai9Mode } }));";
133+
if (html.Contains(oldInstanceEvent))
87134
{
88-
html = html.Replace(" var buildUrl = \"Build\";", helper + " var buildUrl = \"Build\";");
135+
html = html.Replace(oldInstanceEvent, " thuai9DispatchLater('thuai9-unity-instance-created', { mode: thuai9Mode });");
89136
}
90137

91138
if (!html.Contains("thuai9SetUnityInstance(unityInstance);"))

interface/Unity/Unity-Playback/Assets/Plugins/WebGL/THUAI9WebGLBridge.jslib

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
mergeInto(LibraryManager.library, {
22
$THUAI9WebGLBridge: {
33
maxBase64Bytes: 16 * 1024 * 1024,
4+
maxRemotePlaybackBytes: 64 * 1024 * 1024,
5+
activePlaybackObjectUrl: null,
6+
objectUrlCleanupRegistered: false,
47
sendMessage: function (gameObjectName, methodName, payload) {
58
if (typeof SendMessage === 'function') { SendMessage(gameObjectName, methodName, payload || ''); return; }
69
var unityInstance = window.unityInstance || window.THUAIGameInstance || window.gameInstance || (typeof Module !== 'undefined' ? Module : null);
710
if (unityInstance && typeof unityInstance.SendMessage === 'function') { unityInstance.SendMessage(gameObjectName, methodName, payload || ''); return; }
811
console.error('[THUAI9] Unity SendMessage is not available', { gameObjectName: gameObjectName, methodName: methodName });
912
},
13+
sendMessageLater: function (gameObjectName, methodName, payload) {
14+
var send = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, methodName, payload || ''); };
15+
if (typeof window.setTimeout === 'function') { window.setTimeout(send, 0); return; }
16+
send();
17+
},
1018
arrayBufferToBase64: function (arrayBuffer) {
1119
var bytes = new Uint8Array(arrayBuffer), chunkSize = 0x8000, binary = '';
1220
for (var i = 0; i < bytes.length; i += chunkSize) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize));
@@ -35,53 +43,90 @@ mergeInto(LibraryManager.library, {
3543
dispatchCustomEvent: function (eventName, detail) {
3644
if (typeof window.CustomEvent === 'function') { window.dispatchEvent(new CustomEvent(eventName, { detail: detail })); return; }
3745
var event = document.createEvent('CustomEvent'); event.initCustomEvent(eventName, false, false, detail); window.dispatchEvent(event);
46+
},
47+
dispatchCustomEventLater: function (eventName, detail) {
48+
var dispatch = function () { THUAI9WebGLBridge.dispatchCustomEvent(eventName, detail); };
49+
if (typeof window.setTimeout === 'function') { window.setTimeout(dispatch, 0); return; }
50+
dispatch();
51+
},
52+
revokeActivePlaybackObjectUrl: function () {
53+
if (!THUAI9WebGLBridge.activePlaybackObjectUrl) return;
54+
try { URL.revokeObjectURL(THUAI9WebGLBridge.activePlaybackObjectUrl); } catch (_) {}
55+
THUAI9WebGLBridge.activePlaybackObjectUrl = null;
56+
},
57+
ensureObjectUrlCleanup: function () {
58+
if (THUAI9WebGLBridge.objectUrlCleanupRegistered || typeof window.addEventListener !== 'function') return;
59+
THUAI9WebGLBridge.objectUrlCleanupRegistered = true;
60+
window.addEventListener('beforeunload', function () {
61+
THUAI9WebGLBridge.revokeActivePlaybackObjectUrl();
62+
});
3863
}
3964
},
4065
THUAI9_SelectPlaybackFile__deps: ['$THUAI9WebGLBridge'],
4166
THUAI9_SelectPlaybackFile: function (gameObjectNamePtr, callbackNamePtr) {
4267
var gameObjectName = UTF8ToString(gameObjectNamePtr); var callbackName = UTF8ToString(callbackNamePtr);
4368
var input = document.createElement('input'); input.type = 'file'; input.accept = '.thuaipb,application/octet-stream'; input.style.display = 'none';
44-
input.onchange = function () { var file = input.files && input.files[0]; if (!file) { input.remove(); return; } var url = URL.createObjectURL(file); var payload = JSON.stringify({ url: url, name: file.name || 'playback.thuaipb', size: file.size || 0 }); THUAI9WebGLBridge.sendMessage(gameObjectName, callbackName, payload); THUAI9WebGLBridge.dispatchCustomEvent('thuai9-playback-file-selected', { url: url, name: file.name, size: file.size }); input.remove(); };
69+
input.onchange = function () {
70+
var file = input.files && input.files[0];
71+
if (!file) { input.remove(); return; }
72+
var size = file.size || 0;
73+
var name = file.name || 'playback.thuaipb';
74+
if (size > THUAI9WebGLBridge.maxRemotePlaybackBytes) {
75+
THUAI9WebGLBridge.dispatchCustomEvent('thuai9-playback-error', 'file-too-large:' + size);
76+
input.remove();
77+
return;
78+
}
79+
80+
THUAI9WebGLBridge.revokeActivePlaybackObjectUrl();
81+
THUAI9WebGLBridge.ensureObjectUrlCleanup();
82+
var url = URL.createObjectURL(file);
83+
THUAI9WebGLBridge.activePlaybackObjectUrl = url;
84+
var payload = JSON.stringify({ url: url, name: name, size: size });
85+
THUAI9WebGLBridge.sendMessageLater(gameObjectName, callbackName, payload);
86+
THUAI9WebGLBridge.dispatchCustomEventLater('thuai9-playback-file-selected', { url: url, name: name, size: size });
87+
input.remove();
88+
};
4589
document.body.appendChild(input); input.click();
4690
},
4791
THUAI9_ClearDevelopmentConsole: function () {
4892
if (typeof console !== 'undefined' && typeof console.clear === 'function') console.clear();
4993
},
5094
THUAI9_NotifyUnityReady__deps: ['$THUAI9WebGLBridge'],
5195
THUAI9_NotifyUnityReady: function (gameObjectNamePtr) {
96+
THUAI9WebGLBridge.ensureObjectUrlCleanup();
5297
var gameObjectName = UTF8ToString(gameObjectNamePtr); window.THUAI9Unity = window.THUAI9Unity || {}; window.THUAI9Unity.gameObjectName = gameObjectName;
53-
window.THUAI9Unity.sendMessage = function (methodName, payload) { THUAI9WebGLBridge.sendMessage(gameObjectName, methodName, payload || ''); };
98+
window.THUAI9Unity.sendMessage = function (methodName, payload) { THUAI9WebGLBridge.sendMessageLater(gameObjectName, methodName, payload || ''); };
5499
window.THUAI9Unity.setPlaybackFile = function (url, name) {
55100
var selection = (url && typeof url === 'object')
56101
? url
57102
: { url: THUAI9WebGLBridge.normalizeUrl(url), name: name || url };
58103
if (selection.url) selection.url = THUAI9WebGLBridge.normalizeUrl(selection.url);
59-
THUAI9WebGLBridge.sendMessage(gameObjectName, 'SetPlaybackFile', JSON.stringify(selection));
104+
THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'SetPlaybackFile', JSON.stringify(selection));
60105
};
61106
window.THUAI9Unity.loadPlaybackBase64 = function (base64, name) {
62107
var normalized = THUAI9WebGLBridge.normalizeBase64(base64);
63108
var estimatedBytes = THUAI9WebGLBridge.estimateBase64ByteCount(normalized);
64109
if (estimatedBytes > THUAI9WebGLBridge.maxBase64Bytes) {
65-
THUAI9WebGLBridge.dispatchCustomEvent('thuai9-playback-error', 'base64-too-large:' + estimatedBytes);
110+
THUAI9WebGLBridge.dispatchCustomEventLater('thuai9-playback-error', 'base64-too-large:' + estimatedBytes);
66111
return;
67112
}
68-
THUAI9WebGLBridge.sendMessage(gameObjectName, 'LoadPlaybackBase64', JSON.stringify({ data: normalized, name: name || 'playback.thuaipb' }));
113+
THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'LoadPlaybackBase64', JSON.stringify({ data: normalized, name: name || 'playback.thuaipb' }));
69114
};
70-
window.THUAI9Unity.requestPlaybackFile = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'RequestPlaybackFile', ''); };
71-
window.THUAI9Unity.play = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'PlayPlayback', ''); };
72-
window.THUAI9Unity.pause = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'PausePlayback', ''); };
73-
window.THUAI9Unity.togglePlayPause = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'TogglePlayback', ''); };
74-
window.THUAI9Unity.stop = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'StopPlayback', ''); };
75-
window.THUAI9Unity.seekToFrame = function (frameIndex) { THUAI9WebGLBridge.sendMessage(gameObjectName, 'SeekPlaybackFrame', String(frameIndex || 0)); };
76-
window.THUAI9Unity.setSpeed = function (speed) { THUAI9WebGLBridge.sendMessage(gameObjectName, 'SetPlaybackSpeed', String(speed || 1)); };
77-
window.THUAI9Unity.stepForward = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'StepPlaybackForward', ''); };
78-
window.THUAI9Unity.stepBackward = function () { THUAI9WebGLBridge.sendMessage(gameObjectName, 'StepPlaybackBackward', ''); };
79-
THUAI9WebGLBridge.dispatchCustomEvent('thuai9-unity-ready', { gameObjectName: gameObjectName, mode: 'playback' });
115+
window.THUAI9Unity.requestPlaybackFile = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'RequestPlaybackFile', ''); };
116+
window.THUAI9Unity.play = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'PlayPlayback', ''); };
117+
window.THUAI9Unity.pause = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'PausePlayback', ''); };
118+
window.THUAI9Unity.togglePlayPause = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'TogglePlayback', ''); };
119+
window.THUAI9Unity.stop = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'StopPlayback', ''); };
120+
window.THUAI9Unity.seekToFrame = function (frameIndex) { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'SeekPlaybackFrame', String(frameIndex || 0)); };
121+
window.THUAI9Unity.setSpeed = function (speed) { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'SetPlaybackSpeed', String(speed || 1)); };
122+
window.THUAI9Unity.stepForward = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'StepPlaybackForward', ''); };
123+
window.THUAI9Unity.stepBackward = function () { THUAI9WebGLBridge.sendMessageLater(gameObjectName, 'StepPlaybackBackward', ''); };
124+
THUAI9WebGLBridge.dispatchCustomEventLater('thuai9-unity-ready', { gameObjectName: gameObjectName, mode: 'playback' });
80125
},
81126
THUAI9_DispatchUnityEvent__deps: ['$THUAI9WebGLBridge'],
82127
THUAI9_DispatchUnityEvent: function (eventNamePtr, payloadPtr) {
83128
var eventName = UTF8ToString(eventNamePtr); var payload = UTF8ToString(payloadPtr);
84-
THUAI9WebGLBridge.dispatchCustomEvent('thuai9-unity-event', { eventName: eventName, payload: payload });
85-
THUAI9WebGLBridge.dispatchCustomEvent('thuai9-' + eventName, payload);
129+
THUAI9WebGLBridge.dispatchCustomEventLater('thuai9-unity-event', { eventName: eventName, payload: payload });
130+
THUAI9WebGLBridge.dispatchCustomEventLater('thuai9-' + eventName, payload);
86131
}
87132
});

0 commit comments

Comments
 (0)