From 8f5a9f718885ec0b2d9ecd7dc8014b48efd8d1d2 Mon Sep 17 00:00:00 2001 From: DuckieTM Date: Sat, 7 Feb 2026 19:08:39 +0100 Subject: [PATCH] :up: Patch GlTextureSystem & Fix effects like BBRed - Monkey-patch the renderer's GlTextureSystem to prevent crashes from destroyed textures --- packages/avatar/src/AvatarImage.ts | 9 ++-- .../RoomWindowReflectionState.ts | 6 ++- .../avatar/AvatarVisualization.ts | 19 ++++++-- .../object/visualization/room/RoomPlane.ts | 22 +++++---- .../room/src/renderer/RoomSpriteCanvas.ts | 2 +- .../room/src/renderer/utils/ExtendedSprite.ts | 2 +- packages/utils/src/GetRenderer.ts | 46 ++++++++++++++++++- 7 files changed, 84 insertions(+), 22 deletions(-) diff --git a/packages/avatar/src/AvatarImage.ts b/packages/avatar/src/AvatarImage.ts index c85abb8..4af8b54 100644 --- a/packages/avatar/src/AvatarImage.ts +++ b/packages/avatar/src/AvatarImage.ts @@ -291,8 +291,6 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener clear: true }); - container.destroy(); - //@ts-ignore this._activeTexture.source.hitMap = null; @@ -585,7 +583,7 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener this._effectIdInUse = -1; } - if(effectChanged) this._cache.disposeInactiveActions(); + if(effectChanged) this._cache.disposeInactiveActions(0); if(this._lastActionsString != this._currentActionsString) { @@ -653,6 +651,11 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener if(animation.hasDirectionData()) this._directionOffset = animation.directionData.offset; if(animation.hasAvatarData()) this._avatarSpriteData = animation.avatarData; + + if(!this._isAnimating && (animation.spriteData?.length > 0 || animation.hasAvatarData())) + { + this._isAnimating = true; + } } } } diff --git a/packages/room/src/object/visualization/RoomWindowReflectionState.ts b/packages/room/src/object/visualization/RoomWindowReflectionState.ts index b4b390d..ed1f1ba 100644 --- a/packages/room/src/object/visualization/RoomWindowReflectionState.ts +++ b/packages/room/src/object/visualization/RoomWindowReflectionState.ts @@ -7,6 +7,7 @@ interface IWindowReflectionAvatarState id: number; texture: Texture; location: IVector3D; + verticalOffset: number; } export class RoomWindowReflectionState @@ -14,7 +15,7 @@ export class RoomWindowReflectionState private static _avatars: Map = new Map(); private static _updateId: number = 0; - public static setAvatar(id: number, texture: Texture, location: IVector3D): void + public static setAvatar(id: number, texture: Texture, location: IVector3D, verticalOffset: number = 0): void { if(!texture || !location) return; @@ -25,7 +26,8 @@ export class RoomWindowReflectionState this._avatars.set(id, { id, texture, - location: storedLocation + location: storedLocation, + verticalOffset }); // Always bump updateId so reflected walk cycles stay frame-synced diff --git a/packages/room/src/object/visualization/avatar/AvatarVisualization.ts b/packages/room/src/object/visualization/avatar/AvatarVisualization.ts index 3200625..4a158b6 100644 --- a/packages/room/src/object/visualization/avatar/AvatarVisualization.ts +++ b/packages/room/src/object/visualization/avatar/AvatarVisualization.ts @@ -76,6 +76,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement private _isAvatarReady: boolean; private _needsUpdate: boolean; private _geometryUpdateCounter: number; + private _reflectionVerticalOffset: number; private _additions: Map; @@ -127,6 +128,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement this._isAvatarReady = false; this._needsUpdate = false; this._geometryUpdateCounter = -1; + this._reflectionVerticalOffset = 0; this._additions = new Map(); } @@ -307,7 +309,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement sprite.offsetX = ((((-1 * scale) / 2) + _local_20[0]) - ((sprite.texture.width - scale) / 2)); sprite.offsetY = (((-(sprite.texture.height) + (scale / 4)) + _local_20[1]) + this._postureOffset); } - + if(this._isLaying) { if(this._layInside) sprite.relativeDepth = -0.5; @@ -329,8 +331,6 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement } } - this.updateWindowReflectionSource(); - const typingBubble = this.getAddition(AvatarVisualization.TYPING_BUBBLE_ID) as TypingBubbleAddition; if(typingBubble) @@ -451,6 +451,17 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement _local_21++; } } + + const avatarSprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR); + + if(avatarSprite?.texture) + { + const baseOffsetY = (-(avatarSprite.texture.height) + (scale / 4)); + + this._reflectionVerticalOffset = avatarSprite.offsetY - baseOffsetY; + } + + this.updateWindowReflectionSource(); } } @@ -1007,7 +1018,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement if(sprite?.texture) { - RoomWindowReflectionState.setAvatar(this.object.id, sprite.texture, this.object.getLocation()); + RoomWindowReflectionState.setAvatar(this.object.id, sprite.texture, this.object.getLocation(), this._reflectionVerticalOffset); return; } diff --git a/packages/room/src/object/visualization/room/RoomPlane.ts b/packages/room/src/object/visualization/room/RoomPlane.ts index 9a2feaf..4aeef53 100644 --- a/packages/room/src/object/visualization/room/RoomPlane.ts +++ b/packages/room/src/object/visualization/room/RoomPlane.ts @@ -91,8 +91,8 @@ export class RoomPlane implements IRoomPlane private _windowMasks: { leftSideLoc: number; rightSideLoc: number }[] = []; private _lastWindowReflectionUpdateId: number = -1; private _windowReflectionFirstSeenAt: Map = new Map(); - private _windowReflectionLastVisible: Map = new Map(); - private _windowReflectionFadeOut: Map = new Map(); + private _windowReflectionLastVisible: Map = new Map(); + private _windowReflectionFadeOut: Map = new Map(); constructor(origin: IVector3D, location: IVector3D, leftSide: IVector3D, rightSide: IVector3D, type: number, usesMask: boolean, secondaryNormals: IVector3D[], randomSeed: number, textureOffsetX: number = 0, textureOffsetY: number = 0, textureMaxX: number = 0, textureMaxY: number = 0) { @@ -884,8 +884,8 @@ export class RoomPlane implements IRoomPlane const container = new Container(); const visibleAvatarIds = new Set(); - const addReflectionSprite = (texture: Texture, location: IVector3D, alpha: number): boolean => { - if(!texture?.source || !location || alpha < 0) return false; + const addReflectionSprite = (texture: Texture, location: IVector3D, alpha: number, verticalOffset: number = 0): boolean => { + if(!texture?.source || texture.source.destroyed || !texture.source.style || !location || alpha < 0) return false; const relative = Vector3d.dif(location, this._location); const planeDistance = Math.abs(Vector3d.scalarProjection(relative, this._normal)); @@ -906,7 +906,7 @@ export class RoomPlane implements IRoomPlane if(!closestMask || (closestMask.score > 3)) return false; const x = (canvasWidth - ((canvasWidth * leftSideLoc) / this._leftSide.length)); - const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)); + const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)) + verticalOffset; const sprite = new Sprite(texture); sprite.anchor.set(0.5, 1); @@ -922,7 +922,7 @@ export class RoomPlane implements IRoomPlane for(const avatar of avatars) { - if(!avatar?.texture?.source || !avatar.location) continue; + if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location) continue; let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id); @@ -935,7 +935,7 @@ export class RoomPlane implements IRoomPlane const progress = (elapsed / fadeDurationMs); const alpha = (0.4 * progress); - if(!addReflectionSprite(avatar.texture, avatar.location, alpha)) continue; + if(!addReflectionSprite(avatar.texture, avatar.location, alpha, avatar.verticalOffset || 0)) continue; if(!this._windowReflectionFirstSeenAt.has(avatar.id)) this._windowReflectionFirstSeenAt.set(avatar.id, firstSeenAt); @@ -947,7 +947,8 @@ export class RoomPlane implements IRoomPlane this._windowReflectionLastVisible.set(avatar.id, { texture: avatar.texture, - location: storedLocation + location: storedLocation, + verticalOffset: avatar.verticalOffset || 0 }); } @@ -955,7 +956,7 @@ export class RoomPlane implements IRoomPlane { if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue; - if(!lastVisible.texture?.source) + if(!lastVisible.texture?.source || lastVisible.texture.source.destroyed || !lastVisible.texture.source.style) { this._windowReflectionLastVisible.delete(id); this._windowReflectionFirstSeenAt.delete(id); @@ -966,6 +967,7 @@ export class RoomPlane implements IRoomPlane this._windowReflectionFadeOut.set(id, { texture: lastVisible.texture, location: lastVisible.location, + verticalOffset: lastVisible.verticalOffset, startedAt: now }); @@ -986,7 +988,7 @@ export class RoomPlane implements IRoomPlane const alpha = (0.4 * (1 - (elapsed / fadeDurationMs))); - if(!addReflectionSprite(fadeOut.texture, fadeOut.location, alpha)) this._windowReflectionFadeOut.delete(id); + if(!addReflectionSprite(fadeOut.texture, fadeOut.location, alpha, fadeOut.verticalOffset)) this._windowReflectionFadeOut.delete(id); } if(!container.children.length) diff --git a/packages/room/src/renderer/RoomSpriteCanvas.ts b/packages/room/src/renderer/RoomSpriteCanvas.ts index 0c52850..a37a758 100644 --- a/packages/room/src/renderer/RoomSpriteCanvas.ts +++ b/packages/room/src/renderer/RoomSpriteCanvas.ts @@ -409,7 +409,7 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas const texture = sprite.texture; const baseTexture = texture && texture.source; - if(!texture || !baseTexture || baseTexture.destroyed) continue; + if(!texture || !baseTexture) continue; const spriteX = ((x + sprite.offsetX) + this._screenOffsetX); const spriteY = ((y + sprite.offsetY) + this._screenOffsetY); diff --git a/packages/room/src/renderer/utils/ExtendedSprite.ts b/packages/room/src/renderer/utils/ExtendedSprite.ts index 99ec957..f3b08be 100644 --- a/packages/room/src/renderer/utils/ExtendedSprite.ts +++ b/packages/room/src/renderer/utils/ExtendedSprite.ts @@ -29,7 +29,7 @@ export class ExtendedSprite extends Sprite public setTexture(texture: Texture): void { - if(!texture || texture.source?.destroyed) texture = Texture.EMPTY; + if(!texture) texture = Texture.EMPTY; if(texture === this.texture) return; diff --git a/packages/utils/src/GetRenderer.ts b/packages/utils/src/GetRenderer.ts index 3c2188e..d0ebd94 100644 --- a/packages/utils/src/GetRenderer.ts +++ b/packages/utils/src/GetRenderer.ts @@ -1,13 +1,57 @@ -import { AutoDetectOptions, Renderer, autoDetectRenderer } from 'pixi.js'; +import { AutoDetectOptions, Renderer, Texture, autoDetectRenderer } from 'pixi.js'; let renderer: Renderer = null; +const patchGlTextureSystem = (r: Renderer): void => +{ + const textureSystem = (r as any).texture; + + if(!textureSystem) return; + + const proto = Object.getPrototypeOf(textureSystem); + + if(!proto) return; + + const origUpdateStyle = proto.updateStyle; + + if(origUpdateStyle && !proto.__patchedUpdateStyle) + { + proto.updateStyle = function(source: any, firstCreation: boolean) + { + if(!source || source.destroyed || !source.style) return; + + return origUpdateStyle.call(this, source, firstCreation); + }; + + proto.__patchedUpdateStyle = true; + } + + const origBindSource = proto.bindSource; + + if(origBindSource && !proto.__patchedBindSource) + { + proto.bindSource = function(source: any, location = 0) + { + if(!source || source.destroyed || !source.style) + { + source = Texture.EMPTY.source; + } + + return origBindSource.call(this, source, location); + }; + + proto.__patchedBindSource = true; + } +}; + export const PrepareRenderer = async (options: Partial): Promise => { renderer = await autoDetectRenderer(options); renderer.events?.destroy(); + patchGlTextureSystem(renderer); + return renderer; };