Editor/MovieMaker/Extensions/CollectionExtensions.cs
using System.Collections;
using Sandbox.MovieMaker;
using System.Collections.Immutable;
using System.Linq;
namespace Editor.MovieMaker;
#nullable enable
public static class CollectionExtensions
{
public static T? FirstOrNull<T>( this IEnumerable<T> enumerable, Func<T, bool> predicate )
where T : struct
{
foreach ( var item in enumerable )
{
if ( predicate( item ) )
{
return item;
}
}
return null;
}
public static IReadOnlyList<T> Slice<T>( this IReadOnlyList<T> list, int offset, int count )
{
if ( offset < 0 )
{
throw new ArgumentException( "Offset must be >= 0.", nameof( offset ) );
}
if ( list.Count < offset + count )
{
throw new ArgumentException( "Slice exceeds list element count.", nameof( count ) );
}
// Fast paths
if ( count == 0 ) return Array.Empty<T>();
if ( offset == 0 && count == list.Count ) return list;
switch ( list )
{
case T[] array:
return new ArraySegment<T>( array, offset, count );
case ImmutableArray<T> immutableArray:
return immutableArray.Slice( offset, count );
case ArraySegment<T> segment:
return segment.Slice( offset, count );
}
// Slow copy
return list.Skip( offset ).Take( count ).ToArray();
}
/// <summary>
/// Given two ascending lists of time ranges, union any overlapping pairs between the two lists and return them.
/// </summary>
public static IEnumerable<MovieTimeRange> Union( this IEnumerable<MovieTimeRange> first, IEnumerable<MovieTimeRange> second )
{
using var enumeratorA = first.GetEnumerator();
using var enumeratorB = second.GetEnumerator();
var hasItemA = enumeratorA.MoveNext();
var hasItemB = enumeratorB.MoveNext();
while ( hasItemA || hasItemB )
{
var next = !hasItemB || hasItemA && enumeratorA.Current.Start <= enumeratorB.Current.Start
? enumeratorA.Current
: enumeratorB.Current;
while ( true )
{
if ( hasItemA && next.Intersect( enumeratorA.Current ) is not null )
{
next = next.Union( enumeratorA.Current );
hasItemA = enumeratorA.MoveNext();
continue;
}
if ( hasItemB && next.Intersect( enumeratorB.Current ) is not null )
{
next = next.Union( enumeratorB.Current );
hasItemB = enumeratorB.MoveNext();
continue;
}
break;
}
yield return next;
}
}
}
public interface ISynchronizedList<TSrc, TItem> : IReadOnlyList<TItem>
{
public void Clear() => Update( [] );
bool Update( IEnumerable<TSrc> source );
int IndexOf( TSrc src );
}
/// <summary>
/// Maintains a set of <typeparamref name="TItem"/>, mapped from a collection of <see cref="TSrc"/>.
/// </summary>
public sealed class SynchronizedSet<TSrc, TItem> : ISynchronizedList<TSrc, TItem>
where TSrc : notnull
{
private readonly Dictionary<TSrc, TItem> _items;
private readonly Dictionary<TSrc, int> _indices;
private readonly List<TSrc> _orderedSources = new();
private readonly Func<TSrc, TItem> _addFunc;
private readonly Action<TItem>? _removeAction;
private readonly Func<TSrc, TItem, bool>? _updateAction;
public SynchronizedSet( Func<TSrc, TItem> addFunc,
Action<TItem>? removeAction = null,
Func<TSrc, TItem, bool>? updateAction = null,
IEqualityComparer<TSrc>? comparer = null )
{
_addFunc = addFunc;
_removeAction = removeAction;
_updateAction = updateAction;
_items = new Dictionary<TSrc, TItem>( comparer );
_indices = new Dictionary<TSrc, int>( comparer );
}
public TItem? Find( TSrc key ) => _items.GetValueOrDefault( key );
public void Clear() => Update( [] );
public bool Update( IEnumerable<TSrc> source )
{
_indices.Clear();
foreach ( var src in source )
{
_indices.Add( src, _indices.Count );
}
var changed = false;
// Remove items
for ( var i = _orderedSources.Count - 1; i >= 0; --i )
{
var src = _orderedSources[i];
if ( _indices.ContainsKey( src ) ) continue;
_orderedSources.RemoveAt( i );
if ( !_items.Remove( src, out var item ) ) continue;
_removeAction?.Invoke( item );
changed = true;
}
// Add items
foreach ( var src in _indices.Keys )
{
if ( _items.ContainsKey( src ) ) continue;
var item = _addFunc( src );
_orderedSources.Add( src );
_items.Add( src, item );
changed = true;
}
// Sort and update items
_orderedSources.Sort( ( a, b ) => _indices[a] - _indices[b] );
if ( _updateAction is { } updateAction )
{
foreach ( var src in _orderedSources )
{
changed |= updateAction( src, _items[src] );
}
}
return changed;
}
public int IndexOf( TSrc src ) => _indices.TryGetValue( src, out var index ) ? index : -1;
public IEnumerator<TItem> GetEnumerator() => _orderedSources.Select(x => _items[x] ).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
public int Count => _items.Count;
public TItem this[ int index ] => _items[_orderedSources[index]];
}
/// <summary>
/// Maintains a list of <typeparamref name="TItem"/> with the same length as a list of <typeparamref name="TSrc"/>.
/// </summary>
public sealed class SynchronizedList<TSrc, TItem> : ISynchronizedList<TSrc, TItem>
{
private readonly List<TSrc> _sources = new();
private readonly List<TItem> _items = new();
private readonly Func<TSrc, TItem> _addFunc;
private readonly Action<TItem>? _removeAction;
private readonly UpdateItemDelegate? _updateAction;
public IEnumerable<TSrc> Sources => _sources;
public delegate bool UpdateItemDelegate( TSrc source, ref TItem item );
public SynchronizedList( Func<TSrc, TItem> addFunc,
Action<TItem>? removeAction = null,
UpdateItemDelegate? updateAction = null )
{
_addFunc = addFunc;
_removeAction = removeAction;
_updateAction = updateAction;
}
public void Clear() => Update( [] );
public bool Update( IEnumerable<TSrc> source )
{
_sources.Clear();
_sources.AddRange( source );
var changed = false;
// Remove items
while ( _items.Count > _sources.Count )
{
_removeAction?.Invoke( _items[^1] );
_items.RemoveAt( _items.Count - 1 );
changed = true;
}
// Add items
while ( _items.Count < _sources.Count )
{
_items.Add( _addFunc( _sources[_items.Count] ) );
changed = true;
}
if ( _updateAction is { } updateAction )
{
for ( var i = 0; i < _sources.Count; ++i )
{
var item = _items[i];
if ( updateAction( _sources[i], ref item ) )
{
_items[i] = item;
changed = true;
}
}
}
return changed;
}
public int IndexOf( TSrc src ) => _sources.IndexOf( src );
public IEnumerator<TItem> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
public int Count => _items.Count;
public TItem this[int index] => _items[index];
}