diff --git a/packages/uikit/src/components/image.ts b/packages/uikit/src/components/image.ts index d5549fc9..974b3a19 100644 --- a/packages/uikit/src/components/image.ts +++ b/packages/uikit/src/components/image.ts @@ -107,7 +107,7 @@ export function createImage( setupCursorCleanup(hoveredSignal, initializers) const src = computed(() => readReactive(style.value?.src) ?? readReactive(properties.value?.src)) - loadResourceWithParams(texture, loadTextureImpl, initializers, src) + loadResourceWithParams(texture, loadTextureImpl, cleanupTexture, initializers, src) const textureAspectRatio = computed(() => { const tex = texture.value @@ -354,7 +354,13 @@ function transformInsideBorder( const textureLoader = new TextureLoader() -async function loadTextureImpl(src?: string | Texture) { +function cleanupTexture(texture: (Texture & { disposable?: boolean }) | undefined): void { + if (texture?.disposable === true) { + texture.dispose() + } +} + +async function loadTextureImpl(src?: string | Texture): Promise<(Texture & { disposable?: boolean }) | undefined> { if (src == null) { return Promise.resolve(undefined) } @@ -365,7 +371,7 @@ async function loadTextureImpl(src?: string | Texture) { const texture = await textureLoader.loadAsync(src) texture.colorSpace = SRGBColorSpace texture.matrixAutoUpdate = false - return texture + return Object.assign(texture, { disposable: true }) } catch (error) { console.error(error) return undefined diff --git a/packages/uikit/src/components/svg.ts b/packages/uikit/src/components/svg.ts index 739680bb..9a647cf9 100644 --- a/packages/uikit/src/components/svg.ts +++ b/packages/uikit/src/components/svg.ts @@ -1,5 +1,16 @@ import { Signal, computed, effect, signal } from '@preact/signals-core' -import { Box3, Group, Mesh, MeshBasicMaterial, Object3D, Plane, ShapeGeometry, Vector3 } from 'three' +import { + Box3, + BufferGeometry, + Group, + Material, + Mesh, + MeshBasicMaterial, + Object3D, + Plane, + ShapeGeometry, + Vector3, +} from 'three' import { Listeners } from '../index.js' import { Object3DRef, ParentContext, RootContext } from '../context.js' import { FlexNode, FlexNodeState, YogaProperties, createFlexNodeState } from '../flex/index.js' @@ -26,6 +37,7 @@ import { computedIsVisible, computedMergedProperties, createNode, + disposeGroup, keepAspectRatioPropertyTransformer, loadResourceWithParams, } from './utils.js' @@ -143,6 +155,7 @@ export function createSvg( loadResourceWithParams( svgObject, loadSvg, + disposeGroup, initializers, src, parentContext.root, diff --git a/packages/uikit/src/components/utils.ts b/packages/uikit/src/components/utils.ts index 8fcbaffb..6276fdcd 100644 --- a/packages/uikit/src/components/utils.ts +++ b/packages/uikit/src/components/utils.ts @@ -1,5 +1,5 @@ import { Signal, computed, effect } from '@preact/signals-core' -import { Color, Matrix4, Mesh, MeshBasicMaterial, Object3D } from 'three' +import { BufferGeometry, Color, Material, Matrix4, Mesh, MeshBasicMaterial, Object3D } from 'three' import { WithActive, addActiveHandlers } from '../active.js' import { WithPreferredColorScheme } from '../dark.js' import { WithHover, addHoverHandlers } from '../hover.js' @@ -16,6 +16,22 @@ import { computedInheritableProperty, } from '../properties/index.js' +export function disposeGroup(object: Object3D | undefined) { + object?.traverse((mesh) => { + if (!(mesh instanceof Mesh)) { + return + } + + if (mesh.material instanceof Material) { + mesh.material.dispose() + } + + if (mesh.geometry instanceof BufferGeometry) { + mesh.geometry.dispose() + } + }) +} + export function computedGlobalMatrix( parentMatrix: Signal, localMatrix: Signal, @@ -52,6 +68,7 @@ export type WithConditionals = WithHover & WithResponsive & WithPreferr export function loadResourceWithParams>( target: Signal, fn: (param: P, ...additional: A) => Promise, + cleanup: ((value: R) => void) | undefined, initializers: Initializers, param: Signal

| P, ...additionals: A @@ -72,6 +89,15 @@ export function loadResourceWithParams>( return () => (canceled = true) }), ) + if (cleanup != null) { + subscriptions.push(() => { + const { value } = target + if (value == null) { + return + } + cleanup(value) + }) + } return subscriptions }) }