coronalabs / corona

Solar2D Game Engine main repository (ex Corona SDK)
https://solar2d.com/
MIT License
2.51k stars 269 forks source link

Timing bug in scene transitions #706

Open ggcrunchy opened 3 months ago

ggcrunchy commented 3 months ago

I finally tracked down a bug I've been running into for quite some time.

I first leave a scene with a "swipeLeft", going to a little dummy in-between scene. I then do a "no effect" transition back to the previous scene, albeit with different data. Sometimes on my return I'll only see the new graphics pop in on the left side.[1]

What I believe is happening is that the assumption described in this comment is not completely reliable.

The scene transitions are using transition.to() under the hood, and gotoScene() (and scene events) are using timers. What seems to be happening is that the scene group's position is restored to 0, 0, then the "slideLeft" transition finishes and slams it back to the left.

(I noticed, using Runtime.getFrameID(), that the "will" and "did" phases of the "show" event occurred in the same frame, but I don't know if that speaks to the problem other than how soon the follow-up "no effect" / "crossFade" transition happens.)

In my case, the timing window is pretty narrow, but I could see this showing up with random performance hiccups during scene transitions. (Maybe there are some incidents if I dig around in the forum.)

Rather than the fragile timing, this probably wants some kind of "don't start transition X unless / until transition Y has finished (and die if it's cancelled?)" option.


[1] - In particular, up to 0, with anchorX equal to 1, i.e. overlapping the left-hand letterbox rect. This is an issue in its own right (z-order not preserved), but without these few bits that did appear I'd have been totally at sea.😄 (EDIT: I might be misdiagnosing this part, so disregard. 🙂)

ggcrunchy commented 3 months ago

(Also probably a transition.cancel() on the scene view for good measure.)

ggcrunchy commented 3 months ago

I pored over the timer and event dispatcher logic for quite a long time, trying to figure out where the flaw was. On the bright side, they seem generally robust. 😄

Where the (same frame) situation can actually occur seems to be in _nextTransition: https://github.com/coronalabs/framework-composer/blob/b6b4bf4dcaeb895cb1e294c0cc9ea1ea780b2a54/composer.lua#L901 if enough time lapses on the current frame to complete the transition as well.

(The "same frame" issue always happens when I see the problem, but isn't the problem itself. But this does tie things back to clashing transitions.)

I realized I was working around this, without quite knowing the problem. Once I cleared that away I was able to reproduce the problem every time I ran. (So I know the fix below worked.)

I did try that transition.cancel() idea and was wondering why it didn't help at all. It appeared to be this line at fault.

I'm now able to prevent the problem by doing this immediately before composer.gotoScene:

local s = composer.getScene("game.scene.level")

if s and s.view then
local vv
for i, v in ipairs(transition._transitionTable) do
  if v._target == s.view then
  vv=v
  break
  end
end
for i, v in ipairs(transition._enterFrameTweens) do
  if v._target == s.view then
  vv=v
  break
  end
end
  if vv then
    vv._generatedByComposer = false
  end

  transition.cancel(s.view)

  if vv then
    vv._generatedByComposer = true
  end
end

I imagine something cleaner and more automatic can be added to composer and / or transition. Will try to give it a look later.