Memory leak, that I can not deal with

Hello everyone.
I am using React (“react”: “^18.2.0”) and Three.js (“three”: “^0.151.3”)
The problem I am facing is that, while you go from page to page, three.js animations get slower. I was thinking that is a memory leak, but now I am not so sure what is the problem. I think that everything I did is like you guys described, but something is bothering me. I will give you one sample component I am using, every else, is pretty much the same. I just need clarification that is not up to three.js, but something else, some other JavaScript that is making a problem. I am disposing everything I can, and also the whole scene and renderer itself when leaving the page, but next page get slower…
Note. I am getting warnings: “WARNING: Too many active WebGL contexts. Oldest context will be lost…” so that is the reason why I am thinking that maybe this part of the code is making me that problem. I also made a global renderer, and use it for the whole website, but that changed nothing.
Thank you all in advance!

export default memo(
  ({ antialias, engineOptions, adaptToDeviceRatio, sceneOptions, onRender, onSceneReady, handleMaskFade, ...rest }) => {
    const reactCanvas = useRef(null);

    let renderer;
    let scene;
    let camera;
    let mousePosition = new THREE.Vector3();
    let cameraCtrl;
    let mouse = new THREE.Vector2();
    let raycaster = new THREE.Raycaster();
    let mixerarr = [];
    let geometries = [];
    let clock = new THREE.Clock();
    let geometryDodecahedronGeometry;
    let composer, iMesh;
    let pointLight;
    let cscale = chroma.scale(["#dd3e1b", "#0b509c"]);
    let mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
    let textureAtlas1;
    let geometryGlow1;
    let materialGlow1;
    let mouseOver;
    let NUM_INSTANCES = 1000;
    let instances = [];
    let { randFloat: rnd, randFloatSpread: rndFS } = THREE.MathUtils;
    let unmounted = true;
    let materialShader;
    let meshphongFragBody, meshphongFragHead;

    useEffect(() => {
      const { current: canvas } = reactCanvas;

      sceneOptions = {
        useGeometryUniqueIdsMapSearch: true,
        useMaterialMeshMapSearch: true,
        useClonedMeshMap: true,
      };
      if (!canvas) return;
      unmounted = false;
      // create a new WebGL renderer
      renderer = new THREE.WebGLRenderer({ canvas, antialias, ...engineOptions });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setClearColor(0x00ff00, 0);
      renderer.alpha = true;
      renderer.antialias = true;
      scene = new THREE.Scene();
      // scene.background = null;

      // create a new camera
      camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(0, 0, 5);
      creatingAssets();
      cameraConfig();
      threeManager = {
        activeScene: scene,
        transitionScene: null,
      };
      function animate() {
      }

      animate();

      const resize = () => {
        const width = window.innerWidth;
        const height = window.innerHeight;
        camera.aspect = width / height;
        camera.updateProjectionMatrix();

        if (isPhone()) {
          renderer.setSize(window.outerWidth, window.outerHeight);
        } else {
          renderer.setSize(width, height);
        }
      };

      if (window) {
        window.addEventListener("resize", resize);
      }

      return () => {
        unmounted = true;

        if(renderer.attributes) renderer.attributes.remove(iMesh.instanceMatrix)
        if (geometryDodecahedronGeometry) geometryDodecahedronGeometry.dispose();
        materialShader.dispose();
        instances = [];
        scene.remove(iMesh);
        scene.remove(pointLight);
        scene.remove(womanMaskMesh);
        iMesh = null;

        if (geometryGlow1) geometryGlow1.dispose();
        if (textureAtlas1) textureAtlas1.dispose();
        if (materialGlow1) materialGlow1.dispose();
        if (materialShader) materialShader.dispose();

        if (composer) composer.dispose();
        if (pointLight) pointLight.dispose();
        if (scene.current && camera.current) {
          scene.current.dispose();
          camera.current.dispose();
        }
        if (renderer.current) {
          renderer.current.dispose();
        }

        if (window) {
          window.removeEventListener("resize", resize);
        }

        document.body.removeEventListener("click", randomColors);
        document.body.removeEventListener("mousemove", onMouseMove, false);

        renderer = null;
        scene = null;
        camera = null;
        pointLight = null;
        womanMaskMesh = null;
        materialGlow1 = null;
        mousePlane = null;
        
      reactCanvas.current = null;

      };
    }, []);

    return (
      <div>
        <canvas ref={reactCanvas} {...rest}></canvas>
      </div>
    );

    function randomColors() {
      const c1 = chroma.random(),
        c2 = chroma.random();
      cscale = chroma.scale([c1, c2]);
      updateColors();
    }

    function updateColors() {
      const colors = [];
      for (let i = 0; i < NUM_INSTANCES; i++) {
        const color = new THREE.Color(cscale(rnd(0, 1)).hex());
        colors.push(color.r, color.g, color.b);
      }
      iMesh.geometry.setAttribute("color", new THREE.InstancedBufferAttribute(new Float32Array(colors), 3));
    }

is there a reason you’re not using r3f and drei for this? i’m pretty sure the majority of the issues you’re experiencing are handled by these mutations of three already, no need to try to handle these by yourself…

It’s a big antipattern to create expensive resources in the body of a React component. At the minimum you’ll need to try useRef to store those things and reuse them. Using R3F would probably be a better choice, since they have tons of examples to guide you on the right paths.

1 Like

Hello @Lawrence3DPK , first thing first, thank you very much for the reply! The truth is that I am new to three.js, and I started working on the project with already architecture set up. I did not work with r3f yet, so I do not know do I have time to switch over… Project is complete, except for this part…

Hello @donmccurdy, thank you. So there is pretty much nothing that I can do here…

I also had a major memory leak in my ThreeJS application, and I got to know while unit testing my code by loading a 3d object file and clearing it from the scene on button click. I used the .dispose() function in renderer. Maybe it helps you.

1 Like

I found out what was the problem. My components are not so expensive, so the problem was mainly in combining react and three.js with little knowledge of how Three.js works. If anyone in the future has some problems with this, please send me a message, don’t give up on your project! There were a lot of mistakes from my side, besides from little knowledge on Three.js, and its libraries… also calling requestAnimationFrame too frequently, and also using a deprecated Tween… And so on, and so on.

Hey @laki , Could you share you findings please? I’m seeing memory leak in a react project.

  • I’m calling dispose() on three objects, which should free up GPU resources
  • Even after the react component is unmounted, I still see the ArrayBuffer not detached in the Memory Profile. When the component unmounts, three.js objects are not supposed to have any references to it and should get picked up by GC right?

Hello @laki I am stuck with same problem. Actually I a am trying to load a streaming point cloud data
from websocket. I am receiving the point cloud as an array but after receiving more the 3-4 messages from websocket the the webGL context is lost.

Are you calling .dispose() on the buffer geometries that you don’t need anymore?

No.I am not calling .dispose()