Search Unity

Bug (Re)Playing a Timeline with Wrap Mode set to None triggers rebuild of the PlayableGraph (allocs!)

Discussion in 'Timeline' started by Midiphony-panda, Dec 22, 2021.

  1. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    Hello !

    Everything is in the title.

    When replaying a (ended) Timeline with Wrap Mode set to None, the PlayableDirector rebuilds the internal PlayableGraph, triggering garbage allocations.

    upload_2021-12-22_19-0-29.png

    Is this by design ?
    (I suppose the PlayableDirector just disposes the PlayableGraph when the Timeline has ended if Wrap Mode is "None")

    What are the solutions to avoid these allocations ?
     
  2. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    A hacky solution to stop evaluating the Timeline and avoid recreating the internal playable when replaying the timeline, would be to set the Wrap Mode to Hold and pause the Director when the timeline is finished.
     
  3. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    542
    I did not have time to profile this myself, but it should be fixed if true, did you try to make a bug report directly inside Unity?

    I would love to hear from Unity why the graph is recreated every time, is it a bug or on pupropse for some reason?

    Did you try to explicitly create the graph instance once and then explicitly set your created graph instance, does it have the same result?
     
  4. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    I have not made a bug report nor have I tried your proposed solution.

    This is very easy to profile. Play a Timeline once, notice the big GC.Alloc, wait for the timeline to end, and replay it : the GC.Alloc is triggered again.
     
  5. R1PFake

    R1PFake

    Joined:
    Aug 7, 2015
    Posts:
    542
    I finally had some time to look into this, because Im at the point where I add Timeline into my game.

    I can also confirm the allocations, but the PlayableDirector Play() method mentions "Instatiates a Playable using the provided PlayableAsset and starts playback." and the Stop() method "Stops playback of the current Playable and destroys the corresponding graph."

    So it looks like this is by design.

    The PlayMode "None" will call Stop() at the end and destroy the current graph.

    As you mentiond there is a "workaround" by using the "Hold" mode, but there are some important details if you want to use this method.

    Important: The following text is only important if you want to avoid these allocations, if you don't care about these allocations then you can just call Play() and Pause() and / or use the "None" mode, without these additional boilerplate checks!

    I will write down my findings, so maybe someone else can use this information (maybe my future self) or someone else can add additional information.

    As mentiond above the Play() and Stop() method should not be called, because they will create / destroy the graph.

    So we have to use the Resume() and Pause() methods instead with the "Hold" mode.

    But if you just call Resume() you will notice that nothing happens, this is because there is no valid graph yet.

    There are two solutions, either you check if the current graph was created yet, by calling the IsValid() method on the playableGraph struct and call Play() once to create the graph else call Resume() if the graph is already valid / created.

    Or you can manually create the graph once by calling RebuildGraph() at any point. (I personally use this method, but both solutions should work)

    Now the next step, if you set "None" the timeline will automatically "Reset" at the end, which will not happen with Hold mode unless you call Stop() but we don't want to call Stop() because that would destory the graph, so what's the other solution?

    First we have to detect if the Timeline is "done", because with "Hold" mode the state property will always return "Playing" even at the end, instead with have to check if the current time is at the end by comparing it to the total duration. (Please let me know if there is a better solution)

    Ok now we know that the timeline is "done" and holding at the end, now we want to "Stop" it without calling Stop():
    This can be done by calling Pause() and setting the time to 0.

    Almost done, you will notice one last thing, the Timeline will be paused but the object's don't reset. That's because after setting the time to 0, the timeline didn't update anymore, so you can either wait one frame between setting time to 0 and calling Pause() or you manually call Evaluate() after setting the time to 0.

    I like this method more, because dealing with frame wait times etc. is annoying so the full "Fake Stop" is 3 lines
    - Set time to 0
    - Call Evaluate()
    - Call Pause()

    The next time you want to "Play" the timeline you can just call Resume() again

    This method will create the graph only once and reuse it for all following Resume() and Pause() calls, I don't know if there is any downside of "keeping" the graph "alive" for a long time, but so far I did not find any issues, maybe someone from Unity can confirm if there could be issues?.
     
    Last edited: Aug 21, 2022
    akent99 likes this.