Move movie runtime support to frontend
This commit is contained in:
184
engine/Age.Engine.Frontend/MovieSurfaceRegistry.cs
Normal file
184
engine/Age.Engine.Frontend/MovieSurfaceRegistry.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Age.Engine.Model;
|
||||
|
||||
internal readonly record struct MovieSurfaceBinding(long PlaybackId, long ResourceId, int SurfaceSlot);
|
||||
internal sealed record MovieSurfaceFrame(RgbaImage Image, string Name, int AssetId);
|
||||
public sealed record MovieSurfaceDiagnostic(int SurfaceSlot, long PlaybackId, long ResourceId,
|
||||
bool Completed, bool HasFrame, string? Name);
|
||||
internal enum MovieSurfaceReleaseKind { NotBound, Active, Released }
|
||||
internal readonly record struct MovieSurfaceRelease(MovieSurfaceReleaseKind Kind,
|
||||
MovieSurfaceBinding Binding);
|
||||
|
||||
/// <summary>Instance-keyed movie/surface ownership. A resource may be played more than once concurrently;
|
||||
/// completion and frames therefore belong to a playback, never to the shared asset id.</summary>
|
||||
internal sealed class MovieSurfaceRegistry
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Dictionary<int, MovieSurfaceBinding> _bySurface = new();
|
||||
private readonly Dictionary<long, MovieSurfaceBinding> _byPlayback = new();
|
||||
private readonly Dictionary<long, MovieSurfaceFrame> _frames = new();
|
||||
private readonly HashSet<long> _completed = new();
|
||||
private readonly HashSet<long> _knownMovieResources = new();
|
||||
private long _nextPlaybackId;
|
||||
|
||||
public MovieSurfaceBinding Begin(int surfaceSlot, long resourceId,
|
||||
out MovieSurfaceBinding? replaced)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
replaced = _bySurface.TryGetValue(surfaceSlot, out var prior) ? prior : null;
|
||||
if (replaced.HasValue) RemoveLocked(prior);
|
||||
var binding = new MovieSurfaceBinding(++_nextPlaybackId, resourceId, surfaceSlot);
|
||||
_knownMovieResources.Add(resourceId);
|
||||
_bySurface[surfaceSlot] = binding;
|
||||
_byPlayback[binding.PlaybackId] = binding;
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PublishFrame(long playbackId, RgbaImage image, string name, int assetId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_byPlayback.ContainsKey(playbackId)) return false;
|
||||
_frames[playbackId] = new MovieSurfaceFrame(image, name, assetId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Complete(long playbackId)
|
||||
{
|
||||
lock (_lock)
|
||||
return _byPlayback.ContainsKey(playbackId) && _completed.Add(playbackId);
|
||||
}
|
||||
|
||||
public bool IsActive(int surfaceSlot)
|
||||
{
|
||||
lock (_lock)
|
||||
return _bySurface.TryGetValue(surfaceSlot, out var binding)
|
||||
&& !_completed.Contains(binding.PlaybackId);
|
||||
}
|
||||
|
||||
public bool IsBound(int surfaceSlot)
|
||||
{
|
||||
lock (_lock) return _bySurface.ContainsKey(surfaceSlot);
|
||||
}
|
||||
|
||||
public bool HasActivePlayback
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
return _byPlayback.Keys.Any(playbackId => !_completed.Contains(playbackId));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryResolveSurface(int surfaceSlot, out MovieSurfaceFrame? frame)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_bySurface.TryGetValue(surfaceSlot, out var binding)
|
||||
&& _frames.TryGetValue(binding.PlaybackId, out var found))
|
||||
{
|
||||
frame = found;
|
||||
return true;
|
||||
}
|
||||
frame = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryResolveResource(long resourceId, out MovieSurfaceFrame? frame)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
long newestPlaybackId = long.MinValue;
|
||||
MovieSurfaceFrame? newestFrame = null;
|
||||
foreach (var binding in _byPlayback.Values)
|
||||
{
|
||||
if (binding.ResourceId != resourceId || binding.PlaybackId <= newestPlaybackId ||
|
||||
!_frames.TryGetValue(binding.PlaybackId, out var found))
|
||||
continue;
|
||||
newestPlaybackId = binding.PlaybackId;
|
||||
newestFrame = found;
|
||||
}
|
||||
frame = newestFrame;
|
||||
return newestFrame != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Catalog ids are immutable within a mounted resource set. Remembering that an id entered
|
||||
/// the typed movie path prevents a cleanup-frame render snapshot from treating its .AGF-named MPEG
|
||||
/// payload as a still image after the last live surface binding has been detached.</summary>
|
||||
public bool IsKnownMovieResource(long resourceId)
|
||||
{
|
||||
lock (_lock) return _knownMovieResources.Contains(resourceId);
|
||||
}
|
||||
|
||||
public MovieSurfaceRelease ReleaseIfCompleted(int surfaceSlot)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_bySurface.TryGetValue(surfaceSlot, out var binding))
|
||||
return new MovieSurfaceRelease(MovieSurfaceReleaseKind.NotBound, default);
|
||||
if (!_completed.Contains(binding.PlaybackId))
|
||||
return new MovieSurfaceRelease(MovieSurfaceReleaseKind.Active, binding);
|
||||
RemoveLocked(binding);
|
||||
return new MovieSurfaceRelease(MovieSurfaceReleaseKind.Released, binding);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Abandon(long playbackId, out MovieSurfaceBinding binding)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_byPlayback.TryGetValue(playbackId, out binding)) return false;
|
||||
RemoveLocked(binding);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<MovieSurfaceBinding> ReleaseRange(int firstSlot, int count)
|
||||
{
|
||||
int end = checked(firstSlot + count);
|
||||
lock (_lock)
|
||||
{
|
||||
var released = _bySurface.Values
|
||||
.Where(binding => binding.SurfaceSlot >= firstSlot && binding.SurfaceSlot < end)
|
||||
.OrderBy(binding => binding.SurfaceSlot)
|
||||
.ToArray();
|
||||
foreach (var binding in released) RemoveLocked(binding);
|
||||
return released;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<MovieSurfaceDiagnostic> Snapshot()
|
||||
{
|
||||
lock (_lock)
|
||||
return _bySurface.Values
|
||||
.OrderBy(binding => binding.SurfaceSlot)
|
||||
.Select(binding => new MovieSurfaceDiagnostic(
|
||||
binding.SurfaceSlot,
|
||||
binding.PlaybackId,
|
||||
binding.ResourceId,
|
||||
_completed.Contains(binding.PlaybackId),
|
||||
_frames.ContainsKey(binding.PlaybackId),
|
||||
_frames.TryGetValue(binding.PlaybackId, out var frame) ? frame.Name : null))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<long> CompletedPlaybackIds()
|
||||
{
|
||||
lock (_lock) return _completed.OrderBy(id => id).ToArray();
|
||||
}
|
||||
|
||||
private void RemoveLocked(MovieSurfaceBinding binding)
|
||||
{
|
||||
_bySurface.Remove(binding.SurfaceSlot);
|
||||
_byPlayback.Remove(binding.PlaybackId);
|
||||
_frames.Remove(binding.PlaybackId);
|
||||
_completed.Remove(binding.PlaybackId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user