A game framework written with osu! in mind.
at master 89 lines 3.3 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.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}