A game framework written with osu! in mind.
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}