From 08d41cdec1cdbbac53261e94b84242c25d580560 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sun, 28 Jun 2026 14:23:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(framework):=20net=20=E6=94=B6=E5=8C=85?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E7=9C=8B=E9=97=A8=E7=8B=97(=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=20Clock=20=E5=8F=AF=E6=B5=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../assets/framework/net/heartbeat.ts | 23 +++++++++ .../framework-tests/net/heartbeat.test.ts | 48 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 cocoscreator_projects/YouleNexus/assets/framework/net/heartbeat.ts create mode 100644 cocoscreator_projects/framework-tests/net/heartbeat.test.ts diff --git a/cocoscreator_projects/YouleNexus/assets/framework/net/heartbeat.ts b/cocoscreator_projects/YouleNexus/assets/framework/net/heartbeat.ts new file mode 100644 index 0000000..465a803 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/assets/framework/net/heartbeat.ts @@ -0,0 +1,23 @@ +export interface Clock { + setTimeout(fn: () => void, ms: number): unknown; + clearTimeout(handle: unknown): void; +} + +export const realClock: Clock = { + setTimeout: (fn, ms) => setTimeout(fn, ms), + clearTimeout: (h) => clearTimeout(h as any), +}; + +/** 收包超时看门狗。docs/protocol/01 §4.1:每收一包 feed() 重置;超时调 onTimeout。 */ +export class HeartbeatWatchdog { + private handle: unknown = null; + constructor(private ms: number, private onTimeout: () => void, private clock: Clock = realClock) {} + + feed(): void { + this.stop(); + this.handle = this.clock.setTimeout(() => { this.handle = null; this.onTimeout(); }, this.ms); + } + stop(): void { + if (this.handle != null) { this.clock.clearTimeout(this.handle); this.handle = null; } + } +} diff --git a/cocoscreator_projects/framework-tests/net/heartbeat.test.ts b/cocoscreator_projects/framework-tests/net/heartbeat.test.ts new file mode 100644 index 0000000..b2909dc --- /dev/null +++ b/cocoscreator_projects/framework-tests/net/heartbeat.test.ts @@ -0,0 +1,48 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { HeartbeatWatchdog, type Clock } from '../../YouleNexus/assets/framework/net/heartbeat.ts'; + +function fakeClock() { + let seq = 0; + const timers = new Map void; at: number }>(); + let now = 0; + const clock: Clock = { + setTimeout: (fn, ms) => { const id = ++seq; timers.set(id, { fn, at: now + ms }); return id; }, + clearTimeout: (id: any) => { timers.delete(id); }, + }; + const advance = (ms: number) => { + now += ms; + for (const [id, t] of [...timers]) if (t.at <= now) { timers.delete(id); t.fn(); } + }; + return { clock, advance }; +} + +test('超时未 feed 触发回调', () => { + const { clock, advance } = fakeClock(); + let fired = 0; + const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock); + wd.feed(); + advance(30000); + assert.equal(fired, 1); +}); + +test('feed 重置计时,未到阈值不触发', () => { + const { clock, advance } = fakeClock(); + let fired = 0; + const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock); + wd.feed(); + advance(20000); + wd.feed(); // 重置 + advance(20000); // 距上次 feed 仅 20s + assert.equal(fired, 0); +}); + +test('stop 后不再触发', () => { + const { clock, advance } = fakeClock(); + let fired = 0; + const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock); + wd.feed(); + wd.stop(); + advance(30000); + assert.equal(fired, 0); +});