Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 449 memory management #466

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 4 additions & 30 deletions playground/src/pages/perf/AkuAku.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { dispose } from '@tresjs/core'
import { useGLTF } from '@tresjs/cientos'
import { useControls } from '@tresjs/leches'

const { nodes } = await useGLTF(
'https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/aku-aku/AkuAku.gltf',
Expand All @@ -9,38 +9,12 @@ const { nodes } = await useGLTF(

const model = nodes.AkuAku

/* useControls({
button: {
label: 'Manual dispose',
type: 'button',
onClick() {
disposeModel()
},
},
}) */

function disposeModel() {
console.log('disposingModel')
model.traverse((child) => {
if (child.isMesh) {
// Dispose of the material
if (child.material) {
child.material.dispose()
}

// Dispose of the geometry
if (child.geometry) {
child.geometry.dispose()
}
}
})
console.log('disposingModel Finished')
dispose(model)
}

model.traverse((child) => {
if (child.material) {
console.log('child.material', child.material.uuid)
}
onUnmounted(() => {
disposeModel()
})
</script>

Expand Down
28 changes: 18 additions & 10 deletions playground/src/pages/perf/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const gl = {

const router = useRouter()

const { sphere } = useControls({
sphere: true,
const { isShown } = useControls({
isShown: true,
})

const ctx = ref(null)
Expand Down Expand Up @@ -51,15 +51,23 @@ useControls({
<TresPerspectiveCamera :position="[3, 3, 3]" />
<OrbitControls />
<Suspense>
<AkuAku v-if="sphere" />
<AkuAku v-if="isShown" />
</Suspense>
<!-- <TresMesh
v-if="sphere.value"
:position="[0, 0, 0]"
>
<TresSphereGeometry />
<TresMeshStandardMaterial color="teal" />
</TresMesh> -->
<!-- <TresGroup v-if="isShown">
<TresMesh
:position="[0, 0, 0]"
>
<TresSphereGeometry />
<TresMeshToonMaterial color="teal" />
</TresMesh>
<TresMesh

:position="[2, 0, 0]"
>
<TresSphereGeometry />
<TresMeshToonMaterial color="pink" />
</TresMesh>
</TresGroup> -->
<TresAmbientLight :intensity="1" />
</TresCanvas>
</template>
109 changes: 64 additions & 45 deletions src/core/nodeOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,71 +115,90 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
},
remove(node) {
if (!node) return
// remove is only called on the node being removed and not on child nodes.

if (node.isObject3D) {
const object3D = node as unknown as Object3D
const disposeMaterialsAndGeometries = (object3D: Object3D) => {
const tresObject3D = object3D as TresObject3D

const disposeMaterialsAndGeometries = (object3D: Object3D) => {
const tresObject3D = object3D as TresObject3D

if (!object3D.userData.tres__materialViaProp) {
tresObject3D.material?.dispose()
tresObject3D.material = undefined
}

if (!object3D.userData.tres__geometryViaProp) {
tresObject3D.geometry?.dispose()
tresObject3D.geometry = undefined
}
if (!object3D.userData.tres__materialViaProp) {
tresObject3D.material?.dispose()
delete tresObject3D.material
}

if (!object3D.userData.tres__geometryViaProp) {
tresObject3D.geometry?.dispose()
delete tresObject3D.geometry
}
}

const deregisterAtPointerEventHandler = scene?.userData.tres__deregisterAtPointerEventHandler
const deregisterBlockingObjectAtPointerEventHandler
const deregisterAtPointerEventHandler = scene?.userData.tres__deregisterAtPointerEventHandler
const deregisterBlockingObjectAtPointerEventHandler
= scene?.userData.tres__deregisterBlockingObjectAtPointerEventHandler

const deregisterAtPointerEventHandlerIfRequired = (object: TresObject) => {
const deregisterAtPointerEventHandlerIfRequired = (object: TresObject) => {

if (!deregisterBlockingObjectAtPointerEventHandler)
throw 'could not find tres__deregisterBlockingObjectAtPointerEventHandler on scene\'s userData'
if (!deregisterBlockingObjectAtPointerEventHandler)
throw 'could not find tres__deregisterBlockingObjectAtPointerEventHandler on scene\'s userData'

scene?.userData.tres__deregisterBlockingObjectAtPointerEventHandler?.(object as Object3D)
scene?.userData.tres__deregisterBlockingObjectAtPointerEventHandler?.(object as Object3D)

if (!deregisterAtPointerEventHandler)
throw 'could not find tres__deregisterAtPointerEventHandler on scene\'s userData'
if (!deregisterAtPointerEventHandler)
throw 'could not find tres__deregisterAtPointerEventHandler on scene\'s userData'

if (
object?.onClick
if (
object?.onClick
|| object?.onPointerMove
|| object?.onPointerEnter
|| object?.onPointerLeave
)
deregisterAtPointerEventHandler?.(object as Object3D)
}
)
deregisterAtPointerEventHandler?.(object as Object3D)
}

const deregisterCameraIfRequired = (object: Object3D) => {
const deregisterCamera = scene?.userData.tres__deregisterCamera
const deregisterCameraIfRequired = (object: Object3D) => {
const deregisterCamera = scene?.userData.tres__deregisterCamera

if (!deregisterCamera)
throw 'could not find tres__deregisterCamera on scene\'s userData'
if (!deregisterCamera)
throw 'could not find tres__deregisterCamera on scene\'s userData'

if ((object as Camera).isCamera)
deregisterCamera?.(object as Camera)
}
if ((object as Camera).isCamera)
deregisterCamera?.(object as Camera)
}

// Since remove is only called on the node being removed and not on child nodes.
// We need to recursively remove objects.
// In order for an object to be able to dispose it has to have
// - a dispose method,
// - it cannot be a <primitive object={...} />
// - it cannot be a THREE.Scene, because three has broken it's own api
//
const isPrimitive = node?.primitive
const shouldDispose = !isPrimitive && (node.dispose === undefined ? node.dispose !== null : false)

// Add code to detach
if (node?.isObject3D && node.parent?.isObject3D) {
node.parent.remove(node)
node.removeFromParent?.()
object3D.traverse((child: Object3D) => {
disposeMaterialsAndGeometries(child)
deregisterCameraIfRequired(child)
deregisterAtPointerEventHandlerIfRequired?.(child as TresObject)
})

disposeMaterialsAndGeometries(object3D)
deregisterCameraIfRequired(object3D)
deregisterAtPointerEventHandlerIfRequired?.(object3D as TresObject)
}

node.dispose?.()
// Only dispose if it's not a primitive and it has a dispose method,
// if its a primitive, disposal needs to be handled by the user.
if (!isPrimitive && node?.isObject3D) {
node.children.forEach((child: TresObject) => nodeOps.remove(child))
Tinoooo marked this conversation as resolved.
Show resolved Hide resolved
disposeMaterialsAndGeometries(node as Object3D)
deregisterCameraIfRequired(node as Object3D)
deregisterAtPointerEventHandlerIfRequired?.(node as TresObject)
for (const p in node) {
if (p !== 'geometry' && p !== 'material') {
;(p as any).dispose?.()
delete node[p]
}
}
}

if (shouldDispose && node.dispose && node.type !== 'Scene') {
node.dispose()
}

// Add invalidation code similar to https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/utils.ts#L436
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an open todo?

},
patchProp(node, prop, _prevValue, nextValue) {
if (node) {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { App } from 'vue'
import TresCanvas from './components/TresCanvas.vue'
import { normalizeColor, normalizeVectorFlexibleParam } from './utils/normalize'
import { dispose } from './utils/'
import templateCompilerOptions from './utils/template-compiler-options'

export * from './composables'
Expand Down Expand Up @@ -29,4 +30,5 @@ export {
normalizeColor,
normalizeVectorFlexibleParam,
templateCompilerOptions,
dispose,
}
9 changes: 9 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,12 @@ export function deepArrayEqual(arr1: any[], arr2: any[]): boolean {
* TypeSafe version of Array.isArray
*/
export const isArray = Array.isArray as (a: any) => a is any[] | readonly any[]

// Disposes an object and all its properties
export function dispose<TresObj extends { dispose?: () => void; type?: string; [key: string]: any }>(obj: TresObj) {
if (obj.dispose && obj.type !== 'Scene') obj.dispose()
for (const p in obj) {
;(p as any).dispose?.()
delete obj[p]
}
}