@uzuhq/code-sdk 0.7.0 → 0.7.1
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/README.md +37 -25
- package/dist/index.d.ts +1 -1
- package/dist/run/local-server-action.js +1 -1
- package/dist/types.d.ts +18 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,61 +219,73 @@ run({
|
|
|
219
219
|
### GameLogic
|
|
220
220
|
|
|
221
221
|
```ts
|
|
222
|
-
import {
|
|
222
|
+
import type { GameLogic } from '@uzuhq/code-sdk';
|
|
223
223
|
|
|
224
224
|
const logic: GameLogic<MyState> = {
|
|
225
|
-
setup(seats,
|
|
226
|
-
// 初期 state を生成。random は SeededRandom
|
|
225
|
+
setup({ seats, ctx }) {
|
|
226
|
+
// 初期 state を生成。ctx.random は SeededRandom、ctx.now はサーバーの実時刻。
|
|
227
227
|
// seats には観戦系の席 (kind: 'spectator' | 'admin') も含まれ得るため、
|
|
228
228
|
// ゲームの席は kind === 'player' に絞る
|
|
229
229
|
return { players: {}, items: [] };
|
|
230
230
|
},
|
|
231
231
|
|
|
232
|
+
// クライアント先読みとサーバーの 2 回走る。決定的でなければならない。
|
|
232
233
|
actions: {
|
|
233
|
-
move(state, payload, playerId,
|
|
234
|
+
move({ state, payload, playerId, ctx }) {
|
|
234
235
|
// state を直接変更する(Immer 的な mutable 操作)
|
|
235
236
|
state.players[playerId].x += payload.dx;
|
|
236
|
-
// emit でイベント発火
|
|
237
|
-
emit('sound', { sound: 'step' });
|
|
238
|
-
// ctx.
|
|
237
|
+
// ctx.emit でイベント発火 (購読側は events で predict を宣言する)
|
|
238
|
+
ctx.emit('sound', { sound: 'step' });
|
|
239
|
+
// 時刻は ctx.now を使う。Date.now() は端末とサーバーでズレる
|
|
239
240
|
},
|
|
241
|
+
},
|
|
240
242
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
// サーバーでのみ走る。実時刻・乱数・fetch などクライアントが再現できない処理。
|
|
244
|
+
// actions と同名にすると「同じ action のサーバー側の続き」になる。
|
|
245
|
+
serverActions: {
|
|
246
|
+
async notifyExternal({ state, payload, playerId }) {
|
|
243
247
|
await fetch('https://example.com/notify', {
|
|
244
248
|
method: 'POST',
|
|
245
249
|
body: JSON.stringify({ playerId, ...payload }),
|
|
246
250
|
});
|
|
247
251
|
state.notifiedAt = Date.now();
|
|
248
|
-
}
|
|
252
|
+
},
|
|
249
253
|
},
|
|
250
254
|
|
|
251
|
-
update(state, ctx) {
|
|
252
|
-
// 毎 tick 実行。ctx.tick
|
|
255
|
+
update({ state, ctx }) {
|
|
256
|
+
// 毎 tick 実行。ctx.tick / ctx.random / ctx.now / ctx.emit / ctx.playerInputs が使える
|
|
253
257
|
},
|
|
254
258
|
|
|
255
|
-
tickRate: 10, // 秒間 tick 数 (default: 0 = tick
|
|
259
|
+
tickRate: 10, // 秒間 tick 数 (default: 0 = tick なし)
|
|
256
260
|
};
|
|
257
261
|
```
|
|
258
262
|
|
|
259
|
-
| キー
|
|
260
|
-
|
|
|
261
|
-
| `setup`
|
|
262
|
-
| `actions`
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
263
|
+
| キー | 型 | 必須 | 説明 |
|
|
264
|
+
| --------------- | ---------------------------------------- | ---- | ---------------------------------------------------------------- |
|
|
265
|
+
| `setup` | `(args: SetupArgs) => S` | Yes | 初期 state を生成。seats には `kind !== 'player'` の席も含まれる |
|
|
266
|
+
| `actions` | `Record<string, ActionHandler<S>>` | Yes | クライアント先読み + サーバーの 2 回走る。決定的であること |
|
|
267
|
+
| `serverActions` | `Record<string, ServerActionHandler<S>>` | No | サーバーでのみ走る。async 可。`ctx` に `tick` / `random` が入る |
|
|
268
|
+
| `update` | `(args: UpdateArgs<S>) => void` | Yes | 毎 tick 実行 (`tickRate` が 0 なら呼ばれない) |
|
|
269
|
+
| `tickRate` | `number` | No | 秒間 tick 数 (default: 0 = tick なし) |
|
|
270
|
+
|
|
271
|
+
ハンドラの引数は 1 つのオブジェクトで、使うものだけ書けばよい。
|
|
272
|
+
`state` / `payload` / `playerId` はその呼び出しの事実、`ctx` は実行環境が与えるもの。
|
|
273
|
+
|
|
274
|
+
時刻起点で何かを起こしたい場合は `tickRate` で毎秒ポーリングせず `ctx.schedule()` を使う。
|
|
275
|
+
サーバーが指定時刻に自分で起きて action を撃つので、その間 Durable Object が hibernate できる。
|
|
265
276
|
|
|
266
277
|
#### `serverOnly(handler)`
|
|
267
278
|
|
|
268
|
-
`
|
|
279
|
+
> **Deprecated**: `serverActions` に直接書く。
|
|
269
280
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
- handler のシグネチャ: `(state, payload, playerId, emit, ctx) => Promise<void> | void`
|
|
273
|
-
- 例外を投げると送信元クライアントに `__action_error` が返る (state は変更されない)
|
|
281
|
+
`actions` に登録する handler を「サーバーでだけ実行される」ものに変換する旧 wrapper。
|
|
282
|
+
`serverActions` フィールドが同じことを型で表現できるので、新しいコードでは使わない。
|
|
274
283
|
|
|
275
284
|
```ts
|
|
276
|
-
|
|
285
|
+
// before
|
|
286
|
+
actions: { notifyExternal: serverOnly(async (state) => { ... }) }
|
|
287
|
+
// after
|
|
288
|
+
serverActions: { notifyExternal: async ({ state }) => { ... } }
|
|
277
289
|
```
|
|
278
290
|
|
|
279
291
|
### manifest.json(run() を使う場合)
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* scenario ディレクトリで `uzu dev` を実行するだけで良い。 子 iframe は既存 online mode
|
|
16
16
|
* (`?server=ws://localhost:<port>` 経路) で dev-server に接続する。
|
|
17
17
|
*/
|
|
18
|
-
export type { PlayScreenMessage, BridgeChannel, BridgeMessage, Seat, SeatKind, Emit, ServerEvent, SeededRandom, GameLogic, GameConfig, SyncConfig, PatchFn, SetFn, Operation, ConnectionState, ConnectionCallbacks, PlayerVoiceState, PlayersChangedMessage, ActionArgs, ActionContext, ActionHandler, ServerActionArgs, UpdateArgs, UpdateContext, EventHandler, EventSubscription, ScheduleOptions, Scheduler, ServerActionContext, ServerActionHandler, ServerOnlyAction, ServerOnlyActionContext, ServerOnlyActionHandlerFn, } from './types.js';
|
|
18
|
+
export type { PlayScreenMessage, BridgeChannel, BridgeMessage, Seat, SeatKind, Emit, ServerEvent, SeededRandom, GameLogic, GameConfig, SyncConfig, PatchFn, SetFn, Operation, ConnectionState, ConnectionCallbacks, PlayerVoiceState, PlayersChangedMessage, ActionArgs, ActionContext, ActionHandler, ServerActionArgs, SetupArgs, SetupContext, UpdateArgs, UpdateContext, EventHandler, EventSubscription, ScheduleOptions, Scheduler, ServerActionContext, ServerActionHandler, ServerOnlyAction, ServerOnlyActionContext, ServerOnlyActionHandlerFn, } from './types.js';
|
|
19
19
|
export { SERVER_TIME, DEFAULT_ICON_URLS, SCHEDULED_ACTOR } from './types.js';
|
|
20
20
|
export { serverOnly, isServerOnlyAction } from './server-only.js';
|
|
21
21
|
export { serverNow } from './server-clock.js';
|
|
@@ -50,7 +50,7 @@ export function runLocalServerAction(config) {
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
// setRawState で全置換できるよう let。closures は名前参照なので最新束縛を読む。
|
|
53
|
-
let state = logic.setup(players, random);
|
|
53
|
+
let state = logic.setup({ seats: players, ctx: { random, now: Date.now() } });
|
|
54
54
|
let tick = 0;
|
|
55
55
|
const playerInputs = {};
|
|
56
56
|
// Action 処理。`actions` は同期実行で `sendAction()` 直後の同期 onState を保証する
|
package/dist/types.d.ts
CHANGED
|
@@ -200,12 +200,28 @@ export interface UpdateArgs<S> {
|
|
|
200
200
|
state: S;
|
|
201
201
|
ctx: UpdateContext;
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
/**
|
|
204
|
+
* `setup()` の実行文脈。サーバーでしか走らないので実時刻をそのまま渡せる。
|
|
205
|
+
*
|
|
206
|
+
* `emit` は無い。まだ誰も購読していない時点なので、鳴らしても届かない。
|
|
207
|
+
* `schedule` も無い。開幕から予約したい要求が出たら足す。
|
|
208
|
+
*/
|
|
209
|
+
export interface SetupContext {
|
|
210
|
+
random: SeededRandom;
|
|
211
|
+
/** サーバーの実時刻 (ms)。setup はサーバーでしか走らないので常に正確。 */
|
|
212
|
+
now: number;
|
|
213
|
+
}
|
|
214
|
+
/** `setup()` の引数。 */
|
|
215
|
+
export interface SetupArgs {
|
|
204
216
|
/**
|
|
205
217
|
* seats には kind !== 'player' の席 (spectator / admin) も含まれる。
|
|
206
218
|
* ゲームの配役は kind === 'player' (または kind 省略) だけを対象にすること。
|
|
207
219
|
*/
|
|
208
|
-
|
|
220
|
+
seats: Seat[];
|
|
221
|
+
ctx: SetupContext;
|
|
222
|
+
}
|
|
223
|
+
export interface GameLogic<S> {
|
|
224
|
+
setup(args: SetupArgs): S;
|
|
209
225
|
/**
|
|
210
226
|
* クライアント先読みとサーバーの両方で走る handler。決定的でなければならない。
|
|
211
227
|
*
|