A game framework written with osu! in mind.
at master 132 lines 4.7 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Concurrent; 6using System.Threading.Tasks; 7using osu.Framework.Development; 8using osu.Framework.Platform; 9using osu.Framework.Statistics; 10 11namespace osu.Framework.Audio 12{ 13 /// <summary> 14 /// A base class for audio components which offers audio thread deferring, disposal and basic update logic. 15 /// </summary> 16 public abstract class AudioComponent : IDisposable, IUpdateable 17 { 18 /// <summary> 19 /// Audio operations will be run on a separate dedicated thread, so we need to schedule any audio API calls using this queue. 20 /// </summary> 21 protected ConcurrentQueue<Task> PendingActions = new ConcurrentQueue<Task>(); 22 23 private bool acceptingActions = true; 24 25 /// <summary> 26 /// Whether an audio thread specific action can be performed inline. 27 /// </summary> 28 protected bool CanPerformInline => 29 ThreadSafety.IsAudioThread || (ThreadSafety.ExecutionMode == ExecutionMode.SingleThread && ThreadSafety.IsUpdateThread); 30 31 /// <summary> 32 /// Enqueues an action to be performed on the audio thread. 33 /// </summary> 34 /// <param name="action">The action to perform.</param> 35 /// <returns>A task which can be used for continuation logic. May return a <see cref="Task.CompletedTask"/> if called while already on the audio thread.</returns> 36 protected Task EnqueueAction(Action action) 37 { 38 if (ThreadSafety.ExecutionMode == ExecutionMode.SingleThread) 39 { 40 if (ThreadSafety.IsDrawThread) 41 throw new InvalidOperationException("Cannot perform audio operation from draw thread."); 42 43 if (ThreadSafety.IsInputThread) 44 throw new InvalidOperationException("Cannot perform audio operation from input thread."); 45 } 46 47 if (CanPerformInline) 48 { 49 action(); 50 return Task.CompletedTask; 51 } 52 53 if (!acceptingActions) 54 // we don't want consumers to block on operations after we are disposed. 55 return Task.CompletedTask; 56 57 var task = new Task(action); 58 PendingActions.Enqueue(task); 59 return task; 60 } 61 62 /// <summary> 63 /// Run each loop of the audio thread's execution after queued actions are completed to allow components to perform any additional operations. 64 /// </summary> 65 protected virtual void UpdateState() 66 { 67 } 68 69 /// <summary> 70 /// Run each loop of the audio thread's execution, after <see cref="UpdateState"/> as a way to update any child components. 71 /// </summary> 72 protected virtual void UpdateChildren() 73 { 74 } 75 76 /// <summary> 77 /// Updates this audio component. Always runs on the audio thread. 78 /// </summary> 79 public void Update() 80 { 81 ThreadSafety.EnsureNotUpdateThread(); 82 83 if (IsDisposed) 84 throw new ObjectDisposedException(ToString(), "Can not update disposed audio components."); 85 86 FrameStatistics.Add(StatisticsCounterType.TasksRun, PendingActions.Count); 87 FrameStatistics.Increment(StatisticsCounterType.Components); 88 89 while (!IsDisposed && PendingActions.TryDequeue(out Task task)) 90 task.RunSynchronously(); 91 92 if (!IsDisposed) 93 UpdateState(); 94 95 UpdateChildren(); 96 } 97 98 /// <summary> 99 /// Whether this component has completed playback and is in a stopped state. 100 /// </summary> 101 public virtual bool HasCompleted => !IsAlive; 102 103 /// <summary> 104 /// When false, this component has completed all processing and is ready to be removed from its parent. 105 /// </summary> 106 public virtual bool IsAlive => !IsDisposed; 107 108 /// <summary> 109 /// Whether this component has finished loading its resources. 110 /// </summary> 111 public virtual bool IsLoaded => true; 112 113 #region IDisposable Support 114 115 protected volatile bool IsDisposed; 116 117 public void Dispose() 118 { 119 acceptingActions = false; 120 PendingActions.Enqueue(new Task(() => Dispose(true))); 121 122 GC.SuppressFinalize(this); 123 } 124 125 protected virtual void Dispose(bool disposing) 126 { 127 IsDisposed = true; 128 } 129 130 #endregion 131 } 132}