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.Generic;
6using System.Diagnostics;
7
8namespace osu.Framework.Graphics.OpenGL
9{
10 /// <summary>
11 /// Helper class used to manage GL disposals in a thread-safe manner.
12 /// </summary>
13 internal class GLDisposalQueue
14 {
15 private readonly List<PendingDisposal> newDisposals;
16 private readonly List<PendingDisposal> pendingDisposals;
17
18 public GLDisposalQueue()
19 {
20 newDisposals = new List<PendingDisposal>();
21 pendingDisposals = new List<PendingDisposal>();
22 }
23
24 /// <summary>
25 /// Schedules a new disposal action to be executed at a later point in time.
26 /// This method can be called concurrently from multiple threads.
27 /// By default the disposal will run <see cref="GLWrapper.MAX_DRAW_NODES"/> frames after enqueueing.
28 /// </summary>
29 /// <param name="disposalAction">The disposal action to be executed.</param>
30 public void ScheduleDisposal(Action disposalAction)
31 {
32 lock (newDisposals)
33 newDisposals.Add(new PendingDisposal(disposalAction));
34 }
35
36 /// <summary>
37 /// Checks pending disposals and executes ones whose frame delay has expired.
38 /// This method can NOT be called concurrently from multiple threads.
39 /// </summary>
40 public void CheckPendingDisposals()
41 {
42 lock (newDisposals)
43 {
44 pendingDisposals.AddRange(newDisposals);
45 newDisposals.Clear();
46 }
47
48 // because disposals are added in batches every frame,
49 // and each frame the remaining frame delay of all disposal tasks is decremented by 1,
50 // all disposals that are executable this frame must be placed at the start of the list.
51 // track the index of the last one, so we can clean them up in one fell swoop instead of as-we-go
52 // (the latter approach can incur a quadratic time penalty).
53 int lastExecutedDisposal = -1;
54
55 for (var i = 0; i < pendingDisposals.Count; i++)
56 {
57 var item = pendingDisposals[i];
58
59 if (item.RemainingFrameDelay-- == 0)
60 {
61 item.Action();
62 lastExecutedDisposal = i;
63 }
64 }
65
66 if (lastExecutedDisposal < 0)
67 return;
68
69 // note the signs - a 0 in the inner loop is a -1 here due to the postfix decrement.
70 Debug.Assert(pendingDisposals[lastExecutedDisposal].RemainingFrameDelay < 0);
71 Debug.Assert(lastExecutedDisposal + 1 == pendingDisposals.Count
72 || pendingDisposals[lastExecutedDisposal + 1].RemainingFrameDelay >= 0);
73
74 pendingDisposals.RemoveRange(0, lastExecutedDisposal + 1);
75 }
76
77 private class PendingDisposal
78 {
79 public int RemainingFrameDelay = GLWrapper.MAX_DRAW_NODES;
80
81 public readonly Action Action;
82
83 public PendingDisposal(Action action)
84 {
85 Action = action;
86 }
87 }
88 }
89}