// <copyright>
//   Copyright (c) 2012 Codefarts
//   All rights reserved.
//   contact@codefarts.com
//   http://www.codefarts.com
// </copyright>

namespace Codefarts.ObjectPooling
{
    using System;

    /// <summary>
    /// Provides a generic object pooling manager.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObjectPoolManager<T> where T : class
    {
        /// <summary>
        /// Holds a reference to a singleton instance.
        /// </summary>
        private static ObjectPoolManager<T> instance;

        /// <summary>
        /// Used to track the number of items that have been pushed to the pool.
        /// </summary>
        private int count;

        /// <summary>
        /// Holds the pooled item references.
        /// </summary>
        private T[] cachedItems = new T[10000];


        /// <summary>
        /// Gets or sets the creation callback.
        /// </summary>
        public Func<T> CreationCallback { get; set; }

        /// <summary>
        /// Gets the number of items in the pool.
        /// </summary>
        public int Count
        {
            get
            {
                return this.count;
            }
        }

        /// <summary>
        /// Pops a item from the pool.
        /// </summary>
        /// <returns>A pooled object reference.</returns>
        /// <exception cref="System.NullReferenceException">'CreationCallback' property must be set if you try to pop a item and there are no items available.</exception>
        public T Pop()
        {
            // lock here to prevent treading conflicts with array manipulation
            lock (this.cachedItems)
            {
                // check if there are any pooled objects
                if (this.count < 1)
                {
                    // check if creation callback is null
                    if (this.CreationCallback == null)
                    {
                        throw new NullReferenceException("'CreationCallback' property must be set if you try to pop a item and there are no items available.");
                    }

                    // there are no available objects so create a new one.
                    return this.CreationCallback();
                }

                // reduce the count
                this.count--;

                // retrieve the item and return it
                return this.cachedItems[this.count];
            }
        }

        /// <summary>
        /// Pushes the specified value.
        /// </summary>
        /// <param name="value">The value to push into the pool.</param>
        public void Push(T value)
        {
            // lock here to prevent treading conflicts with array manipulation
            lock (this.cachedItems)
            {
                // update the count
                this.count++;

                // if we need more room for storage increase the size of the cache array
                if (this.count > this.cachedItems.Length)
                {
                    Array.Resize(ref this.cachedItems, this.cachedItems.Length * 2);
                }

                // store the value 
                this.cachedItems[this.count - 1] = value;
            }
        }

        /// <summary>
        /// Gets the singleton instance of the class.
        /// </summary>
        public static ObjectPoolManager<T> Instance
        {
            get
            {
                return instance ?? (instance = new ObjectPoolManager<T>());
            }
        }
    }
}

And some unit tests to go along with it.

// <copyright>
//   Copyright (c) 2012 Codefarts
//   All rights reserved.
//   contact@codefarts.com
//   http://www.codefarts.com
// </copyright>

namespace Codefarts.Tests.ObjectPooling
{
    using System;

    using Codefarts.ObjectPooling;

    using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;

    [TestClass]
    public class ObjectPoolingManagerTests
    {
        ObjectPoolManager<TestObject> manager;

        [TestInitialize]
        public void Setup()
        {
            this.manager = new ObjectPoolManager<TestObject>();
        }

        [TestCleanup]
        public void Cleanup()
        {
            this.manager = null;
        }

        public class TestObject
        {
            public string stringValue;
            public int intValue;
        }

        [TestMethod]
        public void Pop_With_Empty_Pool_NoCallback()
        {
            try
            {
                var item = this.manager.Pop();
                Assert.IsNotNull(item);
            }
            catch (Exception ex)
            {
                Assert.IsTrue(ex is NullReferenceException);
            }
        }

        [TestMethod]
        public void Pop_With_Empty_Pool_WithCallback()
        {
            try
            {
                this.manager.CreationCallback = this.Callback;
                var item = this.manager.Pop();
                Assert.IsNotNull(item);
                Assert.AreEqual(0, item.intValue);
                Assert.AreEqual(null, item.stringValue);
            }
            catch (Exception ex)
            {
                Assert.IsTrue(ex is NullReferenceException);
            }
        }

        private TestObject Callback()
        {
            return new TestObject();
        }

        [TestMethod]
        public void Push_Object()
        {
            try
            {
                Assert.AreEqual(0, this.manager.Count);
                this.manager.Push(new TestObject());
                Assert.AreEqual(1, this.manager.Count);
            }
            catch (Exception ex)
            {
                Assert.Fail(ex.ToString());
            }
        }

        [TestMethod]
        public void Push_Pop_Objects()
        {
            try
            {
                Assert.AreEqual(0, this.manager.Count);
                for (var i = 0; i < 3; i++)
                {
                    this.manager.Push(new TestObject() { stringValue = "Item" + i, intValue = i });
                }

                Assert.AreEqual(3, this.manager.Count);

                for (var i = 3 - 1; i >= 0; i--)
                {
                    var item = this.manager.Pop();
                    Assert.AreEqual(i, item.intValue);
                    Assert.AreEqual("Item" + i, item.stringValue);
                }

                Assert.AreEqual(0, this.manager.Count);
            }
            catch (Exception ex)
            {
                Assert.Fail(ex.ToString());
            }
        }
    }
}

Source (adapted for GenericImage) -> http://stackoverflow.com/a/11176961/341706

/// <summary>
        /// Rotates the image.
        /// </summary>
        /// <param name="image">
        /// The source image to rotate.
        /// </param>
        /// <param name="angle">
        /// The angle in degrees to rotate the image.
        /// </param>
        /// <param name="centerX">The x position of the rotation axis.</param>
        /// <param name="centerY">The y position of the rotation axis.</param>
        /// <returns>
        /// Returns a new rotated image.
        /// </returns>
        public static GenericImage<T> Rotate<T>(this GenericImage<T> image, int centerX, int centerY, float angle)
        {
            // source (adapted for GenericImage) -> http://stackoverflow.com/a/11176961/341706
            var radians = (Math.PI / 180) * angle;
            var cos = Math.Cos(radians);
            var sin = Math.Sin(radians);
            var newImage = new GenericImage<T>(image.Width, image.Height);

            for (var x = 0; x < image.Width; x++)
                for (var y = 0; y < image.Height; y++)
                {
                    var m = x - centerX;
                    var n = y - centerY;
                    var j = ((int)(m * cos + n * sin)) + centerX;
                    var k = ((int)(n * cos - m * sin)) + centerY;
                    if (j >= 0 && j < image.Width && k >= 0 && k < image.Height)
                    {
                        newImage[x, y] = image[j, k];
                    }
                }

            return newImage;
        }

Source -> http://stackoverflow.com/questions/10322341/simple-algorithm-for-drawing-filled-ellipse-in-c-c

/// <summary>
        /// Draws a filled Ellipse.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="x">
        /// The x left most position of the ellipse.
        /// </param>
        /// <param name="y">
        /// The y top most position of the ellipse.
        /// </param>
        /// <param name="width">
        /// The width of the ellipse.
        /// </param>
        /// <param name="height">
        /// The height of the ellipse.
        /// </param>
        /// <param name="color">
        /// The color to use.
        /// </param>
        public static void FillEllipse<T>(this GenericImage<T> image, int x, int y, int width, int height, T color)
        {
            width = width / 2;
            height = height / 2;
            var centerX = x + width;
            var centerY = y + height;

            // source -> http://stackoverflow.com/questions/10322341/simple-algorithm-for-drawing-filled-ellipse-in-c-c
            for (var indexY = -height; indexY <= height; indexY++)
            {
                for (var indexX = -width; indexX <= width; indexX++)
                {
                    var dx = indexX / (double)width;
                    var dy = indexY / (double)height;
                    if (dx * dx + dy * dy <= 1)
                    {
                        image[centerX + indexX, centerY + indexY] = color;
                    }
                }
            }
        }

Source (converted from C++) -> http://www.dailyfreecode.com/Code/draw-ellipse-midpoint-ellipse-algorithm-714.aspx

/// <summary>
        /// Draws a Ellipse.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="centerX">
        /// The x center position of the circle.
        /// </param>
        /// <param name="centerY">
        /// The y center position of the circle.
        /// </param>
        /// <param name="width">
        /// The width of the Ellipse.
        /// </param>
        /// <param name="height">
        /// The height of the Ellipse.
        /// </param>
        /// <param name="color">
        /// The color to use.
        /// </param>
        public static void DrawEllipse<T>(this GenericImage<T> image, int centerX, int centerY, int width, int height, T color)
        {
            // source (converted from C++) -> http://www.dailyfreecode.com/Code/draw-ellipse-midpoint-ellipse-algorithm-714.aspx
            float aa = (width * width);
            float bb = (height * height);
            float aa2 = (aa * 2);
            float bb2 = (bb * 2);

            float x = 0;
            float y = height;

            float fx = 0;
            float fy = (aa2 * height);

            float p = (int)(bb - (aa * height) + (0.25 * aa) + 0.5);

            image[(int)(centerX + x), (int)(centerY + y)] = color;
            image[(int)(centerX + x), (int)(centerY - y)] = color;
            image[(int)(centerX - x), (int)(centerY - y)] = color;
            image[(int)(centerX - x), (int)(centerY + y)] = color;

            while (fx < fy)
            {
                x++;
                fx += bb2;

                if (p < 0) p += (fx + bb);

                else
                {
                    y--;
                    fy -= aa2;
                    p += (fx + bb - fy);
                }

                image[(int)(centerX + x), (int)(centerY + y)] = color;
                image[(int)(centerX + x), (int)(centerY - y)] = color;
                image[(int)(centerX - x), (int)(centerY - y)] = color;
                image[(int)(centerX - x), (int)(centerY + y)] = color;
            }

            p = (int)((bb * (x + 0.5) * (x + 0.5)) + (aa * (y - 1) * (y - 1)) - (aa * bb) + 0.5);

            while (y > 0)
            {
                y--;
                fy -= aa2;

                if (p >= 0) p += (aa - fy);

                else
                {
                    x++;
                    fx += bb2;
                    p += (fx + aa - fy);
                }

                image[(int)(centerX + x), (int)(centerY + y)] = color;
                image[(int)(centerX + x), (int)(centerY - y)] = color;
                image[(int)(centerX - x), (int)(centerY - y)] = color;
                image[(int)(centerX - x), (int)(centerY + y)] = color;
            }
        }


Source -> http://www.dailyfreecode.com/code/fill-circle-scan-line-circle-fill-675.aspx

/// <summary>
        /// Fills a circle.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="centerX">
        /// The x center position of the circle.
        /// </param>
        /// <param name="centerY">
        /// The y center position of the circle.
        /// </param>
        /// <param name="radius">
        /// The radius of the circle.
        /// </param>  
        /// <param name="color">
        /// The color to use.
        /// </param>    
        public static void FillCircle<T>(this GenericImage<T> image, int centerX, int centerY, int radius, T color)
        {
            // source -> http://www.dailyfreecode.com/code/fill-circle-scan-line-circle-fill-675.aspx
            int x1;
            int x2;

            var counter = (centerY + radius);

            for (var count = (centerY - radius); count <= counter; count++)
            {
                x1 = (int)(centerX + Math.Sqrt((radius * radius) - ((count - centerY) * (count - centerY))) + 0.5);
                x2 = (int)(centerX - Math.Sqrt((radius * radius) - ((count - centerY) * (count - centerY))) + 0.5);

                image.DrawLine(x1, count, x2, count, color);
            }
        }


Source (converted from Java) -> http://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#Java

/// <summary>
        /// Draws a circle.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="centerX">
        /// The x center position of the circle.
        /// </param>
        /// <param name="centerY">
        /// The y center position of the circle.
        /// </param>
        /// <param name="radius">
        /// The radius of the circle.
        /// </param>  
        /// <param name="color">
        /// The color to use.
        /// </param>    
        public static void DrawCircle<T>(this GenericImage<T> image, int centerX, int centerY, int radius, T color)
        {
            // source (converted from Java) -> http://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#Java
            var d = (5 - radius * 4) / 4;
            var x = 0;
            var y = radius;

            do
            {
                // ensure index is in range before setting (depends on your image implementation)
                // in this case we check if the pixel location is within the bounds of the image before setting the pixel
                image[centerX + x, centerY + y] = color;
                image[centerX + x, centerY - y] = color;
                image[centerX - x, centerY + y] = color;
                image[centerX - x, centerY - y] = color;
                image[centerX + y, centerY + x] = color;
                image[centerX + y, centerY - x] = color;
                image[centerX - y, centerY + x] = color;
                image[centerX - y, centerY - x] = color;
                if (d < 0)
                {
                    d += 2 * x + 1;
                }
                else
                {
                    d += 2 * (x - y) + 1;
                    y--;
                }
                x++;
            } while (x <= y);
        }


Source (converted from C) -> http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C

/// <summary>
        /// Draws a line on an <see cref="GenericImage{T}"/>.
        /// </summary>
        /// <param name="image">
        /// The destination image.
        /// </param>
        /// <param name="x1">
        /// The x position for the start of the line.
        /// </param>
        /// <param name="y1">
        /// The y position for the start of the line.
        /// </param>
        /// <param name="x2">
        /// The x position for the end of the line.
        /// </param>
        /// <param name="y2">
        /// The y position for the end of the line.
        /// </param>
        /// <param name="color">
        /// The color that the line will be drawn with.
        /// </param>
        public static void DrawLine<T>(this GenericImage<T> image, int x1, int y1, int x2, int y2, T color)
        {
            // source (converted from C) -> http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C
            int dx = Math.Abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
            int dy = Math.Abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
            int err = (dx > dy ? dx : -dy) / 2;

            for (; ; )
            {
                image[x1, y1] = color;
                if (x1 == x2 && y1 == y2) break;
                int e2 = err;
                if (e2 > -dx) { err -= dy; x1 += sx; }
                if (e2 < dy) { err += dx; y1 += sy; }
            }
        }

Below is a simple bit of code that will attempt to retrieve the asset path of the prefab stored on disk. The method will also try to walk up the prefab hierarchy if the prefab parameter appears to be a instance of the original. 

/// <summary>
/// Gets the source path to a prefab if any.
/// </summary>
/// <param name="prefab">The prefab reference to get the asset path for.</param>
/// <returns>Returns a asset path for a prefab.</returns>
/// <remarks>This method will attempt to find the source asset of the given <see cref="UnityEngine.Object"/> by 
/// walking up the parent prefab hierarchy.</remarks>
public static string GetSourcePrefab(UnityEngine.Object prefab)
{
    // if no prefab specified then return null
    if (prefab == null)
    {
        return null;
    }

    // attempt to get the path
    var path = AssetDatabase.GetAssetPath(prefab);

    // if no path returned it may be an instantiated prefab so try to get the parent prefab
    while (String.IsNullOrEmpty(path))
    {
        // try parent prefab
        var parent = PrefabUtility.GetPrefabParent(prefab);
                
        // no parent so must be generated through code so just exit loop
        if (parent == null)
        {
            break;
        }

        // attempt to get path for 
        path = AssetDatabase.GetAssetPath(parent);

        // set prefab reference to parent for next loop
        prefab = parent;
    }

    // return the path if any
    return path;
}

If you have some editor scripts and you want to detect weather or not unity has compiled your scripts you can try this simple method.

Created a private class within your editor window and declare a field of that private class.

public class YourEditor : EditorWindow
{
    private RecompileClass recompile;

    private class RecompileClass
    {  
    }
}

In the OnGUI method (or where appropriate) perform a check to see if the field is null. If it’s null you can assume that unity compiled your scripts again so do what you need to do is create a new instance of the private class and assign it to the field.

public class YourEditor : EditorWindow
{
    private RecompileClass recompile;
      
    private class RecompileClass
    {
    }

    public void OnGUI()
    {
        // check if recompile variable is null 
        if (this.recompile == null  )
        {
            // if yes assume recompile then create a reference to a recompile class 
            this.recompile = new RecompileClass();

            // do what you need to do here after detecting a recompile  
        }

        // do regular stuff here
    }
}

The next time unity recompiles your scripts the field will loose it’s reference and will be null again.


    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Implements a generic equality comparer that uses a callback to perform the comparison.
    /// </summary>
    /// <typeparam name="T">The type we want to compare.</typeparam>
    public class EqualityComparerCallback<T> : IEqualityComparer<T>
    {
        /// <summary>
        /// Holds a reference to the callback that handles comparisons.
        /// </summary>
        private readonly Func<T, T, bool> function;

        /// <summary>
        /// Holds a reference to the callback used to calculate hash codes.
        /// </summary>
        private Func<T, int> hashFunction;
      
        /// <summary>
        /// Initializes a new instance of the <see cref="EqualityComparerCallback{T}"/> class.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Throws a <see cref="ArgumentNullException"/> if the <see cref="func"/> parameter is null.
        /// </exception>
        public EqualityComparerCallback(Func<T, T, bool> func)
        {
            if (func == null)
            {
                throw new ArgumentNullException("func");
            }

            this.function = func;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EqualityComparerCallback{T}"/> class.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <param name="hash">
        /// The callback function that will return the hash code.
        /// </param>  
        public EqualityComparerCallback(Func<T, T, bool> func, Func<T, int> hash)
            : this(func)
        {
            this.hashFunction = hash;
        }

        /// <summary>
        /// Gets or sets the callback function to use to compute the hash.
        /// </summary>
        public Func<T, int> HashFunction
        {
            get
            {
                return this.hashFunction;
            }

            set
            {
                this.hashFunction = value;
            }
        }
                  
        /// <summary>
        /// Provides a static method for returning a <see cref="EqualityComparerCallback{T}"/> type.
        /// </summary>
        /// <param name="func">
        /// The callback function that will do the comparing.
        /// </param>
        /// <returns>Returns a new instance of a <see cref="EqualityComparerCallback{T}"/> type.</returns>
        public static IEqualityComparer<T> Compare(Func<T, T, bool> func)
        {
            return new EqualityComparerCallback<T>(func);
        }

        /// <summary>
        /// Determines whether the specified object instances are considered equal.
        /// </summary>
        /// <param name="x">The first object to compare.</param>
        /// <param name="y">The second object to compare.</param>
        /// <returns>True if the objects are considered equal otherwise, false. If both objA and objB are null, the method returns true.</returns>
        public bool Equals(T x, T y)
        {
            return this.function(x, y);
        }    

        /// <summary>
        /// The get hash code for a object.
        /// </summary>
        /// <param name="obj">
        /// The object whose hash code will be computed.          
        /// </param>
        /// <returns>
        /// Returns a <see cref="int"/> value representing the hash code.
        /// </returns>
        /// <remarks>If no callback function was set for the <see cref="HashFunction"/> the object default <see cref="GetHashCode"/> method will be used.</remarks>
        public int GetHashCode(T obj)
        {
            if (this.hashFunction == null)
            {
                return obj.GetHashCode();
            }

            return this.hashFunction(obj);
        }
    }

A usage scenario

    // build a list of unique categories
    var list = new List<string>();
    foreach (var rule in this.rules)
    {
        if (string.IsNullOrEmpty(rule.Category ?? string.Empty))
        {
            continue;
        }

        // check if already exists
        if (list.Contains(rule.Category, new EqualityComparerCallback<string>((a, b) => a.Trim() == b.Trim())))
        {
            continue;
        }
    }
    return list.ToArray();

Created by: X

Just another personal website in this crazy online world

Name of author Dean Lunz (aka Created by: X)
Computer programming nerd, and tech geek.
About Me -- Resume