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

useEffectListener doesn't fire when window is not in focus #57

Closed
artt opened this issue Oct 11, 2020 · 4 comments
Closed

useEffectListener doesn't fire when window is not in focus #57

artt opened this issue Oct 11, 2020 · 4 comments
Labels
bug Something isn't working

Comments

@artt
Copy link

artt commented Oct 11, 2020

Issue description

It seems like the effects doesn't fire until the window/tab is in focus. Minimal demonstration of the problem:

  • set updateStateAfterEffects to true
  • Have one button which calls ctx.effects.foo().
  • In the listener, print out current time.
  • Open two clients in the same browser window but two tabs.
  • Click the button. In the first tab you should see the current time.
  • Wait a bit, and switch to the second tab. You'll see the "current" time as well.

Use case

This is quite a weird problem but basically I have three effects to be fired in a row when a dice is rolled:

  • ctx.effects.roll() with duration of 3, animates the dice (using setInterval to random dice face)
  • ctx.effects.rollDone() with no duration, clears that interval show the final outcome.

When I develop my game, I have two tabs open, one for each player. Tab A clicks roll. I then switch to Tab B. Wait until everything is done. Perform some action that alters G. Everything looks fine in Tab B. But then when I switch back to Tab A, the dice is still rolling since rollDone() was never fired.

At first I thought this has something to do with setInterval on my code, but removing it doesn't solve the problem.

Not sure if you could recommend some workaround for this. Thanks a lot!

@delucis
Copy link
Owner

delucis commented Oct 12, 2020

Hi @artt — thanks for the report.

This is partially expected because the effects are fired from requestAnimationFrame, which tends to be paused for non-active windows:

requestAnimationFrame() calls are paused in most browsers when running in background tabs or hidden <iframe>s in order to improve performance and battery life. (Source: MDN)

If I understand the bug correctly this is what’s happening:

tab 1 tab 2
1 Make move that triggers roll effect.
2 Switch to tab 2.
3 Wait for rollDone.
4 Make some other action.
5 Switch to tab 1.
Has state for latest action, rollDone was never fired.

Once you switch to tab 2 (before rollDone is fired), requestAnimationFrame in tab 1 pauses. Once you switch back to tab 1, requestAnimationFrame can resume, but now the state has updated due to the new action made in tab 2, so it processes the new effects queue, not the old one containing rollDone.

I think slow/paused effects in an inactive tab is acceptable, but we should still make sure effects complete as expected rather than getting lost like they currently seem to be.

@delucis delucis added the bug Something isn't working label Oct 12, 2020
@artt
Copy link
Author

artt commented Oct 12, 2020

I think slow/paused effects in an inactive tab is acceptable, but we should still make sure effects complete as expected rather than getting lost like they currently seem to be.

I agree with this.

I have no idea how all of this works... they're all magic to me :P
But maybe this will help?

@delucis
Copy link
Owner

delucis commented Jan 5, 2021

After doing some more research, there are two parts:

  1. Use the Page Visibility API, for example with a hook like that provided by react-page-visibility. When the tab is hidden, switch to setInterval for dispatching effects. This can probably run once every second successfully (at least on desktop — hidden mobile tabs might be more restricted). Precise timing is less essential for a hidden tab, so this seems like a nice way to keep things flowing, while not overloading a user’s CPU pointlessly. (Alternatively, all effects could simply be fired immediately for hidden tabs, doing away with timing completely.)

  2. Make sure effects get dispatched when a new queue arrives. Currently, if the state updates while the queue is still being processed, the in-progress queue just gets discarded and the new queue starts. I think this seems right in general, but in the specific case above where a new queue arrives after the previous should have finished, perhaps we should consider flushing those old effects. (That said, for your specific roll/rollDone logic, check out the new on-end callbacks in useEffectListener, which might fit your use case and work better.)

delucis added a commit that referenced this issue Jan 22, 2022
Partially addresses #57. `requestAnimationFrame` is usually paused when a browser tab is inactive or in the background. We now schedule a `setTimeout` each time we request a new frame, which will only get run if the rAF callback is not called within 1000ms. `setTimeout` is also usually throttled, but will more likely run in the background than `requestAnimationFrame`.
@delucis
Copy link
Owner

delucis commented Jan 22, 2022

This should be fully resolved in v0.7.0 if you‘d like to try it out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants