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.Threading;
6using osu.Framework.Graphics.OpenGL.Textures;
7
8namespace osu.Framework.Graphics.Textures
9{
10 /// <summary>
11 /// A texture which shares a common reference count with all other textures using the same <see cref="TextureGL"/>.
12 /// </summary>
13 internal class TextureWithRefCount : Texture
14 {
15 private readonly ReferenceCount count;
16
17 public TextureWithRefCount(TextureGL textureGl, ReferenceCount count)
18 : base(textureGl)
19 {
20 this.count = count;
21
22 count.Increment();
23 }
24
25 public sealed override TextureGL TextureGL
26 {
27 get
28 {
29 if (!Available)
30 throw new InvalidOperationException($"Attempting to access a {nameof(TextureWithRefCount)}'s underlying texture after all references are lost.");
31
32 return base.TextureGL;
33 }
34 }
35
36 // The base property invokes the overridden TextureGL property, which will throw an exception if not available
37 // So this property is redirected to reference the intended member
38 public sealed override bool Available => base.TextureGL.Available;
39
40 ~TextureWithRefCount()
41 {
42 // Finalizer implemented here rather than Texture to avoid GC overhead.
43 Dispose(false);
44 }
45
46 public bool IsDisposed { get; private set; }
47
48 protected override void Dispose(bool isDisposing)
49 {
50 base.Dispose(isDisposing);
51
52 if (IsDisposed)
53 return;
54
55 IsDisposed = true;
56
57 count.Decrement();
58 }
59
60 public class ReferenceCount
61 {
62 private readonly object lockObject;
63 private readonly Action onAllReferencesLost;
64
65 private int referenceCount;
66
67 /// <summary>
68 /// Creates a new <see cref="ReferenceCount"/>.
69 /// </summary>
70 /// <param name="lockObject">The <see cref="object"/> which locks will be taken out on.</param>
71 /// <param name="onAllReferencesLost">A delegate to invoke after all references have been lost.</param>
72 public ReferenceCount(object lockObject, Action onAllReferencesLost)
73 {
74 this.lockObject = lockObject;
75 this.onAllReferencesLost = onAllReferencesLost;
76 }
77
78 /// <summary>
79 /// Increments the reference count.
80 /// </summary>
81 public void Increment()
82 {
83 lock (lockObject)
84 Interlocked.Increment(ref referenceCount);
85 }
86
87 /// <summary>
88 /// Decrements the reference count, invoking <see cref="onAllReferencesLost"/> if there are no remaining references.
89 /// The delegate is invoked while a lock on the provided <see cref="lockObject"/> is held.
90 /// </summary>
91 public void Decrement()
92 {
93 lock (lockObject)
94 {
95 if (Interlocked.Decrement(ref referenceCount) == 0)
96 onAllReferencesLost?.Invoke();
97 }
98 }
99 }
100 }
101}