From b8dbf8f40d2d1c2fc68db10057113f84f157f338 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Mon, 15 Mar 2010 19:08:10 +0000 Subject: [PATCH] Added an object Pool that can be used to recycle objects in order to avoid garbage buildup; wrote unit tests for the new pool class; added interface through which objects entering the pool can be instructed to revert their state git-svn-id: file:///srv/devel/repo-conversion/nusu@192 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (Xbox 360).csproj | 5 + Nuclex.Support.csproj | 5 + Source/Collections/IRecyclable.cs | 45 +++++++ Source/Collections/Pool.Test.cs | 108 ++++++++++++++++ Source/Collections/Pool.cs | 131 ++++++++++++++++++++ Source/Collections/PriorityItemPair.Test.cs | 4 +- 6 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 Source/Collections/IRecyclable.cs create mode 100644 Source/Collections/Pool.Test.cs create mode 100644 Source/Collections/Pool.cs diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj index 8bf4786..0bab4fd 100644 --- a/Nuclex.Support (Xbox 360).csproj +++ b/Nuclex.Support (Xbox 360).csproj @@ -85,6 +85,7 @@ Deque.cs + ItemEventArgs.cs @@ -109,6 +110,10 @@ ParentingCollection.cs + + + Pool.cs + PriorityItemPair.cs diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index 08cd1b5..06fc31a 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -71,6 +71,7 @@ Deque.cs + ItemEventArgs.cs @@ -95,6 +96,10 @@ ParentingCollection.cs + + + Pool.cs + PriorityItemPair.cs diff --git a/Source/Collections/IRecyclable.cs b/Source/Collections/IRecyclable.cs new file mode 100644 index 0000000..d001e0c --- /dev/null +++ b/Source/Collections/IRecyclable.cs @@ -0,0 +1,45 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; + +namespace Nuclex.Support.Collections { + + /// Allows an object to be returned to its initial state + /// + /// + /// This interface is typically implemented by objects which can be recycled + /// in order to avoid the construction overhead of a heavyweight class and to + /// eliminate garbage by reusing instances. + /// + /// + /// Recyclable objects should have a parameterless constructor and calling + /// their Recycle() method should bring them back into the state they were + /// in right after they had been constructed. + /// + /// + public interface IRecyclable { + + /// Returns the object to its initial state + void Recycle(); + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/Pool.Test.cs b/Source/Collections/Pool.Test.cs new file mode 100644 index 0000000..029570a --- /dev/null +++ b/Source/Collections/Pool.Test.cs @@ -0,0 +1,108 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if UNITTEST + +using System; +using System.Collections.Generic; + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Collections { + + /// Unit tests for the Pool class + [TestFixture] + public class PoolTest { + + #region class TestClass + + /// Used to test the pool + private class TestClass : IRecyclable { + + /// Returns the object to its initial state + public void Recycle() { + this.Recycled = true; + } + + /// Whether the instance has been recycled + public bool Recycled; + + } + + #endregion // class TestClass + + /// + /// Verifies that the pool can return newly constructed objects + /// + [Test] + public void TestGet() { + Pool pool = new Pool(); + Assert.IsNotNull(pool.Get()); + } + + /// + /// Verifies that the pool will return a recycled object if one is available + /// + [Test] + public void TestGetRecycled() { + Pool pool = new Pool(); + pool.Redeem(new TestClass()); + + TestClass test = pool.Get(); + Assert.IsTrue(test.Recycled); + } + + /// + /// Tests whether the pool can redeem objects that are no longer used + /// + [Test] + public void TestRedeem() { + Pool pool = new Pool(); + pool.Redeem(new TestClass()); + } + + /// + /// Tests whether the Recycle() method is called at the appropriate time + /// + [Test] + public void TestRecycle() { + Pool pool = new Pool(); + TestClass x = new TestClass(); + + Assert.IsFalse(x.Recycled); + pool.Redeem(x); + Assert.IsTrue(x.Recycled); + } + + /// Verifies that the pool's Capacity is applied correctly + [Test] + public void TestPoolSize() { + Pool pool = new Pool(123); + Assert.AreEqual(123, pool.Capacity); + pool.Capacity = 321; + Assert.AreEqual(321, pool.Capacity); + } + + } + +} // namespace Nuclex.Support.Collections + +#endif // UNITTEST diff --git a/Source/Collections/Pool.cs b/Source/Collections/Pool.cs new file mode 100644 index 0000000..a0b4a8a --- /dev/null +++ b/Source/Collections/Pool.cs @@ -0,0 +1,131 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nuclex.Support.Collections { + + /// Pool that recycles objects in order to avoid garbage build-up + /// Type of objects being pooled + /// + /// + /// Use this class to recycle objects instead of letting them become garbage, + /// creating new instances each time. The Pool class is designed to either be + /// used on its own or as a building block for a static class that wraps it. + /// + /// + /// Special care has to be taken to revert the entire state of a recycled + /// object when it is returned to the pool. For example, events will need to + /// have their subscriber lists emptied to avoid sending out events to the + /// wrong subscribers and accumulating more and more subscribers each time + /// they are reused. + /// + /// + /// To simplify such cleanup, pooled objects can implement the IRecyclable + /// interface. When an object is returned to the pool, the pool will + /// automatically call its IRecyclable.Recycle() method. + /// + /// + public class Pool where ItemType : class, new() { + + /// Default number of recyclable objects the pool will store + public const int DefaultPoolSize = 64; + + /// Initializes a new pool using the default capacity + public Pool() : this(DefaultPoolSize) { } + + /// Initializes a new pool using a user-specified capacity + /// Capacity of the pool + public Pool(int capacity) { + Capacity = capacity; + } + + /// + /// Returns a new or recycled instance of the types managed by the pool + /// + /// A new or recycled instance + public ItemType Get() { + lock(this) { + if(this.items.Count > 0) { + return this.items.Dequeue(); + } else { + return new ItemType(); + } + } + } + + /// + /// Redeems an instance that is no longer used to be recycled by the pool + /// + /// The instance that will be redeemed + public void Redeem(ItemType item) { + + // Call Recycle() when the object is redeemed (instead of when it leaves + // the pool again) in order to eliminate any references the object may hold + // to other objects. + callRecycleIfSupported(item); + + lock(this) { + if(this.items.Count < this.capacity) { + this.items.Enqueue(item); + } + } + } + + /// Number of objects the pool can retain + /// + /// Changing this value causes the pool to be emtpied. It is recommended that + /// you only read the pool's capacity, never change it. + /// + public int Capacity { + get { return this.capacity; } + set { + this.capacity = value; + this.items = new Queue(value); + } + } + + /// + /// Calls the Recycle() method on an objects if it implements + /// the IRecyclable interface + /// + /// + /// Object whose Recycle() method will be called if supported by the object + /// + private static void callRecycleIfSupported(ItemType item) { + IRecyclable recycleable = item as IRecyclable; + if(recycleable != null) { + recycleable.Recycle(); + } + } + + /// Objects being retained for recycling + private Queue items; + /// Capacity of the pool + /// + /// Required because the Queue class doesn't allow this value to be retrieved + /// + private int capacity; + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/PriorityItemPair.Test.cs b/Source/Collections/PriorityItemPair.Test.cs index 918f913..4df1e0f 100644 --- a/Source/Collections/PriorityItemPair.Test.cs +++ b/Source/Collections/PriorityItemPair.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.Collections.Generic; -#if UNITTEST - using NUnit.Framework; using NMock2;