index.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { join } from 'node:path';
  2. import { app, BrowserWindow, ipcMain, utilityProcess, type UtilityProcess, type WebContents } from 'electron';
  3. import { readFileSync } from 'node:fs';
  4. import type { HostResult, MapFiles } from '../preload/api';
  5. import { DISCOVERY_INTERVAL_MS } from '../shared/constants';
  6. import { readMapFiles, readMapImage, resolveAssetsDir } from '../server/assets';
  7. import { DiscoveryListener } from './discovery';
  8. const assetsDir = app.isPackaged ? join(process.resourcesPath, 'assets') : join(app.getAppPath(), 'assets');
  9. process.env.GOUTER_ASSETS = assetsDir;
  10. let server: UtilityProcess | null = null;
  11. let serverReady: Promise<HostResult> | null = null;
  12. /** Forks the authoritative server in a utilityProcess (once) and waits for it to listen. */
  13. function hostServer(name: string): Promise<HostResult> {
  14. if (serverReady) return serverReady;
  15. serverReady = new Promise<HostResult>((resolveReady, reject) => {
  16. const proc = utilityProcess.fork(join(__dirname, 'server.js'), [`--name=${String(name).slice(0, 40)}`], {
  17. serviceName: 'goutermageddon-server',
  18. env: { ...process.env, GOUTER_ASSETS: assetsDir },
  19. stdio: 'inherit',
  20. });
  21. server = proc;
  22. proc.on('message', (msg: { type: string; port?: number; message?: string }) => {
  23. if (msg.type === 'ready') resolveReady({ port: msg.port! });
  24. else if (msg.type === 'error') reject(new Error(msg.message));
  25. });
  26. proc.on('exit', (code) => {
  27. server = null;
  28. serverReady = null;
  29. reject(new Error(`server exited with code ${code}`));
  30. });
  31. });
  32. return serverReady;
  33. }
  34. function createWindow(): void {
  35. const win = new BrowserWindow({
  36. width: 1280,
  37. height: 720,
  38. title: 'Goûtermageddon',
  39. backgroundColor: '#fff1d6',
  40. autoHideMenuBar: true,
  41. webPreferences: {
  42. preload: join(__dirname, '../preload/index.js'),
  43. contextIsolation: true,
  44. nodeIntegration: false,
  45. sandbox: true,
  46. },
  47. });
  48. if (process.env.ELECTRON_RENDERER_URL) void win.loadURL(process.env.ELECTRON_RENDERER_URL);
  49. else void win.loadFile(join(__dirname, '../renderer/index.html'));
  50. }
  51. // LAN discovery: one listener for the app, pushed to the renderer that asked for it.
  52. const discovery = new DiscoveryListener();
  53. let discoveryTimer: NodeJS.Timeout | null = null;
  54. function startDiscovery(target: WebContents): void {
  55. stopDiscovery();
  56. discovery.start((err) => console.error('[discovery] cannot listen:', err.message));
  57. const push = (): void => {
  58. if (target.isDestroyed()) return stopDiscovery();
  59. target.send('lan:games', discovery.list());
  60. };
  61. push();
  62. discoveryTimer = setInterval(push, DISCOVERY_INTERVAL_MS);
  63. }
  64. function stopDiscovery(): void {
  65. if (discoveryTimer) clearInterval(discoveryTimer);
  66. discoveryTimer = null;
  67. discovery.stop();
  68. }
  69. ipcMain.handle('lan:host', (_event, name: string) => hostServer(name));
  70. ipcMain.handle('lan:discover-start', (event) => startDiscovery(event.sender));
  71. ipcMain.handle('lan:discover-stop', () => stopDiscovery());
  72. ipcMain.handle('assets:materials', () => JSON.parse(readFileSync(join(resolveAssetsDir(), 'materials', 'materials.json'), 'utf8')));
  73. ipcMain.handle('assets:map', (_event, mapId: string): MapFiles => {
  74. const dir = resolveAssetsDir();
  75. const { definition, terrain } = readMapFiles(dir, mapId);
  76. return { definition, terrain, background: readMapImage(dir, mapId, 'background.png') };
  77. });
  78. void app.whenReady().then(() => {
  79. createWindow();
  80. app.on('activate', () => {
  81. if (BrowserWindow.getAllWindows().length === 0) createWindow();
  82. });
  83. });
  84. app.on('window-all-closed', () => {
  85. stopDiscovery();
  86. server?.kill();
  87. app.quit();
  88. });