AR-js-org / AR.js

Image tracking, Location Based AR, Marker tracking. All on the Web.
MIT License
5.43k stars 925 forks source link

Memory leak when removing/adding again the "arjs-webcam-texture" component #171

Closed NVFedorov closed 4 years ago

NVFedorov commented 4 years ago

Do you want to request a feature or report a bug? Bug

What is the current behavior? arjs-webcam-texture component doesn't have remove function to be called on component removal. Video stream is not getting stopped.

If the current behavior is a bug, please provide the steps to reproduce. Add arjs-webcam-texture component to scene and remove it. Video stream is still there.

Please mention other relevant information such as the browser version, Operating System and Device Name Windows 10, chrome Version 85.0.4183.83, PC. What is the expected behavior? Video stream gets stopped and memory gets cleaned. If this is a feature request, what is motivation or use case for changing the behavior?

Background: I'm trying to switch between ar.js and aframe and back and so forth. I noticed that I heap size is increased on every switch.

I tried to clean resources when arjs-webcam-texture is removed the following way:

remove: function(){       
        const sceneTraverse = (obj, fn) => {

            if (!obj) return

            fn(obj)

            if (obj.children && obj.children.length > 0) {
                obj.children.forEach(o => {
                    sceneTraverse(o, fn)
                })
            }
        }

        const dispose = () => {         
            sceneTraverse(this.texScene, o => {

                if (o.geometry) {
                    o.geometry.dispose()
                }

                if (o.material) {
                    if (o.material.length) {
                        for (let i = 0; i < o.material.length; ++i) {
                            o.material[i].dispose()
                        }
                    }
                    else {
                        o.material.dispose()
                    }
                }
            })          

            const video = document.querySelector("video");
            const src = video['srcObject'];
            if (src){
                src.getVideoTracks().forEach(t => t.stop());
            }
            video.parentNode.removeChild(video);

            this.texScene = null
            this.texCamera = null
            this.scene.renderer.renderLists.dispose();
            this.scene.renderer.clearDepth();
    }

        dispose();
}

Also I'm removing event listener registered in gps-camera:

window.removeEventListener('gps-entity-place-added', this._onGpsEntityPlaceAdded, false);

Here is my scene:

<a-scene vr-mode-ui='enabled: false'
             embedded
             light="defaultLightsEnabled: false"
    </a-scene>

And here is how I create it's content (this code gets invoked on a button click):

function ArJS(){
    const scene = document.querySelector('a-scene')
    scene.setAttribute('arjs-webcam-texture', '');

    // clean everything after aframe scene
    scene.removeAttribute('fog');
    const camera = document.querySelector('a-camera')
    if (camera) {
        camera.parentNode.removeChild(camera);
        camera.destroy();
    }
    const injectedCamera = document.querySelector('a-entity[camera][aframe-injected]');
    if (injectedCamera)
        injectedCamera.parentNode.removeChild(injectedCamera);

    document.querySelectorAll('a-entity').forEach(e => {
        e.parentNode.removeChild(e);
        e.destroy();
    });

    // light
    const ambient = document.createElement('a-entity');
    ambient.setAttribute('light', 'type: ambient; color: #BBB');
    const directional = document.createElement('a-entity');
    directional.setAttribute('light', 'type: directional; color: #FFF; intensity: 0.6');
    directional.setAttribute('position', '-0.5 1 1');
    scene.appendChild(ambient);
    scene.appendChild(directional);

    // camera
    let camera = document.createElement('a-camera');
    camera.setAttribute('rotation-reader', '');
    scene.appendChild(camera);
// glTF 2.0 model
    createEntity(scene, MODEL);

    scene.render();
}

function AFrame(){
    const scene = document.querySelector('a-scene')
    scene.removeAttribute('arjs-webcam-texture');
    const camera = document.querySelector('a-camera')
    camera.parentNode.removeChild(camera);
    camera.destroy();
    document.querySelectorAll('a-entity').forEach(e => {
        e.parentNode.removeChild(e);
        e.destroy();
    });

    createCamera(scene, false);
    createEntity(scene, MODEL, true)
    createLight(scene);

    scene.render();
}

function createLight(scene){    
    const ambient = document.createElement('a-entity');
    ambient.setAttribute('light', 'type: ambient; color: #BBB');
    const directional = document.createElement('a-entity');
    directional.setAttribute('light', 'type: directional; color: #FFF; intensity: 0.6');
    directional.setAttribute('position', '-0.5 1 1');
    scene.appendChild(ambient);
    scene.appendChild(directional);
}

function createCamera(scene, isGpsCamera) {
    let camera = document.createElement('a-camera');
    camera.setAttribute('rotation-reader', '');

    if (isGpsCamera) {
        let gps_cam_val = `maxDistance:${config.MAX_DISTANCE}; simulateAltitude: 1.6;`
        camera.setAttribute("gps-camera", gps_cam_val);
    }
    scene.appendChild(camera);
}

function createEntity(scene, model, noLocation) {
    let latitude = model.location.latitude;
    let longitude = model.location.longitude;

    let entity = document.createElement('a-entity');
    entity.id = model.id;
    scene.appendChild(entity);

    if (!noLocation) {
        entity.setAttribute('gps-entity-place', `latitude: ${latitude}; longitude: ${longitude};`);
    }

    entity.setAttribute('scale', model.scale);
    entity.setAttribute('rotation', model.rotation);
    entity.setAttribute('position', model.position);
    entity.setAttribute('gltf-model', model.url);

    entity.setAttribute('animation-mixer', '');
}

So every time I click a button to switch between AR.js and AFrame, the heap size is growing. Or maybe I'm doing something wrong?

P.S.: when I do same manipulations without AR.js the heap size is not growing.

nickw1 commented 4 years ago

Sorry, the arjs-webcam-texture component needs to have cleanup code added. I will look into fixing this - should be straightforward.

NVFedorov commented 4 years ago

please bear in mind there is an event to be unsubscribed from: https://github.com/AR-js-org/AR.js/blob/11d3225c5e821df46a91f458f80de839f7944c82/aframe/src/location-based/gps-camera.js#L59

Should I file a separate issue for that?

nickw1 commented 4 years ago

That's a separate thing so I would recommend raising a separate issue: @nicolocarpignoli do you agree?

nicolocarpignoli commented 4 years ago

yep,absolutely, thanks!

NVFedorov commented 4 years ago

created new issue: #172

nickw1 commented 4 years ago

@NVFedorov have, I believe, fixed #171 on dev branch. Feel free to test and see if it resolves the memory leak.

NVFedorov commented 4 years ago

@nickw1 image odd numbers - ar.js with 2 gltf models, even numbers - aframe with forest environment and 1 gltf model.

Did several tests, looks great, thank you!