From 111d0b9e921ad81cb24c32f1ed010b139fb631f4 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 6 Oct 2025 17:14:13 +0200 Subject: [PATCH 1/9] feat: introduce TextureError class for improved error handling in texture management --- exports/index.ts | 4 ++++ src/core/CoreTextureManager.ts | 19 ++++++++++++++++--- src/core/TextureError.ts | 22 ++++++++++++++++++++++ src/core/TextureMemoryManager.ts | 11 ++++++++--- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 src/core/TextureError.ts diff --git a/exports/index.ts b/exports/index.ts index b2854fa9..a8e6c5ac 100644 --- a/exports/index.ts +++ b/exports/index.ts @@ -51,6 +51,10 @@ export { type TextureMap, } from '../src/core/CoreTextureManager.js'; export type { MemoryInfo } from '../src/core/TextureMemoryManager.js'; +export type { + TextureError, + TextureErrorCode, +} from '../src/core/TextureError.js'; export type { ShaderMap, EffectMap } from '../src/core/CoreShaderManager.js'; export type { TextRendererMap } from '../src/core/text-rendering/renderers/TextRenderer.js'; export type { TrFontFaceMap } from '../src/core/text-rendering/font-face-types/TrFontFace.js'; diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index 2af18efc..cbc51d7c 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -32,6 +32,7 @@ import { validateCreateImageBitmap, type CreateImageBitmapSupport, } from './lib/validateImageBitmap.js'; +import { TextureError } from './TextureError.js'; /** * Augmentable map of texture class types @@ -324,7 +325,10 @@ export class CoreTextureManager extends EventEmitter { let texture: Texture | undefined; const TextureClass = this.txConstructors[textureType]; if (!TextureClass) { - throw new Error(`Texture type "${textureType}" is not registered`); + throw new TextureError( + `Texture type "${textureType}" is not registered`, + 'TEXTURE_TYPE_NOT_REGISTERED', + ); } const cacheKey = TextureClass.makeCacheKey(props as any); @@ -411,7 +415,13 @@ export class CoreTextureManager extends EventEmitter { this.stage.txMemManager.criticalCleanupRequested === true ) { // we're at a critical memory threshold, don't upload textures - texture.setState('failed', new Error('Memory threshold exceeded')); + texture.setState( + 'failed', + new TextureError( + 'Memory threshold exceeded', + 'MEMORY_THRESHOLD_EXCEEDED', + ), + ); return; } @@ -428,7 +438,10 @@ export class CoreTextureManager extends EventEmitter { if (texture.textureData === null) { texture.setState( 'failed', - new Error('Texture data is null, cannot upload texture'), + new TextureError( + 'Texture data is null, cannot upload texture', + 'TEXTURE_DATA_NULL', + ), ); return; } diff --git a/src/core/TextureError.ts b/src/core/TextureError.ts new file mode 100644 index 00000000..578769a9 --- /dev/null +++ b/src/core/TextureError.ts @@ -0,0 +1,22 @@ +export const TextureErrorCode = { + MEMORY_THRESHOLD_EXCEEDED: 'MEMORY_THRESHOLD_EXCEEDED', + TEXTURE_DATA_NULL: 'TEXTURE_DATA_NULL', + TEXTURE_TYPE_NOT_REGISTERED: 'TEXTURE_TYPE_NOT_REGISTERED', +} as const; + +type TextureErrorCode = + (typeof TextureErrorCode)[keyof typeof TextureErrorCode]; + +export class TextureError extends Error { + code?: TextureErrorCode; + + constructor(message: string, code?: TextureErrorCode) { + super(message); + + this.name = new.target.name; + + if (code) { + this.code = code; + } + } +} diff --git a/src/core/TextureMemoryManager.ts b/src/core/TextureMemoryManager.ts index 8d407608..92c49a7f 100644 --- a/src/core/TextureMemoryManager.ts +++ b/src/core/TextureMemoryManager.ts @@ -122,6 +122,7 @@ export class TextureMemoryManager { private debugLogging: boolean; private lastCleanupTime = 0; private baselineMemoryAllocation: number; + private lastCriticalWarnTime = 0; public criticalCleanupRequested = false; public doNotExceedCriticalThreshold: boolean; @@ -315,9 +316,13 @@ export class TextureMemoryManager { }); if (this.debugLogging === true || isProductionEnvironment() === false) { - console.warn( - `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, - ); + const now = Date.now(); + if (now - this.lastCriticalWarnTime >= 500) { + this.lastCriticalWarnTime = now; + console.warn( + `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, + ); + } } } else { this.criticalCleanupRequested = false; From cd25b33ab6623cec57bed149b5837d0c9ed191a8 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 6 Oct 2025 20:06:52 +0200 Subject: [PATCH 2/9] fix: restored TextureErrorCode as enum --- src/core/CoreTextureManager.ts | 8 ++++---- src/core/TextureError.ts | 13 +++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index cbc51d7c..486e0e74 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -32,7 +32,7 @@ import { validateCreateImageBitmap, type CreateImageBitmapSupport, } from './lib/validateImageBitmap.js'; -import { TextureError } from './TextureError.js'; +import { TextureError, TextureErrorCode } from './TextureError.js'; /** * Augmentable map of texture class types @@ -327,7 +327,7 @@ export class CoreTextureManager extends EventEmitter { if (!TextureClass) { throw new TextureError( `Texture type "${textureType}" is not registered`, - 'TEXTURE_TYPE_NOT_REGISTERED', + TextureErrorCode.TEXTURE_TYPE_NOT_REGISTERED, ); } @@ -419,7 +419,7 @@ export class CoreTextureManager extends EventEmitter { 'failed', new TextureError( 'Memory threshold exceeded', - 'MEMORY_THRESHOLD_EXCEEDED', + TextureErrorCode.MEMORY_THRESHOLD_EXCEEDED, ), ); return; @@ -440,7 +440,7 @@ export class CoreTextureManager extends EventEmitter { 'failed', new TextureError( 'Texture data is null, cannot upload texture', - 'TEXTURE_DATA_NULL', + TextureErrorCode.TEXTURE_DATA_NULL, ), ); return; diff --git a/src/core/TextureError.ts b/src/core/TextureError.ts index 578769a9..1ca13f81 100644 --- a/src/core/TextureError.ts +++ b/src/core/TextureError.ts @@ -1,11 +1,8 @@ -export const TextureErrorCode = { - MEMORY_THRESHOLD_EXCEEDED: 'MEMORY_THRESHOLD_EXCEEDED', - TEXTURE_DATA_NULL: 'TEXTURE_DATA_NULL', - TEXTURE_TYPE_NOT_REGISTERED: 'TEXTURE_TYPE_NOT_REGISTERED', -} as const; - -type TextureErrorCode = - (typeof TextureErrorCode)[keyof typeof TextureErrorCode]; +export enum TextureErrorCode { + MEMORY_THRESHOLD_EXCEEDED = 'MEMORY_THRESHOLD_EXCEEDED', + TEXTURE_DATA_NULL = 'TEXTURE_DATA_NULL', + TEXTURE_TYPE_NOT_REGISTERED = 'TEXTURE_TYPE_NOT_REGISTERED', +} export class TextureError extends Error { code?: TextureErrorCode; From 1daf33e5dd4d703b34e771e97abd54f77068ac8e Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 6 Oct 2025 20:08:56 +0200 Subject: [PATCH 3/9] fix: inspector texture error --- src/core/textures/Texture.ts | 3 ++- src/main-api/Inspector.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/textures/Texture.ts b/src/core/textures/Texture.ts index 6918c789..b7aa7f44 100644 --- a/src/core/textures/Texture.ts +++ b/src/core/textures/Texture.ts @@ -22,6 +22,7 @@ import type { SubTextureProps } from './SubTexture.js'; import type { Dimensions } from '../../common/CommonTypes.js'; import { EventEmitter } from '../../common/EventEmitter.js'; import type { CoreContextTexture } from '../renderers/CoreContextTexture.js'; +import type { TextureError } from '../TextureError.js'; /** * Event handler for when a Texture is freed @@ -188,7 +189,7 @@ export abstract class Texture extends EventEmitter { return this._dimensions; } - get error(): Error | null { + get error(): TextureError | null { return this._error; } diff --git a/src/main-api/Inspector.ts b/src/main-api/Inspector.ts index a6d6b6ca..e3cdd9d4 100644 --- a/src/main-api/Inspector.ts +++ b/src/main-api/Inspector.ts @@ -533,7 +533,10 @@ export class Inspector { // Update error information if present if (texture.error) { - div.setAttribute('data-texture-error', texture.error.message); + div.setAttribute( + 'data-texture-error', + texture.error.code || texture.error.message, + ); } else { div.removeAttribute('data-texture-error'); } From 327b0f5e87e8f64445865d8db9ac896a10f94eae Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Tue, 7 Oct 2025 21:34:17 +0200 Subject: [PATCH 4/9] refactor: added TextureError overloads --- src/core/CoreTextureManager.ts | 10 ++++----- src/core/TextureError.ts | 39 ++++++++++++++++++++++++++++------ src/core/textures/Texture.ts | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/core/CoreTextureManager.ts b/src/core/CoreTextureManager.ts index 486e0e74..41cd55c3 100644 --- a/src/core/CoreTextureManager.ts +++ b/src/core/CoreTextureManager.ts @@ -326,11 +326,12 @@ export class CoreTextureManager extends EventEmitter { const TextureClass = this.txConstructors[textureType]; if (!TextureClass) { throw new TextureError( - `Texture type "${textureType}" is not registered`, TextureErrorCode.TEXTURE_TYPE_NOT_REGISTERED, + `Texture type "${textureType}" is not registered`, ); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument const cacheKey = TextureClass.makeCacheKey(props as any); if (cacheKey && this.keyCache.has(cacheKey)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -417,10 +418,7 @@ export class CoreTextureManager extends EventEmitter { // we're at a critical memory threshold, don't upload textures texture.setState( 'failed', - new TextureError( - 'Memory threshold exceeded', - TextureErrorCode.MEMORY_THRESHOLD_EXCEEDED, - ), + new TextureError(TextureErrorCode.MEMORY_THRESHOLD_EXCEEDED), ); return; } @@ -439,8 +437,8 @@ export class CoreTextureManager extends EventEmitter { texture.setState( 'failed', new TextureError( - 'Texture data is null, cannot upload texture', TextureErrorCode.TEXTURE_DATA_NULL, + 'Texture data is null, cannot upload texture', ), ); return; diff --git a/src/core/TextureError.ts b/src/core/TextureError.ts index 1ca13f81..34ae8127 100644 --- a/src/core/TextureError.ts +++ b/src/core/TextureError.ts @@ -4,16 +4,43 @@ export enum TextureErrorCode { TEXTURE_TYPE_NOT_REGISTERED = 'TEXTURE_TYPE_NOT_REGISTERED', } +const defaultMessages: Record = { + [TextureErrorCode.MEMORY_THRESHOLD_EXCEEDED]: 'Memory threshold exceeded', + [TextureErrorCode.TEXTURE_DATA_NULL]: 'Texture data is null', + [TextureErrorCode.TEXTURE_TYPE_NOT_REGISTERED]: + 'Texture type is not registered', +}; + export class TextureError extends Error { code?: TextureErrorCode; - constructor(message: string, code?: TextureErrorCode) { - super(message); + constructor(message: string); + constructor(code: TextureErrorCode, message?: string); + constructor(codeOrMessage: TextureErrorCode | string, maybeMessage?: string) { + const isCode = Object.values(TextureErrorCode).includes( + codeOrMessage as TextureErrorCode, + ); - this.name = new.target.name; - - if (code) { - this.code = code; + const code = isCode ? (codeOrMessage as TextureErrorCode) : undefined; + let message: string; + if (isCode && code) { + message = maybeMessage ?? defaultMessages[code]; + } else { + message = String(codeOrMessage); } + + super(message); + this.name = new.target.name; + if (code) this.code = code; } } + +export function isTextureError(err: unknown): err is TextureError { + return ( + err instanceof TextureError || + (typeof err === 'object' && + err !== null && + (err as { name?: unknown }).name === 'TextureError' && + typeof (err as { code?: unknown }).code === 'string') + ); +} diff --git a/src/core/textures/Texture.ts b/src/core/textures/Texture.ts index b7aa7f44..d7d33d30 100644 --- a/src/core/textures/Texture.ts +++ b/src/core/textures/Texture.ts @@ -136,7 +136,7 @@ export abstract class Texture extends EventEmitter { * `null`. */ private _dimensions: Dimensions | null = null; - private _error: Error | null = null; + private _error: TextureError | null = null; // aggregate state public state: TextureState = 'initial'; From f06da784338acfa93c7e485fba61bc93e314785f Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Wed, 8 Oct 2025 21:21:14 +0200 Subject: [PATCH 5/9] fix: restore memory warning --- src/core/TextureMemoryManager.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/core/TextureMemoryManager.ts b/src/core/TextureMemoryManager.ts index 92c49a7f..8d407608 100644 --- a/src/core/TextureMemoryManager.ts +++ b/src/core/TextureMemoryManager.ts @@ -122,7 +122,6 @@ export class TextureMemoryManager { private debugLogging: boolean; private lastCleanupTime = 0; private baselineMemoryAllocation: number; - private lastCriticalWarnTime = 0; public criticalCleanupRequested = false; public doNotExceedCriticalThreshold: boolean; @@ -316,13 +315,9 @@ export class TextureMemoryManager { }); if (this.debugLogging === true || isProductionEnvironment() === false) { - const now = Date.now(); - if (now - this.lastCriticalWarnTime >= 500) { - this.lastCriticalWarnTime = now; - console.warn( - `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, - ); - } + console.warn( + `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, + ); } } else { this.criticalCleanupRequested = false; From 655d4cac848d880b1c65243dfc787f5f33c33dda Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Wed, 8 Oct 2025 21:38:26 +0200 Subject: [PATCH 6/9] fix: only emit the warning once per over-threshold period --- src/core/TextureMemoryManager.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/TextureMemoryManager.ts b/src/core/TextureMemoryManager.ts index 8d407608..53f8e05c 100644 --- a/src/core/TextureMemoryManager.ts +++ b/src/core/TextureMemoryManager.ts @@ -122,6 +122,7 @@ export class TextureMemoryManager { private debugLogging: boolean; private lastCleanupTime = 0; private baselineMemoryAllocation: number; + private hasWarnedAboveCritical = false; public criticalCleanupRequested = false; public doNotExceedCriticalThreshold: boolean; @@ -313,14 +314,18 @@ export class TextureMemoryManager { memUsed: this.memUsed, criticalThreshold: this.criticalThreshold, }); - - if (this.debugLogging === true || isProductionEnvironment() === false) { - console.warn( - `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, - ); + // Only emit the warning once per over-threshold period + if (!this.hasWarnedAboveCritical) { + if (this.debugLogging === true || isProductionEnvironment() === false) { + console.warn( + `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, + ); + } + this.hasWarnedAboveCritical = true; } } else { this.criticalCleanupRequested = false; + this.hasWarnedAboveCritical = false; } } From cb65b3dea411a1519c209403e00a1f6cd3c92727 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Thu, 9 Oct 2025 09:55:53 +0200 Subject: [PATCH 7/9] fix: combined conditions --- src/core/TextureMemoryManager.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/TextureMemoryManager.ts b/src/core/TextureMemoryManager.ts index 53f8e05c..12b99c27 100644 --- a/src/core/TextureMemoryManager.ts +++ b/src/core/TextureMemoryManager.ts @@ -315,12 +315,14 @@ export class TextureMemoryManager { criticalThreshold: this.criticalThreshold, }); // Only emit the warning once per over-threshold period - if (!this.hasWarnedAboveCritical) { - if (this.debugLogging === true || isProductionEnvironment() === false) { - console.warn( - `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, - ); - } + if ( + !this.hasWarnedAboveCritical && + (this.debugLogging === true || isProductionEnvironment() === false) + ) { + console.warn( + `[TextureMemoryManager] Memory usage above critical threshold after cleanup: ${this.memUsed}`, + ); + this.hasWarnedAboveCritical = true; } } else { From 001b8df44e7f49af72e23ba7e6c6fa7f7360211d Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Fri, 10 Oct 2025 16:09:16 +0200 Subject: [PATCH 8/9] fix: NodeTextureFailedPayload error type --- src/common/CommonTypes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/CommonTypes.ts b/src/common/CommonTypes.ts index 33671c49..bf077830 100644 --- a/src/common/CommonTypes.ts +++ b/src/common/CommonTypes.ts @@ -18,6 +18,7 @@ */ import type { CoreNodeRenderState } from '../core/CoreNode.js'; +import type { TextureError } from '../core/TextureError.js'; /** * Types shared between Main Space and Core Space @@ -71,7 +72,7 @@ export type NodeTextFailedPayload = { */ export type NodeTextureFailedPayload = { type: 'texture'; - error: Error; + error: TextureError; }; /** From ae74cc7addb2a67c142ac34c1235240f5ec1cdd2 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Fri, 10 Oct 2025 16:11:45 +0200 Subject: [PATCH 9/9] fix: export TextureError --- exports/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exports/index.ts b/exports/index.ts index a8e6c5ac..b0845452 100644 --- a/exports/index.ts +++ b/exports/index.ts @@ -51,9 +51,10 @@ export { type TextureMap, } from '../src/core/CoreTextureManager.js'; export type { MemoryInfo } from '../src/core/TextureMemoryManager.js'; -export type { +export { TextureError, TextureErrorCode, + isTextureError, } from '../src/core/TextureError.js'; export type { ShaderMap, EffectMap } from '../src/core/CoreShaderManager.js'; export type { TextRendererMap } from '../src/core/text-rendering/renderers/TextRenderer.js';