homegames-common 1.5.2 → 1.5.3

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.
@@ -84,7 +84,7 @@ class GameSessionManager {
84
84
  this.dockerImageName = opts.dockerImageName || 'homegames-runner';
85
85
  this.childServerPath = opts.childServerPath || null;
86
86
  this.gracePeriodMs = opts.gracePeriodMs || 30000;
87
- this.lifecycleCheckMs = opts.lifecycleCheckMs || 10000;
87
+ this.lifecycleCheckMs = opts.lifecycleCheckMs || 3000;
88
88
  this.forgejo = opts.forgejo || {};
89
89
  this.saveDataRoot = opts.saveDataRoot || path.join(os.tmpdir(), 'hg-save-data');
90
90
  this.assetCachePath = opts.assetCachePath || null;
@@ -368,6 +368,8 @@ class GameSessionManager {
368
368
  execArgv: ['--max-old-space-size=64'],
369
369
  });
370
370
 
371
+ this._attachForkLogForwarding(child);
372
+
371
373
  child.send(JSON.stringify({
372
374
  key: input.gameKey || path.basename(gamePath, '.js'),
373
375
  squishVersion,
@@ -433,6 +435,38 @@ class GameSessionManager {
433
435
  });
434
436
  }
435
437
 
438
+ // -----------------------------------------------------------------------
439
+ // Forward forked child stdout/stderr to the parent terminal.
440
+ // Docker sessions are excluded — use GET /sessions/:id/logs or docker logs.
441
+ // -----------------------------------------------------------------------
442
+ _attachForkLogForwarding(child) {
443
+ const emitLine = (line) => {
444
+ if (!line) return;
445
+ console.log(`session log: ${line}`);
446
+ };
447
+
448
+ const attachStream = (stream) => {
449
+ if (!stream) return;
450
+ let buffer = '';
451
+ stream.on('data', (chunk) => {
452
+ buffer += chunk.toString();
453
+ const lines = buffer.split('\n');
454
+ buffer = lines.pop() || '';
455
+ for (const line of lines) {
456
+ emitLine(line);
457
+ }
458
+ });
459
+ stream.on('end', () => {
460
+ if (buffer.length > 0) {
461
+ emitLine(buffer);
462
+ }
463
+ });
464
+ };
465
+
466
+ attachStream(child.stdout);
467
+ attachStream(child.stderr);
468
+ }
469
+
436
470
  // -----------------------------------------------------------------------
437
471
  // Find the project root (nearest ancestor with node_modules or package.json)
438
472
  // so we mount enough of the tree for relative requires to work.
@@ -525,10 +559,30 @@ class GameSessionManager {
525
559
 
526
560
  if (s.type === 'docker') {
527
561
  const running = await isContainerRunning(s.containerId);
528
- this.log.info(`Session ${sessionId} lifecycle check: container running = ${running}`);
529
562
  if (!running) {
530
563
  this.log.info(`Session ${sessionId} container exited`);
531
564
  this._cleanupSession(sessionId);
565
+ return;
566
+ }
567
+
568
+ // Check player count — stop container if empty past grace period
569
+ try {
570
+ const healthData = await this._querySessionHealth(s.port);
571
+ const playerCount = healthData && healthData.playerCount !== undefined ? healthData.playerCount : -1;
572
+ if (playerCount === 0) {
573
+ s._emptyTicks++;
574
+ const emptyMs = s._emptyTicks * this.lifecycleCheckMs;
575
+ if (emptyMs >= this.gracePeriodMs) {
576
+ this.log.info(`Session ${sessionId} empty for ${emptyMs}ms, stopping`);
577
+ await stopContainer(s.containerId);
578
+ this._cleanupSession(sessionId);
579
+ return;
580
+ }
581
+ } else if (playerCount > 0) {
582
+ s._emptyTicks = 0;
583
+ }
584
+ } catch (e) {
585
+ // Health check failed — container might be starting up, ignore
532
586
  }
533
587
  } else if (s.type === 'fork') {
534
588
  // For fork sessions, send heartbeat. The child_game_server.js
@@ -559,6 +613,28 @@ class GameSessionManager {
559
613
  this._cleanupSession(sessionId);
560
614
  }
561
615
 
616
+ _querySessionHealth(port) {
617
+ return new Promise((resolve) => {
618
+ const req = http.request({
619
+ hostname: 'localhost',
620
+ port,
621
+ path: '/health',
622
+ method: 'GET',
623
+ timeout: 2000,
624
+ }, (res) => {
625
+ let data = '';
626
+ res.on('data', (chunk) => { data += chunk; });
627
+ res.on('end', () => {
628
+ try { resolve(JSON.parse(data)); }
629
+ catch (e) { resolve(null); }
630
+ });
631
+ });
632
+ req.on('error', () => resolve(null));
633
+ req.on('timeout', () => { req.destroy(); resolve(null); });
634
+ req.end();
635
+ });
636
+ }
637
+
562
638
  _cleanupSession(sessionId) {
563
639
  const session = this.sessions[sessionId];
564
640
  if (!session) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homegames-common",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "description": "Homegames common tools",
5
5
  "main": "index.js",
6
6
  "scripts": {