Fixes missing icons in tgui [no gbp] (#94748)

## About The Pull Request
Should* fix it. 

tgui was set up to not block while waiting on the icon ref map via
#90270 (a perf win bc it's huge). However, if the icon ref map's
filename hadn't been sent yet, it would error and eventually quit. This
simplifies it by directly assigning the icons once we get the asset
rather than playing catch.

unrelated but removed vector.ts which was already in tgui-core

## Why It's Good For The Game
Fixes #94644
## Changelog
🆑
fix: Fixed an issue in TGUI that caused icons to not load sometimes
/🆑
This commit is contained in:
Jeremiah
2026-01-06 17:36:51 -05:00
committed by GitHub
parent 6ce9a03b3b
commit df8946306e
8 changed files with 23 additions and 95 deletions
+1 -1
View File
@@ -177,7 +177,7 @@ type ByondType = {
/**
* Maps icons to their ref
*/
iconRefMap: Record<string, string>;
iconRefMap: Record<string, string | undefined>;
/**
* Downloads a blob, platform-agnostic
-55
View File
@@ -1,55 +0,0 @@
/**
* N-dimensional vector manipulation functions.
*
* Vectors are plain number arrays, i.e. [x, y, z].
*
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { zip } from 'es-toolkit';
import { map, reduce } from 'es-toolkit/compat';
const ADD = (a: number, b: number): number => a + b;
const SUB = (a: number, b: number): number => a - b;
const MUL = (a: number, b: number): number => a * b;
const DIV = (a: number, b: number): number => a / b;
export type Vector = number[];
// It's really not ideal to bypass the type system and use `as Vector`
// however, there isn't a more eloquent way to type these
export const vecAdd = (...vecs: Vector[]): Vector => {
return map(zip<number>(...vecs) as Vector[], (x) => reduce(x, ADD)) as Vector;
};
export const vecSubtract = (...vecs: Vector[]): Vector => {
return map(zip<number>(...vecs) as Vector[], (x) => reduce(x, SUB)) as Vector;
};
export const vecMultiply = (...vecs: Vector[]): Vector => {
return map(zip<number>(...vecs) as Vector[], (x) => reduce(x, MUL)) as Vector;
};
export const vecDivide = (...vecs: Vector[]): Vector => {
return map(zip<number>(...vecs) as Vector[], (x) => reduce(x, DIV)) as Vector;
};
export const vecScale = (vec: Vector, n: number): Vector => {
return map(vec, (x) => x * n);
};
export const vecInverse = (vec: Vector): Vector => {
return map(vec, (x) => -x);
};
export const vecLength = (vec: Vector): number => {
return Math.sqrt(reduce(vecMultiply(vec, vec), ADD) as number);
};
export const vecNormalize = (vec: Vector): Vector => {
const length = vecLength(vec);
return map(vec, (c) => c / length);
};
+1 -1
View File
@@ -7,7 +7,7 @@
* @license MIT
*/
import { vecLength, vecSubtract } from 'common/vector';
import { vecLength, vecSubtract } from 'tgui-core/vector';
import { focusMap } from 'tgui/focus';
import { canStealFocus, globalEvents } from 'tgui-core/events';
-2
View File
@@ -1,13 +1,11 @@
import { Provider } from 'jotai';
import { store } from './events/store';
import { IconProvider } from './Icons';
import { RoutedComponent } from './routes';
export function App() {
return (
<Provider store={store}>
<RoutedComponent />
<IconProvider />
</Provider>
);
}
-33
View File
@@ -1,33 +0,0 @@
import { Suspense, useEffect } from 'react';
import { fetchRetry } from 'tgui-core/http';
import { resolveAsset } from './assets';
import { logger } from './logging';
function setIconRefMap(map: Record<string, string>): void {
Byond.iconRefMap = map;
}
function loadIconMap(): void {
fetchRetry(resolveAsset('icon_ref_map.json'))
.then((res) => res.json())
.then(setIconRefMap)
.catch((error) => logger.log(error));
}
function IconMapLoader(): null {
useEffect(() => {
if (Object.keys(Byond.iconRefMap).length === 0) {
loadIconMap();
}
}, []);
return null;
}
export function IconProvider() {
return (
<Suspense fallback={null}>
<IconMapLoader />
</Suspense>
);
}
+1 -1
View File
@@ -5,7 +5,7 @@
*/
import { storage } from 'common/storage';
import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'common/vector';
import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'tgui-core/vector';
import type { BooleanLike } from 'tgui-core/react';
import { createLogger } from './logging';
+19 -1
View File
@@ -1,9 +1,27 @@
import { loadMappings } from 'common/assets';
import { fetchRetry } from 'tgui-core/http';
import { loadedMappings } from '../../assets';
/// --------- Handlers ------------------------------------------------------///
/** This just lets us load in our own independent map */
export function handleLoadAssets(payload: Record<string, string>): void {
loadMappings(payload, loadedMappings);
if (
'icon_ref_map.json' in payload &&
Byond.iconRefMap &&
Object.keys(Byond.iconRefMap).length === 0
) {
fetchRetry(payload['icon_ref_map.json'])
.then((res) => res.json())
.then(setIconRefMap)
.catch(console.error);
}
}
/// --------- Helpers -------------------------------------------------------///
// https://biomejs.dev/linter/rules/no-assign-in-expressions/
function setIconRefMap(map: Record<string, string>): void {
Byond.iconRefMap = map;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { vecLength, vecSubtract } from 'common/vector';
import { vecLength, vecSubtract } from 'tgui-core/vector';
import { sortBy } from 'es-toolkit';
import { map } from 'es-toolkit/compat';
import {