Skip to content

ScreenStack likely removes screens one frame too early #6518

@smoogipoo

Description

@smoogipoo

ScreenStack implements its own children life updates:

protected override bool UpdateChildrenLife()
{
if (!base.UpdateChildrenLife()) return false;
// In order to provide custom suspend/resume logic, screens always have RemoveWhenNotAlive set to false.
// We need to manually handle removal here (in the opposite order to how the screens were pushed to ensure bindable sanity).
if (exited.FirstOrDefault()?.IsAlive == false)
{
foreach (var s in exited)
{
RemoveInternal(s, false);
DisposeChildAsync(s);
}
exited.Clear();
}
return true;
}

When screens are exited they are .Expire()d, calculating their expiry time as LatestTransformEndTime:

if (source == null)
{
// This is the first screen that exited
toExit.AsDrawable().Expire();
}

Which means that the intended usage of screens is something like:

void OnExit()
{
    this.FadeOut(1000);
    // Expecting the screen to die after 1000ms.
}

The problem is that UpdateChildrenLife occurs prior to children being updated, which may cause unintended effects if the transforms aren't updated for the final time. It's likely fine for something like a FadeOut() because the final frame will already be at a low alpha value, but consider the following situation:

void OnExit()
{
    this.TransformBindableTo(audioVolumeBindable, 0, 1000);
}

In this case, the effect is not fine as it leaves the sample with >0 volume in the final frame.

This expiry process probably needs to be re-considered a bit, and this may even be an issue outside of screens too because the base UpdateChildrenLife() implementation works much the same way.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions