Code/Compiled/Clip.cs
using System;
using System.Collections.Immutable;
namespace Sandbox.MovieMaker.Compiled;
#nullable enable
/// <summary>
/// An immutable compiled <see cref="IClip"/> designed to be serialized.
/// </summary>
public sealed partial class MovieClip : IClip
{
/// <summary>
/// A clip with no tracks.
/// </summary>
public static MovieClip Empty { get; } = FromTracks();
private readonly ImmutableDictionary<Guid, ICompiledReferenceTrack> _referenceTracks;
/// <inheritdoc cref="IClip.Tracks"/>
public ImmutableArray<ICompiledTrack> Tracks { get; }
public MovieTime Duration { get; }
private MovieClip( IReadOnlySet<ICompiledTrack> tracks )
{
// ReSharper disable once UseCollectionExpression
Tracks = tracks
.OrderBy( x => x.GetDepth() )
.ThenBy( x => x.Name )
.ToImmutableArray();
_referenceTracks = tracks
.OfType<ICompiledReferenceTrack>()
.ToImmutableDictionary( x => x.Id, x => x );
Duration = tracks
.OfType<ICompiledBlockTrack>()
.Select( x => x.TimeRange.End )
.DefaultIfEmpty()
.Max();
}
/// <inheritdoc cref="IClip.GetTrack"/>
public ICompiledReferenceTrack? GetTrack( Guid trackId )
{
return _referenceTracks.GetValueOrDefault( trackId );
}
IEnumerable<ITrack> IClip.Tracks => Tracks.CastArray<ITrack>();
IReferenceTrack? IClip.GetTrack( Guid trackId ) => GetTrack( trackId );
public static MovieClip FromTracks( params ICompiledTrack[] tracks ) =>
FromTracks( tracks.AsEnumerable() );
public static MovieClip FromTracks( IEnumerable<ICompiledTrack> tracks )
{
var allTracks = new HashSet<ICompiledTrack>();
// Find all root tracks
foreach ( var track in tracks )
{
var parent = track;
while ( parent is not null && allTracks.Add( parent ) )
{
parent = parent.Parent;
// No cycles!
if ( parent == track )
{
throw new ArgumentException( "Track hierarchy must not have cycles.", nameof( Tracks ) );
}
}
}
var referenceTracks = new Dictionary<Guid, ICompiledReferenceTrack>();
// IDs must be unique
foreach ( var track in allTracks.OfType<ICompiledReferenceTrack>() )
{
if ( !referenceTracks.TryAdd( track.Id, track ) )
{
throw new ArgumentException( "Tracks must have unique IDs.", nameof( Tracks ) );
}
}
return new MovieClip( allTracks );
}
/// <summary>
/// Create a root <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.GameObject"/> with
/// the given <paramref name="name"/>. To create a nested track, use <see cref="CompiledClipExtensions.GameObject"/>.
/// </summary>
public static CompiledReferenceTrack<GameObject> RootGameObject( string name, Guid? id = null ) => new( id ?? Guid.NewGuid(), name );
/// <summary>
/// Create a root <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.Component"/> with
/// the given <paramref name="type"/>. To create a nested track, use <see cref="CompiledClipExtensions.Component"/>.
/// </summary>
public static ICompiledReferenceTrack RootComponent( Type type, Guid? id = null ) =>
TypeLibrary.GetType( typeof( CompiledReferenceTrack<> ) )
.CreateGeneric<ICompiledReferenceTrack>( [type], [id ?? Guid.NewGuid(), type.Name, null] );
/// <summary>
/// Create a root <see cref="ICompiledReferenceTrack"/> that targets a <see cref="Sandbox.Component"/> with
/// the type <typeparamref name="T"/>. To create a nested track, use <see cref="CompiledClipExtensions.Component{T}"/>.
/// </summary>
public static CompiledReferenceTrack<T> RootComponent<T>( Guid? id = null )
where T : Component => new( id ?? Guid.NewGuid(), typeof( T ).Name );
}