R3F dispose not working (memory is not reduced)

With React conditional rendering, I removed the GLTFModel, but memory is not reduced.

// model with GLTF
function GLTFModel(...) { ... }

// GLTF container
function GLTFContainer() {
  const [show, setShow] = useState(true)
  return show 
            ? <GLTFModel onClick={() => setShow(false) } 
            : <group />
}

Fiber Auto disposes, with the exception of primitive. Primitive points to a foreign object that you or something else control, it can’t just go and destroy it. You can dispose yourself if you control the object yourself.

On top of that all loaders are cached, two useGLTF with the same url will return the same object data, it won’t load or parse twice, but after unmount the cached data is still there.

You have access to the cache and can clear it but usually that’s a rare use case. Most webgl projects will load all assets up front and do not mount or change them runtime, because mounting in threejs is very expensive. Could you describe what you want to do?

Thank you for your reply, I do this because we have the feature to switch between 2D/3D styles. Maybe I can refresh the page, but need to keep all the use state.

all loader hooks use this library GitHub - pmndrs/suspend-react: 🚥 Async/await for React components it gives you cache control. you can clear stuff out with it. clear is also available on all the hooks.

useLoader.clear(GLTFLoader, url)
useGLTF.clear(url)

otherwise you can also use GLTFLoader/TextureLoader directly and handle assets any way you want.

i would also suggest using gltfjsx --transform to compress models and textures. or better yet use ktx2. if you’re dealing with such an amount of data that you need to clear cache that probably points to a lot of excess data.

I have questions, my understanding is that gltfjsx is for specific models, predefined. But my models is hosting on the server, I can only get their urls and names, then I use useGLTF or useLoader to load them. In my case, does gltfjsx works?

Do you mean GLTF models in <primitive>...</primitive> will not be disposed automatically? Here is my Model3D code:

import { useGLTF } from "@react-three/drei";
import { ThreeEvent } from "@react-three/fiber";
import { Suspense } from "react";

type Model3DEvent = (
  event: ThreeEvent<MouseEvent>,
  model: THREE.Object3D
) => void;

type Model3DProps = {
  url?: string;
  position?: [number, number, number];
  visible?: boolean;
  children?: React.ReactNode;
  onClick?: Model3DEvent;
  onPointerOver?: Model3DEvent;
  onPointerOut?: Model3DEvent;
};

export default function Model3D(props: Model3DProps) {
  return <Suspense>{props.url && <_Model3D {...props} />}</Suspense>;
}

function _Model3D(props: Model3DProps) {
  const { scene } = useGLTF(props.url!);
  return scene ? (
    <primitive
      object={scene}
      position={props.position}
      visible={props.visible}
      onClick={(e: ThreeEvent<MouseEvent>) => {
        props.onClick && props.onClick(e, scene);
      }}
      onPointerOver={(e: ThreeEvent<MouseEvent>) => {
        props.onPointerOver && props.onPointerOver(e, scene);
      }}
      onPointerOut={(e: ThreeEvent<MouseEvent>) => {
        props.onPointerOut && props.onPointerOut(e, scene);
      }}
    >
      {props.children}
    </primitive>
  ) : (
    <group />
  );
}

useGLTF(props.url!) fetches a url, gltfloader parses it, the data is cached, it will never be deleted (unless you call useGLTF.clear on the same url

primitive will never call dispose, because the object it mounts is not declarative, it exists outside of react, therefore it won’t destroy it.

if you want, call dispose and then useGLTF.clear on unmount.

Thank you again, I will try this way. According to the official documentation Loading Models, models are always hosted in primitive, do I have the other way to add a model with auto disposal?

every mesh or object that isn’t a primitive

<mesh /> // <---- this gets removed and disposed of on unmount
<group /> // <---- this gets removed and disposed of on unmount
<orbitControls /> // <---- this gets removed and disposed of on unmount
<foo /> // <---- this gets removed and disposed of on unmount

otherwise you have useEffect, cache clear and a special dispose that traverses the object since dispose in threejs is not really that easy to do by hand, i believe scene.dispose() will do nothing even.

import { dispose } from '@react-three/fiber'

const { scene } = useGLTF(url)
useEffect(() => {
  ...
  return () => {
    dispose(scene)
    useGLTF.clear(url)
  }
}, [url])

keep in mind that by doing so if you mount the model again it will have to parse again, it would be massively wasteful. i would only recommend that when things don’t go onto the screen ever.

ps, i would also use gltfjsx instead of a primitive. looks weird to me to use a primitive object={scene}, which already has children, and then overwrite children like that. it probably works but seems sketchy. with gltfjsx you can also omit the “dispose={null}” that it attaches on the root mesh, if you remove it it will dispose the whole scene automatically since none of it is a primitive. you still need to do the cache clear because the model is essentially destroyed on unmount.

1 Like

@drcmda do would you know if useGLTF automatically uses useMemo internally in order to memoize geometries and materials preventing regeneration when rerendering or would that be handled manually?

it uses suspense, there’s no useMemo.

function Foo() {
  const data = useGLTF("baz.glb")
  ...

function Bar() {
  const data = useGLTF("baz.glb")
  ...

both access the same exact data, the glb is loaded only once.

Yes I understood that from the thread above and fwiu suspense is to wait for the model to load, I guess my question is slightly different from the OP’s, does useGLTF behave in the same way that useMemo for the geometries and materials part of the file in the same way as…

const geo = useMemo(() => {
  return new THREE.BufferGeometry()
} 

so that the geometry and materials associated aren’t recreated ever render eg when the model is hovered for instance?

useMemo memoizes at the component level, two components memoizing a thing will execute twice. suspense is global memoization. the geometries and materials that useGLTF returns are the same where ever it’s called, given that it’s being called with the same key (the url). they aren’t re-created, they are always re-used.

1 Like