Search Unity

Creating and deleting custom clips on a custom track

Discussion in 'Timeline' started by SimonsCat, Jun 7, 2021.

  1. SimonsCat

    SimonsCat

    Joined:
    Mar 11, 2015
    Posts:
    49
    Hi Unity Timeline masters,

    I noticed a strange behavior when I delete and create track clips when the editor application is playing ( UnityEditor.EditorApplication.isPlaying == true ).

    Before we enable the Director we recreate clips on our custom "Subtitle" track:
    ( ( SubtitleTrack )track ).GenerateTrackClips( director, false );

    GenerateTrackClips method does this - deletes current clips on the track:
    Code (CSharp):
    1.  foreach( var clip in this.GetClips() )
    2. {
    3.     DeleteClip( clip );
    4. }
    and then creates new clips with appropriate content:
    Code (CSharp):
    1.  for( int i = 0; i < subtitleScriptableObject.GetCurrentLanguage().ItemList.Count; i++ )
    2.       {
    3.          TimelineClip timelineClip = CreateClip<SubtitleTrackClip>();
    4.          SubtitleTrackClip subtitleTrackClip = ( SubtitleTrackClip )timelineClip.asset;
    5.        
    6.          subtitleTrackClip.subtitleScriptableObject = subtitleScriptableObject;
    7.          subtitleTrackClip.clipIndex = i;
    8.  
    9.          SubtitleLanguageItem item = subtitleScriptableObject.GetCurrentLanguage().ItemList[i];
    10.          timelineClip.start = item.startTime;
    11.          timelineClip.duration = item.endTime - item.startTime;
    12.          timelineClip.displayName = item.subtitleLocText;
    13.       }
    then we call director.RebuildGraph(); and then director.Play.

    And here we go... I noticed that every time I run the script, new SubtitleTrackClips are created and referenced by the SubtitleTrack... This is how it was intended... however old clips information (those clips that were DeleteClip( clip );) still remain in the .playable asset. So, every time the timeline is played with the procedure above more new clip info are generated and every time we exit the play mode, the timeline asset is dirty and when the project is saved all data (new+OLD) gets serialized into the timeline.playable asset.
    The asset gets bigger and bigger and I can't figure out a way to drop the information about deleted clips from the playable asset.

    THE PROBLEM: if I delete a clip from the timeline track using DeleteClip( clip ); called from the TrackAsset the data about the clips remains inside the playable asset.

    I will be grateful for any help/hint/idea ;)

    Cheers
     
    andreyefimov2010 likes this.
  2. DavidGeoffroy

    DavidGeoffroy

    Unity Technologies

    Joined:
    Sep 9, 2014
    Posts:
    542
    I have read through the code in Timeline, and I don't see any obvious reasons why they would not be deleted.

    The code that does the deletion is in TimelineAsset_CreateRemove.cs.

    If you copy the Timeline package folder directly in the Packages folder of your project, it's pretty easy to set a breakpoint in DeleteClip to see if it goes through the whole function.

    If you can't figure it out, try to make a reasonably-sized reproduction project, file a bug report and paste the bug number here. I'll take a look.
     
  3. SimonsCat

    SimonsCat

    Joined:
    Mar 11, 2015
    Posts:
    49
    Actually the Clip is deleted from the track, the m_Clips list shows that. And also the Editor removes the deleted clips after director.RebuildGraph(); and TimelineEditor.Refresh( RefreshReason.ContentsAddedOrRemoved );

    m_Clips size is 0 after the DeleteClip( clip );. Then I add new clips and the m_Clips list contains only the new clips... but somehow, the serialized file keeps piling up the deleted clips data.

    I went quickly trough the timeline package code and I see the removal point and no other indications of something wrong. I will make an empty project and start from there...
     
  4. DavidGeoffroy

    DavidGeoffroy

    Unity Technologies

    Joined:
    Sep 9, 2014
    Posts:
    542
    TimelineClip is not a ScriptableObject, so it should not be possible for it to stay stored in the asset (as a sub asset) if nothing else refers to it.

    Can you figure out from the yaml what refers to your clips?
     
  5. SimonsCat

    SimonsCat

    Joined:
    Mar 11, 2015
    Posts:
    49
    Hi David,

    I managed to create a super small reproduction project (5 custom track and clip files with minimum code + a test scene with 1 timeline and a start script).

    The report has been submitted: (Case 1341720) Garbage data piling up in the Timeline.playable asset

    1. open SampleScene
    2. select the Timeline GameObject and check the timeline editor. You can see one track and 2 clips on it.
    3. check the content of the TestTimeline.playable asset in a text editor. You can see that there are 2 clips assigned to the timeline track.
    4. press play in the editor and let the timeline finish. You will see two texts. The PlayTimeline scripts recreates the track clips and starts the timeline. when you see only the black screen the Timeline has finished playing.
    5. stop the player
    6. Save PROJECT
    7. check again the TestTimeline.playable asset in a text editor. You will now see 4 clips, but only 2 of them assigned to the timeline track. If you repeat the above procedure multiple times you will see the clips piling up in the asset file. The Timeline iside the scene looks ok and works ok. At the moment I found no method to remove the garbage clips left in the asset and every time the timeline gets played and the project is saved there is more and more garbage. 2,4,6,8,10...

    Using Unity 2019.4.26. Timeline 1.5.5

    p.s.: No, nothing in the YAML references the garbage clips. Sample file attached.
     

    Attached Files:

  6. DavidGeoffroy

    DavidGeoffroy

    Unity Technologies

    Joined:
    Sep 9, 2014
    Posts:
    542
    First:
    Thank you very much for taking the time to make a tiny repro project, it's much appreciated.

    Second
    Looking at the yaml, it's pretty clear that the clips are deleted, but I forgot that the PlayableAssets themselves were ScriptableObjects, which need to be removed as SubAssets of the Timeline.

    Looks like they are not getting removed from the Timeline when the clips are deleted.
    We'll look into that using your bug report.


    Workaround
    In the meantime, what you can do is remove them from the TimelineAsset yourself before deleting the clips.
    Here is the code we use to remove assets from the Timeline:
    Code (CSharp):
    1. public static void RemoveAssetFromObject(Object childAsset, Object masterAsset)
    2.         {
    3.             if (childAsset == null || masterAsset == null)
    4.                 return;
    5.  
    6. #if UNITY_EDITOR
    7.             if (AssetDatabase.Contains(childAsset) && AssetDatabase.Contains(masterAsset))
    8.                 AssetDatabase.RemoveObjectFromAsset(childAsset);
    9. #endif
    10.         }
    You can copy it somewhere in your project and invoke it before DeleteClip like this:
    Code (CSharp):
    1. RemoveAssetFromObject(clip.asset, this.timelineAsset);
    Where
    this
    is a TrackAsset.
    This should make sure that the playable instances are cleared up.
     
  7. SimonsCat

    SimonsCat

    Joined:
    Mar 11, 2015
    Posts:
    49
    Thank you for the workaround.

    I tested and found only one way to make it work. Only if I add line 29 below:


    Code (CSharp):
    1.  private void RecreateClips()
    2. {
    3.       //remove old clips
    4.       foreach( var clip in this.GetClips() )
    5.       {
    6.          #if UNITY_EDITOR
    7.          RemoveAssetFromObject( clip.asset, this.timelineAsset );
    8.          #endif
    9.          DeleteClip( clip );
    10.       }
    11.  
    12.  
    13.  
    14.       // create/recreate clips
    15.       for( int i = 0; i < subtitleScriptableObject.GetCurrentLanguage().ItemList.Count; i++ )
    16.       {
    17.          TimelineClip timelineClip = CreateClip<SubtitleTrackClip>();
    18.          SubtitleTrackClip subtitleTrackClip = ( SubtitleTrackClip )timelineClip.asset;
    19.        
    20.          subtitleTrackClip.subtitleScriptableObject = subtitleScriptableObject;
    21.          subtitleTrackClip.clipIndex = i;
    22.  
    23.          SubtitleLanguageItem item = subtitleScriptableObject.GetCurrentLanguage().ItemList[i];
    24.          timelineClip.start = item.startTime;
    25.          timelineClip.duration = item.endTime - item.startTime;
    26.          timelineClip.displayName = item.subtitleLocText;
    27.       }
    28.    
    29.       #if UNITY_EDITOR
    30.       AssetDatabase.SaveAssets();
    31.       #endif
    32.    }
    33.  
    34.    private void RemoveAssetFromObject( UnityEngine.Object childAsset, UnityEngine.Object masterAsset )
    35.    {
    36.       if( childAsset == null || masterAsset == null )
    37.          return;
    38.  
    39.       if( AssetDatabase.Contains( childAsset ) && AssetDatabase.Contains( masterAsset ) )
    40.       {
    41.          AssetDatabase.RemoveObjectFromAsset( childAsset );
    42.       }
    43.    }

    If I don't call AssetDatabase.SaveAssets(); before stopping the player I still get garbage left if I later save the project.
     
  8. DavidGeoffroy

    DavidGeoffroy

    Unity Technologies

    Joined:
    Sep 9, 2014
    Posts:
    542
    The domain reload that happens when you go from Edit mode to Play mode kills everything that's not serialized, and there is also a lot of reverting happening, so making "edits" in Play mode is definitely a dicey proposition.

    Do you need the Timeline Window to reflect the new clips you have generated?
    It would be a lot closer to the design idea of Playables/Timeline to generate dynamic Playables based on locale, and not necessarily changing the Timeline asset itself (and would maybe avoid some of these issues).
     
  9. SimonsCat

    SimonsCat

    Joined:
    Mar 11, 2015
    Posts:
    49
    Actually, discard all changes in Play mode is what we wanted in the first place but somehow we could not make it work and did not find any clues about that in the docs.

    The Subtitle track that we created shows subtitles on UI based on the language selected. A Subtitle clip is a trigger when to show a specific subtitle and when to hide it (similar to the activation track but with more data). It also contains the text to display, but that it's only because it was convenient to place it there on load. In fact the texts are stored elsewhere.

    The case is that different languages can have different numbers of subtitle cards (clips) with different timings too. That is why we recreate track clips just before playing the timeline. I would be more than happy to just revert everything but it seems too complicated to do that and the changes we do on the track set the timeline asset dirty and it get saved after exiting the play mode if we save the project.

    The Subtitle track is designed in a way to display a preview of the subtitles while playing the timeline in Editor when setting voices, animations and other tracks, so we don't need to enter in Play mode to set up the subtitle texts and timings.

    We don't need the Timeline Window to reflect the new clips in Paly mode and we don't need to actually store the Play mode changes. Do you have any recommendation on how to achieve this? You mentioned dynamic Playables?