// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Concurrent; using System.Threading.Tasks; using osu.Framework.Development; using osu.Framework.Platform; using osu.Framework.Statistics; namespace osu.Framework.Audio { /// /// A base class for audio components which offers audio thread deferring, disposal and basic update logic. /// public abstract class AudioComponent : IDisposable, IUpdateable { /// /// Audio operations will be run on a separate dedicated thread, so we need to schedule any audio API calls using this queue. /// protected ConcurrentQueue PendingActions = new ConcurrentQueue(); private bool acceptingActions = true; /// /// Whether an audio thread specific action can be performed inline. /// protected bool CanPerformInline => ThreadSafety.IsAudioThread || (ThreadSafety.ExecutionMode == ExecutionMode.SingleThread && ThreadSafety.IsUpdateThread); /// /// Enqueues an action to be performed on the audio thread. /// /// The action to perform. /// A task which can be used for continuation logic. May return a if called while already on the audio thread. protected Task EnqueueAction(Action action) { if (ThreadSafety.ExecutionMode == ExecutionMode.SingleThread) { if (ThreadSafety.IsDrawThread) throw new InvalidOperationException("Cannot perform audio operation from draw thread."); if (ThreadSafety.IsInputThread) throw new InvalidOperationException("Cannot perform audio operation from input thread."); } if (CanPerformInline) { action(); return Task.CompletedTask; } if (!acceptingActions) // we don't want consumers to block on operations after we are disposed. return Task.CompletedTask; var task = new Task(action); PendingActions.Enqueue(task); return task; } /// /// Run each loop of the audio thread's execution after queued actions are completed to allow components to perform any additional operations. /// protected virtual void UpdateState() { } /// /// Run each loop of the audio thread's execution, after as a way to update any child components. /// protected virtual void UpdateChildren() { } /// /// Updates this audio component. Always runs on the audio thread. /// public void Update() { ThreadSafety.EnsureNotUpdateThread(); if (IsDisposed) throw new ObjectDisposedException(ToString(), "Can not update disposed audio components."); FrameStatistics.Add(StatisticsCounterType.TasksRun, PendingActions.Count); FrameStatistics.Increment(StatisticsCounterType.Components); while (!IsDisposed && PendingActions.TryDequeue(out Task task)) task.RunSynchronously(); if (!IsDisposed) UpdateState(); UpdateChildren(); } /// /// Whether this component has completed playback and is in a stopped state. /// public virtual bool HasCompleted => !IsAlive; /// /// When false, this component has completed all processing and is ready to be removed from its parent. /// public virtual bool IsAlive => !IsDisposed; /// /// Whether this component has finished loading its resources. /// public virtual bool IsLoaded => true; #region IDisposable Support protected volatile bool IsDisposed; public void Dispose() { acceptingActions = false; PendingActions.Enqueue(new Task(() => Dispose(true))); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { IsDisposed = true; } #endregion } }