@yyyu-star/arcade-kit 0.11.0 → 0.11.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/src/room.ts CHANGED
@@ -117,6 +117,8 @@ export const DEFAULT_LIMITS: RoomLimits = {
117
117
 
118
118
  /** 闹钟里出错时,过这么久再醒来重试(不用"马上",免得每次都出错时连着计费)。 */
119
119
  const ALARM_RETRY_MS = 60_000;
120
+ /** 每个座位记住最近几个做成的 moveId(0.11.1):客户端断线时攒着没确认的操作,重连后原样补发 */
121
+ const RECENT_MOVES = 8;
120
122
 
121
123
  /** 计量说的门槛在内存里记多久(不进新人时用;报数、还名额时顺便更新)。 */
122
124
  const LEVEL_TTL_MS = 30_000;
@@ -148,6 +150,11 @@ interface SeatState {
148
150
  auto: boolean;
149
151
  /** 这个座位最后一次做成的操作的 moveId(重发去重) */
150
152
  lastMoveId: string | null;
153
+ /**
154
+ * 最近做成的几个 moveId,新的在后(0.11.1,最多 RECENT_MOVES 个;旧房间的状态里没有,读到时当空的)。
155
+ * 只记最后一个不够:连发 A、B,A 的 ack 丢了,重连后补发 A,lastMoveId 已经是 B,不轮流的操作(free)就会被做第二遍
156
+ */
157
+ recentMoves?: string[];
151
158
  }
152
159
 
153
160
  interface Persist<S> {
@@ -389,13 +396,14 @@ export abstract class GameRoom<S, A, V, Env = unknown, O = unknown, E extends Ro
389
396
  this.alarmAt = null;
390
397
  await this.entry(() => this.onAlarm());
391
398
  // 出错时状态可能没走到"重设闹钟"那一步:房间还在、又没有闹钟,就过一会儿再醒来,不然房间永远没人关
392
- if (this.lastStepFailed && this.alarmAt === null && this.load()) {
399
+ // setAlarm 本身失败时 alarmAt 是 undefined(不知道),以存储里读到的为准(0.11.1)
400
+ if (this.lastStepFailed && this.load() && (this.alarmAt === undefined ? await this.ctx.storage.getAlarm() : this.alarmAt) === null) {
393
401
  this.alarmAt = Date.now() + ALARM_RETRY_MS;
394
402
  this.lastStepWrites++;
395
403
  this.totalWrites++;
396
404
  this.unmetered++;
397
405
  this.maxStepWrites = Math.max(this.maxStepWrites, this.lastStepWrites);
398
- await this.ctx.storage.setAlarm(this.alarmAt);
406
+ await this.setAlarmSafely(this.alarmAt);
399
407
  }
400
408
  } catch (e) {
401
409
  console.error('room alarm failed', e instanceof Error ? e.message : e);
@@ -430,11 +438,26 @@ export abstract class GameRoom<S, A, V, Env = unknown, O = unknown, E extends Ro
430
438
  this.totalWrites += this.writes;
431
439
  this.unmetered += this.writes;
432
440
  this.maxStepWrites = Math.max(this.maxStepWrites, this.writes);
433
- if (at !== null) await this.ctx.storage.setAlarm(at);
441
+ if (at !== null && !(await this.setAlarmSafely(at))) this.lastStepFailed = true;
434
442
  await this.afterStep();
435
443
  return out;
436
444
  }
437
445
 
446
+ /**
447
+ * 设闹钟;失败了不往外抛(0.11.1)。planAlarm 已经把 alarmAt 改成了新时间,失败时它就是错的:
448
+ * 留着的话以后 planAlarm 看"已经设了更早的"一直不重设,房间就没人收场。改回"不知道",下一个入口按 getAlarm() 重新读、照常重排
449
+ */
450
+ private async setAlarmSafely(at: number): Promise<boolean> {
451
+ try {
452
+ await this.ctx.storage.setAlarm(at);
453
+ return true;
454
+ } catch (e) {
455
+ console.error('room setAlarm failed', e instanceof Error ? e.message : e);
456
+ this.alarmAt = undefined;
457
+ return false;
458
+ }
459
+ }
460
+
438
461
  // ---------- 计量(0.2.0) ----------
439
462
 
440
463
  /**
@@ -809,7 +832,7 @@ export abstract class GameRoom<S, A, V, Env = unknown, O = unknown, E extends Ro
809
832
  if (seat === null) return this.reject(ws, 'not_seated', moveId);
810
833
  const s = st.seats[seat]!;
811
834
  // 重发的同一个操作:不再做一遍,只回 ack 和当前状态
812
- if (s.lastMoveId === moveId) {
835
+ if (s.lastMoveId === moveId || s.recentMoves?.includes(moveId)) {
813
836
  const bound = this.boundOf(attachmentOf(ws)!.userId);
814
837
  return this.send(ws, this.stateMsg(seat, bound, [{ state: st.game, delay: 0 }], moveId));
815
838
  }
@@ -828,6 +851,7 @@ export abstract class GameRoom<S, A, V, Env = unknown, O = unknown, E extends Ro
828
851
  const out = this.step(seat, action, now, free);
829
852
  if (out) return this.reject(ws, out, moveId);
830
853
  s.lastMoveId = moveId;
854
+ s.recentMoves = [...(s.recentMoves ?? []), moveId].slice(-RECENT_MOVES);
831
855
  const auto = this.runAuto(now);
832
856
  if (seen !== null && auto.length === 0 && this.othersSee(seat) === seen) return this.commitQuiet(ws, seat, moveId);
833
857
  this.commit({ frames: [{ state: st.game, delay: 0 }, ...auto], ack: { ws, moveId } });