mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-17 02:54:44 +01:00
3ab932dddc
## 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
117 lines
2.6 KiB
TypeScript
117 lines
2.6 KiB
TypeScript
import { inspect } from 'node:util';
|
|
|
|
import type { ServerWebSocket } from 'bun';
|
|
|
|
import { createLogger, directLog } from '../logging';
|
|
import { retrace } from './retrace';
|
|
|
|
let server: Bun.Server<unknown>;
|
|
const logger = createLogger('link');
|
|
const DEBUG = process.argv.includes('--debug');
|
|
|
|
export function setupLink() {
|
|
server = Bun.serve({
|
|
fetch: upgradeServer,
|
|
development: true,
|
|
hostname: '127.0.0.1',
|
|
port: 3000,
|
|
websocket: {
|
|
open(ws) {
|
|
ws.subscribe('link');
|
|
logger.log('client connected');
|
|
},
|
|
close(ws) {
|
|
ws.unsubscribe('link');
|
|
logger.log('client disconnected');
|
|
},
|
|
message: handleLinkMessage,
|
|
},
|
|
});
|
|
}
|
|
|
|
export function broadcastMessage(msg: Record<string, any>): void {
|
|
const subscribers = server.subscriberCount('link');
|
|
if (subscribers === 0) return;
|
|
|
|
server?.publish('link', JSON.stringify(msg));
|
|
}
|
|
|
|
function deserializeObject(str: any): any {
|
|
return JSON.parse(str, (_key: string, value: any) => {
|
|
if (typeof value === 'object' && value !== null) {
|
|
if (value.__undefined__) {
|
|
// NOTE: You should not rely on deserialized object's undefined,
|
|
// this is purely for inspection purposes.
|
|
return {
|
|
[inspect.custom]: () => undefined,
|
|
};
|
|
}
|
|
if (value.__number__) {
|
|
return parseFloat(value.__number__);
|
|
}
|
|
if (value.__error__) {
|
|
if (!value.stack) {
|
|
return value.string;
|
|
}
|
|
return retrace(value.stack);
|
|
}
|
|
return value;
|
|
}
|
|
return value;
|
|
});
|
|
}
|
|
|
|
function handleLinkMessage(
|
|
_ws: ServerWebSocket<unknown>,
|
|
message: string | Buffer<ArrayBufferLike>,
|
|
): void {
|
|
const deserializedMsg = deserializeObject(message);
|
|
const { type, payload } = deserializedMsg;
|
|
|
|
if (type === 'log') {
|
|
const { level, ns, args } = payload;
|
|
// Skip debug messages
|
|
if (level <= 0 && !DEBUG) return;
|
|
|
|
directLog(
|
|
ns,
|
|
...args.map((arg) => {
|
|
if (typeof arg === 'object') {
|
|
return inspect(arg, {
|
|
depth: Infinity,
|
|
colors: true,
|
|
compact: 8,
|
|
});
|
|
}
|
|
return arg;
|
|
}),
|
|
);
|
|
|
|
return;
|
|
}
|
|
if (type === 'relay') {
|
|
broadcastMessage(payload);
|
|
|
|
return;
|
|
}
|
|
|
|
logger.log('unhandled message', JSON.stringify(message));
|
|
}
|
|
|
|
function upgradeServer(req: Request, srv: Bun.Server<unknown>) {
|
|
const client = crypto.randomUUID();
|
|
|
|
const upgraded = srv.upgrade(req, {
|
|
data: {
|
|
id: client,
|
|
createdAt: Date.now(),
|
|
},
|
|
});
|
|
|
|
if (upgraded) {
|
|
return new Response('Ok');
|
|
} else {
|
|
return new Response('Upgrade failed', { status: 500 });
|
|
}
|
|
}
|