File: MS\Internal\FontCache\BufferCache.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
//
//
// Description: BufferCache class implementation.
//
 
using System.Threading;
using MS.Internal.Text.TextInterface;
 
namespace MS.Internal.FontCache
{
    /// <summary>
    /// A static, thread safe array cache used to minimize heap allocations.
    /// </summary>
    /// <remarks>
    /// Cached arrays are not zero initialized, and they may be larger than
    /// the requested number of elements.
    /// </remarks>
    internal static class BufferCache
    {
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Attempts to release all allocated memory.  Has no effect if the cache
        /// is locked by another thread.
        /// </summary>
        internal static void Reset()
        {
            if (Interlocked.Increment(ref _mutex) == 1)
            {
                _buffers = null;
            }
            Interlocked.Decrement(ref _mutex);
        }
 
        /// <summary>
        /// Returns a GlyphMetrics[].
        /// </summary>
        /// <param name="length">
        /// Minimum number of elements in the array.
        /// </param>
        internal static GlyphMetrics[] GetGlyphMetrics(int length)
        {
            GlyphMetrics[] glyphMetrics = (GlyphMetrics[])GetBuffer(length, GlyphMetricsIndex);
 
            if (glyphMetrics == null)
            {
                glyphMetrics = new GlyphMetrics[length];
            }
 
            return glyphMetrics;
        }
 
        /// <summary>
        /// Releases a previously allocated GlyphMetrics[], possibly adding it
        /// to the cache.
        /// </summary>
        /// <remarks>
        /// It is not strictly necessary to call this method after receiving an
        /// array.  The penalty is the performance hit of doing a heap allocation
        /// on the next request if this method is not called.
        /// </remarks>
        internal static void ReleaseGlyphMetrics(GlyphMetrics[] glyphMetrics)
        {
            ReleaseBuffer(glyphMetrics, GlyphMetricsIndex);
        }
 
        /// <summary>
        /// Returns a ushort[].
        /// </summary>
        /// <param name="length">
        /// Minimum number of elements in the array.
        /// </param>
        internal static ushort[] GetUShorts(int length)
        {
            ushort[] ushorts = (ushort[])GetBuffer(length, UShortsIndex);
 
            if (ushorts == null)
            {
                ushorts = new ushort[length];
            }
 
            return ushorts;
        }
 
        /// <summary>
        /// Releases a previously allocated ushort[], possibly adding it
        /// to the cache.
        /// </summary>
        /// <remarks>
        /// It is not strictly necessary to call this method after receiving an
        /// array.  The penalty is the performance hit of doing a heap allocation
        /// on the next request if this method is not called.
        /// </remarks>
        internal static void ReleaseUShorts(ushort[] ushorts)
        {
            ReleaseBuffer(ushorts, UShortsIndex);
        }
 
        /// <summary>
        /// Returns a uint[].
        /// </summary>
        /// <param name="length">
        /// Minimum number of elements in the array.
        /// </param>
        internal static uint[] GetUInts(int length)
        {
            uint[] uints = (uint[])GetBuffer(length, UIntsIndex);
 
            if (uints == null)
            {
                uints = new uint[length];
            }
 
            return uints;
        }
 
        /// <summary>
        /// Releases a previously allocated uint[], possibly adding it
        /// to the cache.
        /// </summary>
        /// <remarks>
        /// It is not strictly necessary to call this method after receiving an
        /// array.  The penalty is the performance hit of doing a heap allocation
        /// on the next request if this method is not called.
        /// </remarks>
        internal static void ReleaseUInts(uint[] uints)
        {
            ReleaseBuffer(uints, UIntsIndex);
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        /// Searches for an array in the cache.
        /// </summary>
        /// <param name="length">
        /// Minimum number of elements in the array.
        /// </param>
        /// <param name="index">
        /// Specifies the type of array.
        /// </param>
        /// <returns>
        /// A matching array if present, otherwise null.
        /// </returns>
        private static Array GetBuffer(int length, int index)
        {
            Array buffer = null;
 
            if (Interlocked.Increment(ref _mutex) == 1)
            {
                if (_buffers != null &&
                    _buffers[index] != null &&
                    length <= _buffers[index].Length)
                {
                    buffer = _buffers[index];
                    _buffers[index] = null;
                }
            }
            Interlocked.Decrement(ref _mutex);
 
            return buffer;
        }
 
        /// <summary>
        /// Takes ownership of an array.
        /// </summary>
        /// <param name="buffer">
        /// The array.  May be null.
        /// </param>
        /// <param name="index">
        /// Specifies the type of array.
        /// </param>
        private static void ReleaseBuffer(Array buffer, int index)
        {
            if (buffer != null)
            {
                if (Interlocked.Increment(ref _mutex) == 1)
                {
                    if (_buffers == null)
                    {
                        _buffers = new Array[BuffersLength];
                    }
 
                    if (_buffers[index] == null ||
                        (_buffers[index].Length < buffer.Length && buffer.Length <= MaxBufferLength))
                    {
                        _buffers[index] = buffer;
                    }
                }
                Interlocked.Decrement(ref _mutex);
            }
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // Max number of elements in any cached array.  If a request if made for a larger array
        // it will always be allocated from the heap.
        private const int MaxBufferLength = 1024;
 
        // Indices in _buffers for each supported type.
        private const int GlyphMetricsIndex         = 0;
        private const int UIntsIndex                = 1;
        private const int UShortsIndex              = 2;
        private const int BuffersLength             = 3;
 
        // Guards access to _buffers.
        static private long _mutex;
 
        // Array of cached arrays, one bucker per supported type.
        // Currently, we cache just one array per type.  A more general cache would hold N byte arrays.
        // However, we don't currently have any scenarios that hold more than one array of the same type
        // or more than two arrays of different types at the same time, so it is difficult to justify
        // making the implementation more complex.  ComputeTypographyAvailabilities could benefit from
        // a more general cache (UnicodeRange.GetFullRange could use a cached array), but the savings
        // in profiled scenarios are small, ~16k for MSNBaml.exe.  If we find a more compelling
        // scenario a change might be worthwhile.
        static private Array[] _buffers;
 
        #endregion Private Fields
    }
}