@uzuhq/code-sdk 0.8.7 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +68 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1301,9 +1301,43 @@ function onInsetsChange(cb) {
|
|
|
1301
1301
|
};
|
|
1302
1302
|
}
|
|
1303
1303
|
//#endregion
|
|
1304
|
+
//#region src/scenario-callback.ts
|
|
1305
|
+
/**
|
|
1306
|
+
* @docs
|
|
1307
|
+
* - SDK仕様: docs/docs/uzu_code/play-screen-sdk.md
|
|
1308
|
+
*
|
|
1309
|
+
* SDK からシナリオのコード (描画 / イベント購読 / bridge の購読) を呼ぶときの例外境界。
|
|
1310
|
+
* シナリオのコールバックを呼ぶ箇所は、すべてここを通す。
|
|
1311
|
+
*
|
|
1312
|
+
* これらは state 配信やメッセージ受信のたびに同じ経路を通る。例外をそのまま上へ通すと
|
|
1313
|
+
* 呼び出し元の state 適用 (confirmedState 更新 / ack 処理 / seq 前進) が途中で止まり、
|
|
1314
|
+
* 以降の配信でも同じ経路で投げ続ける。WebSocket もホストアプリも生きたまま画面だけが
|
|
1315
|
+
* 永久に更新されなくなる — 2026-09-09 の UZU TOKYO 公演で実際に起きた壊れ方がこれ。
|
|
1316
|
+
*
|
|
1317
|
+
* ここで断ち切れば state 適用は最後まで走り、次の配信で描画がやり直される
|
|
1318
|
+
* (onState は毎回フル state から再実行されるので冪等)。同じ理由で、購読が複数ある
|
|
1319
|
+
* ところでは 1 件の失敗を他の購読へ波及させない。
|
|
1320
|
+
*/
|
|
1321
|
+
/**
|
|
1322
|
+
* シナリオ側のコールバックを実行し、例外を境界で止める。
|
|
1323
|
+
*
|
|
1324
|
+
* 例外は握り潰さず `console.error` に出す。ホスト側のログ収集がこれを拾う。
|
|
1325
|
+
*/
|
|
1326
|
+
const callScenario = (label, fn) => {
|
|
1327
|
+
try {
|
|
1328
|
+
fn();
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
console.error(`[uzu] シナリオの ${label} で例外が発生しました (処理は継続します)`, err);
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
//#endregion
|
|
1304
1334
|
//#region src/run/optimistic-action-client.ts
|
|
1305
1335
|
function createOptimisticActionClient(config) {
|
|
1306
|
-
const { logic, playerId,
|
|
1336
|
+
const { logic, playerId, events, sendAction } = config;
|
|
1337
|
+
/** 呼び出し箇所ごとに守らず、シナリオの描画へ出る唯一の口をここで塞ぐ。 */
|
|
1338
|
+
const onState = (state, id) => {
|
|
1339
|
+
callScenario("描画 (onState)", () => config.onState(state, id));
|
|
1340
|
+
};
|
|
1307
1341
|
/** サーバー確定 state (楽観的更新のベース) */
|
|
1308
1342
|
let confirmedState = null;
|
|
1309
1343
|
/** 表示用 state (pending actions 適用済み) */
|
|
@@ -1336,7 +1370,11 @@ function createOptimisticActionClient(config) {
|
|
|
1336
1370
|
/** 再適用時はイベントを発火しない (送信時に既に発火済み) */
|
|
1337
1371
|
const noopEmit = () => {};
|
|
1338
1372
|
const dispatchEvents = (evts) => {
|
|
1339
|
-
for (const e of evts)
|
|
1373
|
+
for (const e of evts) {
|
|
1374
|
+
const handler = events?.[e.name]?.handler;
|
|
1375
|
+
if (!handler) continue;
|
|
1376
|
+
callScenario(`event ハンドラ "${e.name}"`, () => handler(e.data));
|
|
1377
|
+
}
|
|
1340
1378
|
};
|
|
1341
1379
|
/** events の同一性キー。name と data が一致すれば「同じ出来事」とみなす。 */
|
|
1342
1380
|
const eventKey = (e) => `${e.name}\u0000${JSON.stringify(e.data ?? {})}`;
|
|
@@ -1423,7 +1461,7 @@ function createOptimisticActionClient(config) {
|
|
|
1423
1461
|
const subscription = events?.[eventName];
|
|
1424
1462
|
if (!subscription?.predict) return;
|
|
1425
1463
|
const payloadData = data ?? {};
|
|
1426
|
-
subscription.handler(payloadData);
|
|
1464
|
+
callScenario(`event ハンドラ "${eventName}"`, () => subscription.handler(payloadData));
|
|
1427
1465
|
firedPredictions.push({
|
|
1428
1466
|
seq,
|
|
1429
1467
|
name: eventName,
|
|
@@ -1448,7 +1486,9 @@ function createOptimisticActionClient(config) {
|
|
|
1448
1486
|
time
|
|
1449
1487
|
});
|
|
1450
1488
|
onState(target, playerId);
|
|
1451
|
-
} catch {
|
|
1489
|
+
} catch (err) {
|
|
1490
|
+
console.warn(`[uzu] action "${type}" の先読み実行で例外`, err);
|
|
1491
|
+
}
|
|
1452
1492
|
sendAction({
|
|
1453
1493
|
action: type,
|
|
1454
1494
|
payload: payload ?? {},
|
|
@@ -1552,13 +1592,7 @@ function runOnlineServerAction(config, gameEndpoint, roomId, seatId, players, se
|
|
|
1552
1592
|
config.inputs((type, payload) => {
|
|
1553
1593
|
client.send(type, payload);
|
|
1554
1594
|
});
|
|
1555
|
-
|
|
1556
|
-
let parsed;
|
|
1557
|
-
try {
|
|
1558
|
-
parsed = JSON.parse(ev.data);
|
|
1559
|
-
} catch {
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1595
|
+
const handleMessage = (parsed) => {
|
|
1562
1596
|
const msgType = parsed.type;
|
|
1563
1597
|
console.log(`[SDK ServerAction] ⬅ recv type=${msgType}`);
|
|
1564
1598
|
client.observeGameTime(parsed.gameTime);
|
|
@@ -1649,6 +1683,21 @@ function runOnlineServerAction(config, gameEndpoint, roomId, seatId, players, se
|
|
|
1649
1683
|
client.reset(parsed.state);
|
|
1650
1684
|
return;
|
|
1651
1685
|
}
|
|
1686
|
+
};
|
|
1687
|
+
ws.addEventListener("message", (ev) => {
|
|
1688
|
+
let parsed;
|
|
1689
|
+
try {
|
|
1690
|
+
parsed = JSON.parse(ev.data);
|
|
1691
|
+
} catch {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (parsed === null || typeof parsed !== "object") return;
|
|
1695
|
+
const msg = parsed;
|
|
1696
|
+
try {
|
|
1697
|
+
handleMessage(msg);
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
console.error(`[SDK ServerAction] ❌ message 処理で例外 type=${String(msg.type)}`, err);
|
|
1700
|
+
}
|
|
1652
1701
|
});
|
|
1653
1702
|
}
|
|
1654
1703
|
//#endregion
|
|
@@ -1657,7 +1706,7 @@ function runLocalServerAction(config) {
|
|
|
1657
1706
|
const { logic, inputs, events } = config;
|
|
1658
1707
|
const onState = (next, id) => {
|
|
1659
1708
|
observeGameTime(gameTime());
|
|
1660
|
-
config.onState(next, id, "player");
|
|
1709
|
+
callScenario("描画 (onState)", () => config.onState(next, id, "player"));
|
|
1661
1710
|
};
|
|
1662
1711
|
const tickRate = logic.tickRate ?? 0;
|
|
1663
1712
|
const clock = new GameClock();
|
|
@@ -1744,7 +1793,11 @@ function runLocalServerAction(config) {
|
|
|
1744
1793
|
wakeupTimer = setTimeout(fireDue, Math.max(0, clock.toWall(next) - Date.now()));
|
|
1745
1794
|
};
|
|
1746
1795
|
const dispatchEvents = (evts) => {
|
|
1747
|
-
for (const e of evts)
|
|
1796
|
+
for (const e of evts) {
|
|
1797
|
+
const handler = events?.[e.name]?.handler;
|
|
1798
|
+
if (!handler) continue;
|
|
1799
|
+
callScenario(`event ハンドラ "${e.name}"`, () => handler(e.data));
|
|
1800
|
+
}
|
|
1748
1801
|
};
|
|
1749
1802
|
clock.restart();
|
|
1750
1803
|
const setupTime = gameTime();
|
|
@@ -2118,7 +2171,7 @@ function handleMessage(msg) {
|
|
|
2118
2171
|
return;
|
|
2119
2172
|
case "playersChanged": {
|
|
2120
2173
|
const players = payload?.players ?? {};
|
|
2121
|
-
_playersChangedHandlers
|
|
2174
|
+
for (const fn of _playersChangedHandlers) callScenario("playersChanged ハンドラ", () => fn(players));
|
|
2122
2175
|
return;
|
|
2123
2176
|
}
|
|
2124
2177
|
case "requestPause":
|
|
@@ -2138,7 +2191,7 @@ function handleMessage(msg) {
|
|
|
2138
2191
|
if (channel === "game") {
|
|
2139
2192
|
const h = gameHandlers.get(type) || [];
|
|
2140
2193
|
console.log(`[SDK] 🔧 handleMessage game/${type} handlers=${h.length}`);
|
|
2141
|
-
h
|
|
2194
|
+
for (const fn of h) callScenario(`game/${type} ハンドラ`, () => fn(payload ?? {}));
|
|
2142
2195
|
return;
|
|
2143
2196
|
}
|
|
2144
2197
|
}
|