Files
Jeremiah 3ab932dddc Modernized tgui backend (#94573)
## About The Pull Request
This has been a long project that has undergone several iterations. I
needed to complete #94514 just to get here, and this is surprisingly
simpler despite handling much more code. This PR replaces the bespoke
redux-based solution with [jotai](https://jotai.org/), a lean state
manager. For the most part, this means the UI will be a bit more
responsive.

I'll post the philosophy from the docs:

In the previous TGUI backend state, both DM messages and UI actions were
handled through an event message system using actions, selectors,
reducers, and middleware. This new event system is designed to handle
only DM messages - separating UI actions into direct state access (eg
setSomeState(true)) or helpers (eg updateSetting({ thing: true})).

The idea behind this was to reduce the amount of abstractions needed and
draw a clear line between what is a server message and what is a UI
action.

There are three components to this system:

1. The event bus, which maintains the list of handlers.
2. The handlers, which delegate backend calls and update application
state.
3. The store, ie the application state.
## Why It's Good For The Game
The previous solution would do a full tree rerender when any backend
message or UI event took place, including pings. We don't need to do
this now. We can atomically update the components when they need
updated.

The code should be easier to maintain (fully documented and concise)
The UI should be more reliable
## Changelog
N/A
2025-12-26 09:53:07 -05:00

70 lines
1.9 KiB
TypeScript

import { createRequire } from 'node:module';
import { config } from '../../rspack.config-dev';
import { loadSourceMaps } from './link/retrace';
import { broadcastMessage, setupLink } from './link/server';
import { createLogger } from './logging';
import { reloadByondCache } from './reloader';
import { resolveGlob } from './util';
const logger = createLogger('rspack');
export class RspackCompiler {
rspack: any;
config: any;
bundleDir: string;
async setup() {
// Create a require context that is relative to project root
// and retrieve all necessary dependencies.
const requireFromRoot = createRequire(`${import.meta.dirname}/../../..`);
const rspack = await requireFromRoot('@rspack/core');
this.rspack = rspack;
this.config = config;
this.bundleDir = config.output?.path || '';
}
async watch() {
logger.log('setting up');
setupLink();
// Instantiate the compiler
const compiler = this.rspack.rspack(this.config);
// Clear garbage before compiling
compiler.hooks.watchRun.tapPromise('tgui-dev-server', async () => {
const files = await resolveGlob(this.bundleDir, '*.hot-update.*');
for (const file of files) {
await Bun.file(file).delete();
}
logger.log('compiling');
});
// Start reloading when it's finished
compiler.hooks.done.tap('tgui-dev-server', async () => {
// Load source maps
await loadSourceMaps(this.bundleDir);
// Reload cache
await reloadByondCache(this.bundleDir);
// Notify all clients that update has happened
broadcastMessage({
type: 'hotUpdate',
});
});
// Start watching
logger.log('watching for changes');
compiler.watch({}, (err, stats) => {
if (err) {
logger.error('compilation error', err);
return;
}
stats
?.toString(this.config.stats)
.split('\n')
.forEach((line) => {
logger.log(line);
});
});
}
}