diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFrame.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFrame.cs index 7c0881e95..48be143ad 100644 --- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFrame.cs +++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFrame.cs @@ -85,10 +85,11 @@ public void Dispose() var name = (uint)_nativeTexturePtr; lock (((ICollection)_NameTable).SyncRoot) { - var _ = _NameTable.Remove(name); + _ = _NameTable.Remove(name); } } _glSyncToken?.Dispose(); + _ = _InstanceTable.Remove(_instanceId); } public void CopyTexture(Texture dst) => Graphics.CopyTexture(_texture, dst); diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFramePool.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFramePool.cs index 5e8d49329..9665bc96f 100644 --- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFramePool.cs +++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Unity/Experimental/TextureFramePool.cs @@ -50,12 +50,17 @@ public TextureFramePool(int textureWidth, int textureHeight, TextureFormat textu _textureFramesInUse = new Dictionary(poolSize); } - void IDisposable.Dispose() + public void Dispose() { _textureFramesLock.EnterWriteLock(); try { + foreach (var textureFrame in _availableTextureFrames) + { + textureFrame.Dispose(); + } _availableTextureFrames.Clear(); + foreach (var textureFrame in _textureFramesInUse.Values) { textureFrame.Dispose(); diff --git a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Util/GlobalInstanceTable.cs b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Util/GlobalInstanceTable.cs index 62f00f121..90f695ec5 100644 --- a/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Util/GlobalInstanceTable.cs +++ b/Packages/com.github.homuler.mediapipe/Runtime/Scripts/Util/GlobalInstanceTable.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Linq; namespace Mediapipe { @@ -14,7 +15,7 @@ namespace Mediapipe public class GlobalInstanceTable where TValue : class { private readonly ReaderWriterLockSlim _tableLock = new ReaderWriterLockSlim(); - private readonly Dictionary> _table = new Dictionary>(); + private readonly Dictionary> _table; private int _maxSize; /// @@ -35,9 +36,26 @@ public int maxSize } } + public int count + { + get + { + _tableLock.EnterReadLock(); + try + { + return _table.Count; + } + finally + { + _tableLock.ExitReadLock(); + } + } + } + public GlobalInstanceTable(int maxSize = 0) { this.maxSize = maxSize; + _table = new Dictionary>(maxSize); } public void Add(TKey key, TValue value) @@ -118,17 +136,29 @@ public bool ContainsKey(TKey key) } } + public bool Remove(TKey key) + { + _tableLock.EnterWriteLock(); + try + { + return _table.Remove(key); + } + finally + { + _tableLock.ExitWriteLock(); + } + } + /// /// Aquire the write lock before calling this method. /// private void ClearUnusedKeys() { - foreach (var pair in _table) + var deadKeys = _table.Where(x => !x.Value.TryGetTarget(out var target)).Select(x => x.Key).ToArray(); + + foreach (var key in deadKeys) { - if (!pair.Value.TryGetTarget(out var _)) - { - var _ = _table.Remove(pair.Key); - } + var _ = _table.Remove(key); } } } diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util.meta b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util.meta new file mode 100644 index 000000000..80f09c24e --- /dev/null +++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65e0904ab391d3751a8aedabf3b0407c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs new file mode 100644 index 000000000..fe8a9752d --- /dev/null +++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs @@ -0,0 +1,219 @@ +// Copyright (c) 2023 homuler +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +using System; +using NUnit.Framework; + +namespace Mediapipe.Tests.Util +{ + public class GlobalInstanceTableTest + { + private class Value + { + public readonly int value; + + public Value(int value) + { + this.value = value; + } + } + + #region Constructor + [Test] + public void Ctor_Throws_WhenMaxSizeIsLessThenZero() => Assert.Throws(() => new GlobalInstanceTable(-1)); + + [Test] + public void Ctor_InstantiateAnEmptyTable_WhenMaxSizeIsZero() + { + var table = new GlobalInstanceTable(); + Assert.AreEqual(0, table.maxSize); + } + + [Test] + public void Ctor_InstantiateAnEmptyTable_WhenMaxSizeIsSpecified() + { + var table = new GlobalInstanceTable(10); + Assert.AreEqual(10, table.maxSize); + } + #endregion + + #region maxSize + [Test] + public void MaxSize_MustBeLargerThanZero() + { + var table = new GlobalInstanceTable(10); + Assert.Throws(() => table.maxSize = -1); + } + + [Test] + public void MaxSize_CanBeChangedToLargerValue() + { + var table = new GlobalInstanceTable(5); + Assert.AreEqual(5, table.maxSize); + + table.maxSize = 6; + Assert.AreEqual(6, table.maxSize); + } + + [Test] + public void MaxSize_CanBeChangedToSmallerValue() + { + var table = new GlobalInstanceTable(5); + Assert.AreEqual(5, table.maxSize); + + table.Add(1, new Value(1)); + table.Add(2, new Value(2)); + table.Add(3, new Value(3)); + table.Add(4, new Value(4)); + table.Add(5, new Value(5)); + + table.maxSize = 2; + Assert.AreEqual(2, table.maxSize); + Assert.AreEqual(5, table.count); + } + #endregion + + #region Add + [Test] + public void CannotAdd_IfCountEqualsMaxSize() + { + var table = new GlobalInstanceTable(1); + var v1 = new Value(1); + + table.Add(1, v1); + Assert.Throws(() => table.Add(2, new Value(2))); + + GC.KeepAlive(v1); + } + + [Test, Ignore("Skip because it's non-deterministic")] + public void CanAdd_IfCountEqualsMaxSize_But_SomeValuesAreGCed() + { + var table = new GlobalInstanceTable(1); + + table.Add(1, new Value(1)); + GC.Collect(); + + Assert.DoesNotThrow(() => table.Add(2, new Value(2))); + } + + [Test] + public void CannotAdd_If_KeyAlreadyExists() + { + var table = new GlobalInstanceTable(2); + var v1 = new Value(1); + + table.Add(1, v1); + Assert.Throws(() => table.Add(1, new Value(2))); + + GC.KeepAlive(v1); + } + + [Test, Ignore("Skip because it's non-deterministic")] + public void CanAdd_If_KeyAlreadyExists_But_TheReferenceIsGCed() + { + var table = new GlobalInstanceTable(2); + + table.Add(1, new Value(1)); + GC.Collect(); + + Assert.DoesNotThrow(() => table.Add(1, new Value(2))); + } + #endregion + + #region TryGetValue + [Test] + public void TryGetValue_ReturnsTrue_IfTheKeyExists() + { + var table = new GlobalInstanceTable(1); + var v1 = new Value(1); + + table.Add(1, v1); + Assert.IsTrue(table.TryGetValue(1, out var v2)); + Assert.AreEqual(v1, v2); + + GC.KeepAlive(v1); + } + + [Test, Ignore("Skip because it's non-deterministic")] + public void TryGetValue_ReturnsFalse_IfTheKeyExists_But_TheValueIsGCed() + { + var table = new GlobalInstanceTable(1); + + table.Add(1, new Value(1)); + GC.Collect(); + + Assert.IsFalse(table.TryGetValue(1, out var _)); + } + + [Test] + public void TryGetValue_ReturnsFalse_IfTheKeyDoesNotExist() + { + var table = new GlobalInstanceTable(1); + Assert.IsFalse(table.TryGetValue(1, out var _)); + } + #endregion + + #region Clear + [Test] + public void Clear_ClearsTheTable() + { + var table = new GlobalInstanceTable(2); + var v1 = new Value(1); + var v2 = new Value(2); + + table.Add(1, v1); + table.Add(2, v2); + Assert.AreEqual(2, table.count); + + table.Clear(); + Assert.AreEqual(0, table.count); + + GC.KeepAlive(v1); + GC.KeepAlive(v2); + } + #endregion + + #region ContainsKey + [Test, Ignore("Skip because it's non-deterministic")] + public void ContainsKey_ReturnsTrue_IfTheKeyExists() + { + var table = new GlobalInstanceTable(1); + table.Add(1, new Value(1)); + + GC.Collect(); + + Assert.IsTrue(table.ContainsKey(1)); + Assert.False(table.TryGetValue(1, out var _)); + } + + [Test] + public void ContainsKey_ReturnsFalse_IfTheKyeDoesNotExist() + { + var table = new GlobalInstanceTable(1); + Assert.IsFalse(table.ContainsKey(1)); + } + #endregion + + #region Remove + [Test] + public void Remove_RemovesTheKeyFromTheTable() + { + var table = new GlobalInstanceTable(1); + var v1 = new Value(1); + + table.Add(1, v1); + Assert.AreEqual(1, table.count); + + table.Remove(1); + Assert.IsFalse(table.ContainsKey(1)); + Assert.AreEqual(0, table.count); + + GC.KeepAlive(v1); + } + #endregion + } +} diff --git a/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs.meta b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs.meta new file mode 100644 index 000000000..773e970db --- /dev/null +++ b/Packages/com.github.homuler.mediapipe/Tests/EditMode/Util/GlobalInstanceTableTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 736d39b2fb6e8d495abc80bd80e72bc3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: