|
| 1 | +Architecture |
| 2 | +============ |
| 3 | + |
| 4 | +Overview |
| 5 | +-------- |
| 6 | + |
| 7 | +NanoForge Loader is a runtime system that serves and executes NanoForge game |
| 8 | +engine projects. It provides two execution modes: |
| 9 | + |
| 10 | +- **Client mode**: Serves a web interface where users can load and play games |
| 11 | + directly in the browser using WebGL. |
| 12 | +- **Server mode**: Executes game server logic in a Node.js worker thread, |
| 13 | + suitable for multiplayer game backends. |
| 14 | + |
| 15 | +Both loaders share a common architecture: they read game files from a directory, |
| 16 | +generate a manifest of assets, and execute the game's ``main.js`` entry point. |
| 17 | + |
| 18 | +Technology Stack |
| 19 | +---------------- |
| 20 | + |
| 21 | +.. list-table:: |
| 22 | + :header-rows: 1 |
| 23 | + :widths: 30 70 |
| 24 | + |
| 25 | + * - Component |
| 26 | + - Technology |
| 27 | + * - Language |
| 28 | + - TypeScript (strict mode) |
| 29 | + * - Runtime |
| 30 | + - Bun (bundler and runtime) |
| 31 | + * - Module Formats |
| 32 | + - ESM + CJS (dual package) |
| 33 | + * - Target |
| 34 | + - Browser (client) / Node.js (server) |
| 35 | + * - Package Manager |
| 36 | + - pnpm 10.x |
| 37 | + * - Node Version |
| 38 | + - 25 |
| 39 | + * - Linter |
| 40 | + - ESLint 9.x |
| 41 | + * - Formatter |
| 42 | + - Prettier 3.x |
| 43 | + * - CI/CD |
| 44 | + - GitHub Actions |
| 45 | + |
| 46 | +Package Architecture |
| 47 | +-------------------- |
| 48 | + |
| 49 | +The loader is organized as a monorepo with three packages: |
| 50 | + |
| 51 | +:: |
| 52 | + |
| 53 | + loader/ |
| 54 | + +-- apps/ |
| 55 | + | +-- client/ # Browser loader server |
| 56 | + | | +-- src/ |
| 57 | + | | | +-- server.ts # HTTP server entry point |
| 58 | + | | | +-- env.ts # Environment configuration |
| 59 | + | | | +-- files.ts # File utilities |
| 60 | + | | | +-- manifest.ts # Manifest generation |
| 61 | + | | | +-- watch.ts # File watcher for hot reload |
| 62 | + | | +-- package.json |
| 63 | + | +-- server/ # Node.js server loader |
| 64 | + | | +-- src/ |
| 65 | + | | | +-- server.ts # Bootstrap entry point |
| 66 | + | | | +-- worker.ts # Worker thread implementation |
| 67 | + | | | +-- env.ts # Environment configuration |
| 68 | + | | | +-- files.ts # File utilities |
| 69 | + | | | +-- watch.ts # File watcher for hot reload |
| 70 | + | | +-- package.json |
| 71 | + | +-- website/ # Web interface |
| 72 | + | +-- src/ |
| 73 | + | | +-- index.ts # Main entry point |
| 74 | + | | +-- cache/ # Caching logic |
| 75 | + | | +-- file-system/ # File system abstraction |
| 76 | + | | +-- game/ # Game runner |
| 77 | + | | +-- loader/ # Game file loader |
| 78 | + | | +-- types/ # TypeScript types |
| 79 | + | | +-- utils/ # Utility functions |
| 80 | + | | +-- version/ # Version management |
| 81 | + | +-- package.json |
| 82 | + +-- package.json # Root workspace config |
| 83 | + +-- pnpm-workspace.yaml # pnpm workspace definition |
| 84 | + +-- turbo.json # Turbo build configuration |
| 85 | + |
| 86 | +Client Loader Flow |
| 87 | +------------------ |
| 88 | + |
| 89 | +The client loader implements an HTTP server using Bun: |
| 90 | + |
| 91 | +1. **Server Initialization**: Starts an HTTP server on the configured port. |
| 92 | + |
| 93 | +2. **Route Handling**: |
| 94 | + |
| 95 | + - ``/`` - Serves the website ``index.html`` |
| 96 | + - ``/*`` - Serves static website assets |
| 97 | + - ``/manifest`` - Returns the game file manifest (JSON) |
| 98 | + - ``/env`` - Returns public environment variables |
| 99 | + - ``/game/*`` - Serves game files from the game directory |
| 100 | + |
| 101 | +3. **Manifest Generation**: On each ``/manifest`` request, scans the game |
| 102 | + directory and builds a list of all game files with their paths. |
| 103 | + |
| 104 | +4. **Hot Reload** (optional): When ``WATCH=true``, starts a WebSocket server |
| 105 | + that notifies the browser when game files change. |
| 106 | + |
| 107 | +.. code-block:: text |
| 108 | +
|
| 109 | + Browser Client Loader Game Directory |
| 110 | + | | | |
| 111 | + |----GET /manifest-------->| | |
| 112 | + | |---scan directory--------->| |
| 113 | + | |<--file list---------------| |
| 114 | + |<---JSON manifest---------| | |
| 115 | + | | | |
| 116 | + |----GET /game/main.js---->| | |
| 117 | + | |---read file-------------->| |
| 118 | + |<---JavaScript file-------| | |
| 119 | + | | | |
| 120 | + |====WebSocket (watch)=====| | |
| 121 | + |<---file changed----------|<--fsnotify---------------| |
| 122 | +
|
| 123 | +Server Loader Flow |
| 124 | +------------------ |
| 125 | + |
| 126 | +The server loader executes game logic in a Node.js worker thread: |
| 127 | + |
| 128 | +1. **Bootstrap**: Reads the game directory and locates ``main.js``. |
| 129 | + |
| 130 | +2. **Worker Fork**: Spawns a child process that runs the worker script. |
| 131 | + |
| 132 | +3. **Game Execution**: The worker imports ``main.js`` and calls its ``main()`` |
| 133 | + function with a file map containing all game assets. |
| 134 | + |
| 135 | +4. **Hot Reload** (optional): When watch mode is enabled, the worker process |
| 136 | + is killed and restarted when files change. |
| 137 | + |
| 138 | +.. code-block:: text |
| 139 | +
|
| 140 | + Server Loader Worker Thread Game Code |
| 141 | + | | | |
| 142 | + |---fork(worker.js)--------->| | |
| 143 | + | |---import main.js-------->| |
| 144 | + | |<--main function----------| |
| 145 | + | |---call main()----------->| |
| 146 | + | | | |
| 147 | + [file change detected] | | |
| 148 | + |---kill worker------------->| | |
| 149 | + |---fork new worker--------->| | |
| 150 | +
|
| 151 | +Website Architecture |
| 152 | +-------------------- |
| 153 | + |
| 154 | +The website package provides the browser-side game loader: |
| 155 | + |
| 156 | +1. **Manifest Fetching**: Requests ``/manifest`` from the client server. |
| 157 | + |
| 158 | +2. **File Caching**: Downloads game files and stores them in browser storage |
| 159 | + (IndexedDB via the file-system abstraction). |
| 160 | + |
| 161 | +3. **Version Checking**: Compares the manifest version with cached version |
| 162 | + to determine if files need updating. |
| 163 | + |
| 164 | +4. **Game Loading**: Imports the main module and creates a file map for |
| 165 | + the game to access assets. |
| 166 | + |
| 167 | +5. **Game Execution**: Calls the game's ``main()`` function with canvas |
| 168 | + and file references. |
| 169 | + |
| 170 | +6. **Watch Integration**: Subscribes to the WebSocket for hot reload |
| 171 | + notifications during development. |
| 172 | + |
| 173 | +Build Pipeline |
| 174 | +-------------- |
| 175 | + |
| 176 | +The project uses Bun for building and bundling: |
| 177 | + |
| 178 | +.. code-block:: bash |
| 179 | +
|
| 180 | + # Build all packages |
| 181 | + pnpm run build |
| 182 | +
|
| 183 | + # What happens internally: |
| 184 | + # 1. website: bun build -> dist/index.html + assets |
| 185 | + # 2. client: bun build -> dist/server.js |
| 186 | + # 3. server: bun build -> dist/server.js + dist/worker.js |
| 187 | +
|
| 188 | +Each package produces optimized bundles for its target environment: |
| 189 | + |
| 190 | +- **Website**: Browser bundle with HTML entry point |
| 191 | +- **Client**: Bun-targeted server bundle |
| 192 | +- **Server**: Node.js-targeted bundles for server and worker |
| 193 | + |
| 194 | +Turbo is used to orchestrate builds across packages with proper dependency |
| 195 | +ordering (website must build before client). |
0 commit comments