A game framework written with osu! in mind.
at master 149 lines 4.9 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.Linq; 7using NUnit.Framework; 8using osu.Framework.Allocation; 9using osu.Framework.Graphics; 10using osu.Framework.Graphics.Containers; 11using osu.Framework.Graphics.OpenGL; 12using osu.Framework.Graphics.Shapes; 13using osuTK; 14using osuTK.Graphics; 15 16namespace osu.Framework.Tests.Visual.Drawables 17{ 18 public class TestSceneDrawNodeDisposal : FrameworkTestScene 19 { 20 /// <summary> 21 /// Tests that all references are lost after a drawable is disposed. 22 /// </summary> 23 [Test] 24 public void TestBasicDrawNodeReferencesRemovedAfterDisposal() => performTest(new Box { RelativeSizeAxes = Axes.Both }); 25 26 /// <summary> 27 /// Tests that all references are lost after a composite is disposed. 28 /// </summary> 29 [Test] 30 public void TestCompositeDrawNodeReferencesRemovedAfterDisposal() => performTest(new NonFlattenedContainer 31 { 32 RelativeSizeAxes = Axes.Both, 33 Children = new[] 34 { 35 new Box 36 { 37 RelativeSizeAxes = Axes.Both, 38 Width = 0.5f, 39 Colour = Color4.Blue 40 }, 41 new Box 42 { 43 RelativeSizeAxes = Axes.Both, 44 X = 0.5f, 45 Width = 0.5f, 46 Colour = Color4.Blue 47 }, 48 } 49 }); 50 51 /// <summary> 52 /// Tests that all references are lost after a buffered container is disposed. 53 /// </summary> 54 [Test] 55 public void TestBufferedDrawNodeReferencesRemovedAfterDisposal() => performTest(new BufferedContainer 56 { 57 RelativeSizeAxes = Axes.Both, 58 Children = new[] 59 { 60 new Box 61 { 62 RelativeSizeAxes = Axes.Both, 63 Width = 0.5f, 64 Colour = Color4.Blue 65 }, 66 new Box 67 { 68 RelativeSizeAxes = Axes.Both, 69 X = 0.5f, 70 Width = 0.5f, 71 Colour = Color4.Blue 72 }, 73 } 74 }); 75 76 private void performTest(Drawable child) 77 { 78 Container parentContainer = null; 79 80 var drawableRefs = new List<WeakReference>(); 81 82 // Add the children to the hierarchy, and build weak-reference wrappers around them 83 AddStep("create hierarchy", () => 84 { 85 drawableRefs.Clear(); 86 buildReferencesRecursive(child); 87 88 Child = parentContainer = new NonFlattenedContainer 89 { 90 Size = new Vector2(200), 91 Child = child 92 }; 93 94 void buildReferencesRecursive(Drawable target) 95 { 96 drawableRefs.Add(new WeakReference(target)); 97 98 if (target is CompositeDrawable compositeTarget) 99 { 100 foreach (var c in compositeTarget.InternalChildren) 101 buildReferencesRecursive(c); 102 } 103 } 104 }); 105 106 AddWaitStep("wait for some draw nodes", GLWrapper.MAX_DRAW_NODES); 107 108 // Clear the parent to ensure no references are held via drawables themselves, 109 // and remove the parent to ensure that the parent maintains references to the child draw nodes 110 AddStep("clear + remove parent container", () => 111 { 112 parentContainer.Clear(); 113 Remove(parentContainer); 114 115 // Lose last hard-reference to the child 116 child = null; 117 }); 118 119 // Wait for all drawables to get disposed 120 DisposalMarker disposalMarker = null; 121 AddStep("add disposal marker", () => AsyncDisposalQueue.Enqueue(disposalMarker = new DisposalMarker())); 122 AddUntilStep("wait for drawables to dispose", () => disposalMarker.Disposed); 123 124 // Induce the collection of drawables 125 AddStep("invoke GC", () => 126 { 127 GC.Collect(); 128 GC.WaitForPendingFinalizers(); 129 }); 130 131 AddUntilStep("all drawable references lost", () => !drawableRefs.Any(r => r.IsAlive)); 132 } 133 134 private class NonFlattenedContainer : Container 135 { 136 protected override bool CanBeFlattened => false; 137 } 138 139 private class DisposalMarker : IDisposable 140 { 141 public bool Disposed { get; private set; } 142 143 public void Dispose() 144 { 145 Disposed = true; 146 } 147 } 148 } 149}