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

requestAnimationFrame called on "self" might not work on multi-window environments #24549

Closed
SkyBladeCloud opened this issue Aug 28, 2022 · 4 comments

Comments

@SkyBladeCloud
Copy link

SkyBladeCloud commented Aug 28, 2022

Is your feature request related to a problem? Please describe.

On a NW.js application which runs in separated context mode, the global window object corresponds to a background, invisible window where requestAnimationFrame never fires. Additional windows spawned using the NW.js API have their own window variable (different from global.window). This causes problems with this line:

if ( typeof self !== 'undefined' ) animation.setContext( self );

Since "self" will be pointing to the global window (background, invisible), the animation callback will never be triggered.

Describe the solution you'd like

A mechanism for the WebGLRenderer to set a custom window object, rather than having the global self hard-coded. For instance:

  • If a canvas is specified in the constructor of WebGLRenderer, use ownerDocument.defaultView, or ownerDocument.parentWindow to know where to call requestAnimationFrame.
  • Perhaps have the WebGLRenderer take an additional parameter to use as window, which would default to "self", even though that would be somewhat redundant considering the previous option.

Describe alternatives you've considered

1 -> Overwrite the window.requestAnimationFrame function, so that it's executed by another window: this is very hackish and only works with one window, so if my application spawns two, each with a three.js canvas, it would break.

2 -> Use NW.js in mixed context mode: this way each spawned window has its own global window object. However, this works by running each window on a different process, which makes it hard to pass information back and forth.

4 -> Thanks to #23686 I can do something like:

window.self = nw.Window.get().window; //Get NW.js child window object into the self reference three.js will use
renderer = new THREE.WebGLRenderer({ antialias: true });
window.self = window; //Set the original window back to the global self reference

This seems to work fine as well, but it's also extremely hackish.

4 -> Directly use requestAnimationFrame on the child window object, as it used to be done.
As far as I understand, this is currently discouraged in favor of setAnimationLoop for WebXR compatibility, but so far it's the best I've come up with. I guess it's an acceptable solution if WebXR support is never to be considered, and if everything else works as expected (please confirm?).

Additional context

Nw.js is a development platform by Intel which allows application programmers to combine a nodejs back-end with a HTML/CSS/JS front-end into a standalone app (similar to Electron):

https://nwjs.io/
https://docs.nwjs.io/en/latest/For%20Users/Advanced/JavaScript%20Contexts%20in%20NW.js/

In Nw.js, visible HTML interfaces don't use the default, global "window". Rather, the global window corresponds to an invisible, background object. This is an useful design choice for headless environments.

Thanks and kind regards.

~Sky

@LeviPesin
Copy link
Contributor

4 -> Thanks to https://github.com/mrdoob/three.js/pull/23686 I can do something like:

window.self = nw.Window.get().window; //Get NW.js child window object into the self reference three.js will use
renderer = new THREE.WebGLRenderer({ antialias: true });
window.self = window; //Set the original window back to the global self reference

This seems to work fine as well, but it's also extremely hackish.

I think this is the best solution.

@Mugen87
Copy link
Collaborator

Mugen87 commented Aug 29, 2022

This is a problem of NW.js and it's inappropriate to address this issue in the engine. To me, it's annoying if JS environments implement web standards differently so you as a developer can't rely things to just work. Probably better to file an issue at NW.js.

@Mugen87 Mugen87 closed this as completed Aug 29, 2022
@SkyBladeCloud
Copy link
Author

SkyBladeCloud commented Aug 29, 2022

@Mugen87 The reality is that the use case that I present in the original message behaves exactly the same way on a normal browser + vanilla js. Consider the following example directly adapted from the three.js docs:

const child = window.open('', 'newwindow', 'width=300,height=250');

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, 300 / 250, 0.1, 1000);

const renderer = new THREE.WebGLRenderer();
renderer.setSize( 300, 250 );
child.document.body.appendChild( renderer.domElement );

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

camera.position.z = 5;

function animate() {
    requestAnimationFrame( animate );

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render( scene, camera );
};

animate();

Here, the only difference is that the renderer's canvas is being mounted on the child window's document. Sure enough, when the parent window is minimized, or occluded by another (or otherwise not visible), the child window stops the animation loop, even though this one is still visible. This is exactly what happens when using setAnimationLoop in my use case. Here it's simply fixed by...

child.requestAnimationFrame( animate );

...which can't be done when using the now recommended setAnimationLoop. However, every scenario would work if the WebGLRenderer set the WebGLAnimation context as "_canvas.ownerDocument.defaultView". I believe it only makes sense to have the animation callback fired in the window that contains the canvas (of course, if the window exists and the canvas is provided).

Most application won't ever encounter this issue, since it's very common to only use isolated window contexts (for example, in this case having children windows with their own script tags that manage their own renderers, scenes, etc...). However, as previously mentioned, this makes it very hard to share data back and forth (for context, in my particular case I'm managing streaming sensor data that I wanted to visualize in a separated window).
This is not an issue of NW.js: it is behaving as it should. The fact that the main window is never visualized doesn't change the fact that a hidden standard browser window has essentially the same behavior.

If you guys consider this particular use case too niche and believe my workarounds are enough (as per LeviPesin's recommendation, which I appreciate), that's OK, I merely wanted to provide feedback and suggest a possible improvement based on my experience with the library. I don't see how that's inappropriate: don't assume I expect things to just work, especially when I've provided plenty enough evidence that I understand what the issue is, possible workarounds and a potential solution.

Thanks and kind regards.

~Sky

@mrdoob
Copy link
Owner

mrdoob commented Aug 29, 2022

4 -> Thanks to #23686 I can do something like:

window.self = nw.Window.get().window; //Get NW.js child window object into the self reference three.js will use
renderer = new THREE.WebGLRenderer({ antialias: true });
window.self = window; //Set the original window back to the global self reference

This seems to work fine as well, but it's also extremely hackish.

Seems fine to me 👍

Repository owner deleted a comment from upintheairsheep Nov 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants