Website may be up and down over next few months. I'm currently doing a complete overhaul of everything. Going back to simple individual .htm pages, new overall site theme, sanitizing and cleaning up html of all pages and blog posts, attempting to implement a new tooling and publishing system etc etc.

I needed a quick file caching solution and here is what I came up with …

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Provides a file model used by the <see cref="FileCache"/> type.
    /// </summary>
    public class FileModel
    {
        /// <summary>
        /// Gets or sets information about the file.
        /// </summary>
        public FileInfo Info { get; set; }

        /// <summary>
        /// Gets or sets the last time that the file had it's contents cached.
        /// </summary>
        public DateTime LastUpdate { get; set; }

        /// <summary>
        /// Gets or sets the file content for the file.
        /// </summary>
        public StringBuilder  Data { get; set; }
    }

    /// <summary>
    /// Provides a caching for file content based on file extensions.
    /// </summary>
    public class FileCache
    {
        /// <summary>
        /// Holds a singleton instance of a <see cref="FileCache"/> type.
        /// </summary>
        private static FileCache singleton;

        /// <summary>
        /// Used to hold the contents of the files.
        /// </summary>
        private readonly Dictionary<string, FileModel> files;

        /// <summary>
        /// Holds the list of extensions for files that will have there contents cached.
        /// </summary>
        private readonly List<string> extensions;

        /// <summary>
        /// Gets or sets a time value used to determine if a files content should be read again.
        /// </summary>
        public TimeSpan CacheTime { get; set; }

        /// <summary>
        /// Gets a list of file extension that will have there contents cached.
        /// </summary>
        public List<string> Extensions
        {
            get
            {
                return this.extensions;
            }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public FileCache()
        {
            this.files = new Dictionary<string, FileModel>();
            this.extensions = new List<string>();
            this.extensions.AddRange(new[] { ".htm" });
            this.CacheTime = TimeSpan.FromSeconds(2);
        }

        /// <summary>
        /// Provides a helper method for retrieving a file.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <returns>Returns a <see cref="FileModel"/> type containing information about the file.</returns>
        public static FileModel GetFile(string path)
        {
            return Instance.Get(path);
        }

        /// <summary>
        /// Provides a helper method for retrieving a file.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <returns>Returns a <see cref="FileModel"/> type containing information about the file.</returns>
        public FileModel Get(string path)
        {
            // if file does not exit just return null
            if (!File.Exists(path))
            {
                return null;
            }

            // check if file already exists in the cache
            FileModel model;
            if (this.files.ContainsKey(path))
            {
                // get existing entry
                model = this.files[path];
            }
            else
            {
                // add new entry
                model = new FileModel { LastUpdate = DateTime.Now, Info = new FileInfo(path) };
                this.files.Add(path, model);
            }

            // check to cache file data
            if (this.extensions.Contains(model.Info.Extension))
            {
                // not data has been read yet OR (the current time has surpassed the last update time plus the cache time AND 
                // the current time is greater then the last write time  ) we can read the contents of the file
                if (model.Data == null || (DateTime.Now > model.LastUpdate + this.CacheTime && DateTime.Now > model.Info.LastWriteTime))
                {
                    model.Data = new StringBuilder(File.ReadAllText(model.Info.FullName));
                }
            }

            // return the modal data
            return model;
        }

        /// <summary>
        /// Gets a singleton instance of a <see cref="FileCache"/> type.
        /// </summary>
        public static FileCache Instance
        {
            get
            {
                return singleton ?? (singleton = new FileCache());
            }
        }
    }

Below is a code sample containing drawing methods for drawing check boxes or any control type in a grid based layout similar to the SelectionGrid.

 
    using System;

    using UnityEngine;

    public class ControlGrid
    {
        /// <summary>
        /// Draw a grid of check boxes similar to SelectionGrid.
        /// </summary>
        /// <param name="checkedValues">Specifies the checked values of the check boxes.</param>
        /// <param name="text">The content for each individual check box.</param>
        /// <param name="columns">The number of columns in the grid.</param>
        /// <param name="style">The style to be applied to each check box.</param>
        /// <param name="options">Specifies layout options to be applied to each check box.</param>
        /// <returns>Returns the checked state for each check box.</returns>
        /// <remarks><p>Check boxes are drawn top to bottom, left to right.</p>  </remarks>
        /// <exception cref="IndexOutOfRangeException">Can occur if the size of the array is too small.</exception>
        public static bool[] DrawCheckBoxGrid(bool[] checkedValues, string[] text, int columns, GUIStyle style, params GUILayoutOption[] options)
        {
            // convert string content into gui content
            var content = new GUIContent[text.Length];
            for (var i = 0; i < content.Length; i++)
            {
                content[i] = new GUIContent(text[i]);
            }
            return DrawCheckBoxGrid(checkedValues, content, columns, style, options);
        }

        /// <summary>
        /// Draw a grid of check boxes similar to SelectionGrid.
        /// </summary>
        /// <param name="checkedValues">Specifies the checked values of the check boxes.</param>
        /// <param name="textures">The content for each individual check box.</param>
        /// <param name="columns">The number of columns in the grid.</param>
        /// <param name="style">The style to be applied to each check box.</param>
        /// <param name="options">Specifies layout options to be applied to each check box.</param>
        /// <returns>Returns the checked state for each check box.</returns>
        /// <remarks><p>Check boxes are drawn top to bottom, left to right.</p>  </remarks>
        /// <exception cref="IndexOutOfRangeException">Can occur if the size of the array is too small.</exception>
        public static bool[] DrawCheckBoxGrid(bool[] checkedValues, Texture2D[] textures, int columns, GUIStyle style, params GUILayoutOption[] options)
        {
            // convert texture content into gui content
            var content = new GUIContent[textures.Length];
            for (var i = 0; i < content.Length; i++)
            {
                content[i] = new GUIContent(string.Empty, textures[i]);
            }
            return DrawCheckBoxGrid(checkedValues, content, columns, style, options);
        }

        /// <summary>
        /// Draw a grid of check boxes similar to SelectionGrid.
        /// </summary>
        /// <param name="checkedValues">Specifies the checked values of the check boxes.</param>
        /// <param name="content">The content for each individual check box.</param>
        /// <param name="columns">The number of columns in the grid.</param>
        /// <param name="style">The style to be applied to each check box.</param>
        /// <param name="options">Specifies layout options to be applied to each check box.</param>
        /// <returns>Returns the checked state for each check box.</returns>
        /// <remarks><p>Check boxes are drawn top to bottom, left to right.</p>  </remarks>
        /// <exception cref="IndexOutOfRangeException">Can occur if the size of the array is too small.</exception>
        public static bool[] DrawCheckBoxGrid(bool[] checkedValues, GUIContent[] content, int columns, GUIStyle style, params GUILayoutOption[] options)
        {
            return DrawGenericGrid((e, i, s, o) => GUILayout.Toggle(e[i], content[i], style, options), checkedValues, content, columns, style, options);
        }

        /// <summary>
        /// Draw a grid of controls using a draw callback similar to SelectionGrid.
        /// </summary>
        /// <param name="drawCallback">Specifies a draw callback that is responsible for performing the actual drawing.</param>
        /// <param name="values">Specifies the values of the controls.</param>
        /// <param name="content">The content for each individual control.</param>
        /// <param name="columns">The number of columns in the grid.</param>
        /// <param name="style">The style to be applied to each control.</param>
        /// <param name="options">Specifies layout options to be applied to each control.</param>
        /// <returns>Returns the value for each control.</returns>
        /// <remarks><p>Controls are drawn top to bottom, left to right.</p>  </remarks>
        /// <exception cref="IndexOutOfRangeException">Can occur if the size of the array is too small.</exception>
        /// <exception cref="ArgumentNullException">If the drawCallback is null.</exception>
        public static T[] DrawGenericGrid<T>(Func<T[], int, GUIStyle, GUILayoutOption[], T> drawCallback, T[] values, GUIContent[] content, int columns, GUIStyle style, params GUILayoutOption[] options)
        {
            if (drawCallback == null)
            {
                throw new ArgumentNullException("drawCallback");
            }

            GUILayout.BeginVertical();
            var rowIndex = 0;
            var columnIndex = 0;
            var index = rowIndex * columns + columnIndex;

            GUILayout.BeginHorizontal();
            while (index < values.Length)
            {
                // draw control
                values[index] = drawCallback(values, index, style, options);

                // move to next column
                columnIndex++;

                // if passed max columns move down to next row and set to first column
                if (columnIndex > columns - 1)
                {
                    columnIndex = 0;
                    rowIndex++;

                    // remember to start a new horizontal layout
                    GUILayout.EndHorizontal();
                    GUILayout.BeginHorizontal();
                }

                // re-calculate the index
                index = rowIndex * columns + columnIndex;
            }
            GUILayout.EndHorizontal();

            GUILayout.EndVertical();
            return values;
        }
    } 

Here is a simple example of how to use the draw methods

var emptyContent = new[] { GUIContent.none, GUIContent.none, GUIContent.none, 
                            GUIContent.none, GUIContent.none, GUIContent.none, 
                            GUIContent.none, GUIContent.none, GUIContent.none };

GUILayout.BeginVertical(GUILayout.MinWidth(128));
rule.IgnoreUpper = GUILayout.Toggle(rule.IgnoreUpper, "Upper Neighbors");
rule.NeighborsUpper = ControlGrid.DrawCheckBoxGrid(rule.NeighborsUpper, emptyContent, 3, GUI.skin.button, GUILayout.MaxWidth(32), GUILayout.MaxHeight(32));
GUILayout.EndVertical();

ControlGrid


MEFHelpers provides a helper class that provides a static method to make MEF composition easier.

    /// <summary>
    /// Provides a helper class for MEF composition.
    /// </summary>
    public sealed class MEFHelpers
    {
        /// <summary>
        /// Composes MEF parts.
        /// </summary>
        /// <param name="parts">The composable object parts.</param>
        public static void Compose(params object[] parts)
        {
            Compose(parts, new string[0]);
        }

        /// <summary>
        /// Composes MEF parts.
        /// </summary>
        /// <param name="searchFolders">Provides a series of search folders to search for *.dll files.</param>
        /// <param name="parts">The composable object parts.</param>
        public static void Compose(IEnumerable<string> searchFolders, params object[] parts)
        {
            // setup composition container
            var catalog = new AggregateCatalog();

            // check if folders were specified
            if (searchFolders != null)
            {
                // add search folders
                foreach (var folder in searchFolders.Where(System.IO.Directory.Exists))
                {
                    catalog.Catalogs.Add(new DirectoryCatalog(folder, "*.dll"));
                }
            }

            // compose and create plug ins
            var composer = new CompositionContainer(catalog);
            composer.ComposeParts(parts);
        }
    }

A usage scenario is provided below

    public class KissCSMEFComposer  
    {
        [ImportMany(typeof(IProcessor))]
        public List<Lazy<IProcessor>> StringProcessors;

        [ImportMany(typeof(ICommandRepository))]
        public List<Lazy<ICommandRepository>> CommandRepository;
    }

    var composer = new KissCSMEFComposer();
    // try to connect with MEF types
    try
    {
        MEFHelpers.Compose(this.SearchFolders, composer);
    }
    catch (Exception ex)
    {
        // ERR: handle error
    }

    // register string processors
    foreach (var processor in composer.StringProcessors)
    {
        StringProcessorRepository.Instance.Register(processor.Value);
    }

    // etc ...

You can set the mouse cursor for a UI control by using EditorGUIUtility.AddCursorRect and specifying a MouseCursor enum.

if (GUILayout.Button("Add"))
{

}
            
// show the "Link" cursor when the mouse is hovering over this rectangle.
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);


I was recently trying to get my Windows 7 Home Premium 64bit laptop to see the network drive on my Asus RT-N56U router with no success. It was a bit frustrating because my Windows 8 Pro desktop could see the drive on the router without any issues. Some search results talked about changing registry settings or changing values under the Group Policy Editor but these did not apply to Windows 7 Home Premium.

Ultimately I came across this one post here that suggested to add an entry to the hosts file. After adding “192.168.1.1  RT-N56U” to the bottom of the list poof I could now see a network place called “RT-N56U” inside file explorer. Wooty Woot !


The code below can be used to log performance of your code.

    using System.Collections.Generic;
    using System.Diagnostics;

    /// <summary>
    /// Provides a simple performance testing class that utilizes <see cref="Stopwatch"/>.
    /// </summary>
    /// <typeparam name="T">The type that will be used as the indexer.</typeparam>
    public class PerformanceTesting<T>
    {
        /// <summary>
        /// Provides a model that contains timer information.
        /// </summary>
        private class TimerModel
        {
            /// <summary>
            /// Used to record performance timings.
            /// </summary>
            public readonly Stopwatch Timer;

            /// <summary>
            /// Used to record how many times the <see cref="Timer"/> has been started.
            /// </summary>
            public int Count;

            /// <summary>
            /// Used to store the enabled state.
            /// </summary>
            private bool enabled;

            /// <summary>
            /// Gets or sets a value whether or not this timer if enabled.
            /// </summary>
            /// <remarks>If <see cref="Timer"/> has been started and Enabled is set to false the timer will be stopped.</remarks>
            public bool Enabled
            {
                get
                {
                    return this.enabled;
                }

                set
                {
                    this.enabled = value;

                    // be sure to stop the timer if disabled
                    if (!value && this.Timer.IsRunning)
                    {
                        this.Timer.Stop();
                    }
                }
            }

            /// <summary>
            /// Default constructor.
            /// </summary>
            public TimerModel()
            {
                this.Timer = new Stopwatch();
                this.enabled = true;
            }
        }

        /// <summary>
        /// Holds a reference to a singleton instance.
        /// </summary>
        private static PerformanceTesting<T> singleton;

        /// <summary>
        /// Used to store various timer information.
        /// </summary>
        private readonly Dictionary<T, TimerModel> timers;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public PerformanceTesting()
        {
            this.timers = new Dictionary<T, TimerModel>();
        }

        /// <summary>
        /// Creates a new timer.
        /// </summary>
        /// <param name="key">The unique key for the timer.</param>
        /// <remarks>It is best to create the timer at the start of the application getting it ready for use.</remarks>
        public void Create(T key)
        {
            this.timers.Add(key, new TimerModel());
        }

        /// <summary>
        /// Creates a new timer for each key.
        /// </summary>
        /// <param name="keys">The unique keys for the timers.</param>
        /// <remarks>It is best to create the timer at the start of the application getting it ready for use.</remarks>
        public void Create(T[] keys)
        {
            foreach (var key in keys)
            {
                this.timers.Add(key, new TimerModel());
            }
        }

        /// <summary>
        /// Returns the total ticks that this timer has observed.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <returns>Returns a time value in ticks.</returns>
        public long TotalTicks(T key)
        {
            var model = this.timers[key];
            return model.Timer.ElapsedTicks;
        }

        /// <summary>
        /// Returns the total ticks that this timer has observed.
        /// </summary>
        /// <param name="keys">The keys to the timers information.</param>
        /// <returns>Returns the sum of the time values in ticks.</returns>
        public long TotalTicks(T[] keys)
        {
            long total = 0;
            foreach (var key in keys)
            {
                var model = this.timers[key];
                total += model.Timer.ElapsedTicks;
            }

            return total;
        }

        /// <summary>
        /// Gets the start count.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <returns>The number of times the timer has started.</returns>
        public int GetStartCount(T key)
        {
            var model = this.timers[key];
            return model.Count;
        }

        /// <summary>
        /// Gets the start count for specified timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <returns>The sum total of times all specified the timers have started.</returns>
        public int GetStartCount(T[] keys)
        {
            int total = 0;
            foreach (var key in keys)
            {
                var model = this.timers[key];
                total += model.Count;
            }

            return total;
        }

        /// <summary>
        /// Returns the total ticks that this timer has observed.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <returns>Returns a time value in milliseconds.</returns>
        public long TotalMilliseconds(T key)
        {
            var model = this.timers[key];
            return model.Timer.ElapsedMilliseconds;
        }

        /// <summary>
        /// Returns the total ticks that this timer has observed.
        /// </summary>
        /// <param name="keys">The keys to the timers information.</param>
        /// <returns>Returns the sum of the time values in milliseconds.</returns>
        public long TotalMilliseconds(T[] keys)
        {
            long total = 0;
            foreach (var key in keys)
            {
                var model = this.timers[key];
                total += model.Timer.ElapsedMilliseconds;
            }

            return total;
        }

        /// <summary>
        /// Calculates the average time in ticks that elapsed while this timer was recording.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <returns>Returns the average time in ticks that elapsed between each start and stop.</returns>
        public long AverageTicks(T key)
        {
            var model = this.timers[key];
            return model.Timer.ElapsedTicks / model.Count;
        }

        /// <summary>
        /// Calculates the total average time in ticks that elapsed while the specified timers were recording.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <returns>Returns the total average time in ticks that elapsed between each start and stop for all the specified timers.</returns>
        public long AverageTicks(T[] keys)
        {
            long total = 0;
            var count = 0;
            foreach (var key in keys)
            {
                var model = this.timers[key];
                total += model.Timer.ElapsedTicks;
                count += model.Count;
            }

            return total / count;
        }

        /// <summary>
        /// Calculates the average time in milliseconds that elapsed while this timer was recording.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <returns>Returns the average time in milliseconds that elapsed between each start and stop.</returns>
        public long AverageMilliseconds(T key)
        {
            var model = this.timers[key];
            if (model.Count == 0)
            {
                return 0;
            }

            return model.Timer.ElapsedMilliseconds / model.Count;
        }

        /// <summary>
        /// Calculates the total average time in milliseconds that elapsed while the specified timers were recording.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <returns>Returns the total average time in milliseconds that elapsed between each start and stop for all the specified timers.</returns>
        public long AverageMilliseconds(T[] keys)
        {
            long total = 0;
            var count = 0;
            foreach (var key in keys)
            {
                var model = this.timers[key];
                total += model.Timer.ElapsedMilliseconds;
                count += model.Count;
            }

            if (count == 0)
            {
                return 0;
            }

            return total / count;
        }

        /// <summary>
        /// Removes the specified timers.
        /// </summary>
        /// <param name="keys">The keys to the timers that will be removed.</param>
        public void Remove(T[] keys)
        {
            foreach (var key in keys)
            {
                this.timers.Remove(key);
            }
        }

        /// <summary>
        /// Removes a timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        public void Remove(T key)
        {
            this.timers.Remove(key);
        }

        /// <summary>
        /// Resets all the timers.
        /// </summary>
        public void ResetAll()
        {
            foreach (var pair in this.timers)
            {
                pair.Value.Timer.Reset();
            }
        }

        /// <summary>
        /// Resets all the timers.
        /// </summary>
        /// <param name="resetCounts">If true will set each timer start count to 0.</param>
        public void ResetAll(bool resetCounts)
        {
            foreach (var pair in this.timers)
            {
                pair.Value.Timer.Reset();
                if (resetCounts)
                {
                    pair.Value.Count = 0;
                }
            }
        }

        /// <summary>
        /// Gets an array of timer keys.
        /// </summary>
        /// <returns>Returns an array of timer keys.</returns>
        public T[] GetKeys()
        {
            var keys = new T[this.timers.Count];
            this.timers.Keys.CopyTo(keys, 0);
            return keys;
        }

        /// <summary>
        /// Sets the enabled state of the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <param name="enabled">If true the timer will be enabled. If false the timer will be stopped if it is running.</param>
        public void SetEnabled(T key, bool enabled)
        {
            var model = this.timers[key];
            model.Enabled = enabled;
        }

        /// <summary>
        /// Sets the enabled state of the specified timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <param name="enabled">If true the timers will be enabled. If false the timers will be stopped if they are running.</param>
        public void SetEnabled(T[] keys, bool enabled)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                model.Enabled = enabled;
            }
        }

        /// <summary>
        /// Gets the enabled state of the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        public bool IsEnabled(T key)
        {
            var model = this.timers[key];
            return model.Enabled;
        }

        /// <summary>
        /// Gets the enabled state of the specified timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <param name="enabled">Will contain the enabled state for each specified key.</param>
        public void IsEnabled(T[] keys, out bool[] enabled)
        {
            var enabledStates = new bool[keys.Length];
            for (int i = 0; i < keys.Length; i++)
            {
                var model = this.timers[keys[i]];
                enabledStates[i] = model.Enabled;
            }

            enabled = enabledStates;
        }

        /// <summary>
        /// Starts the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <remarks>Will not start if the enabled state is false.</remarks>
        public void Start(T key)
        {
            var model = this.timers[key];
            if (!model.Enabled)
            {
                return;
            }
            model.Count++;
            model.Timer.Start();
        }

        /// <summary>
        /// Starts the timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <remarks>Will not start if the timers enabled state is false.</remarks>
        public void Start(T[] keys)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                if (!model.Enabled)
                {
                    continue;
                }
                model.Count++;
                model.Timer.Start();
            }
        }

        /// <summary>
        /// Stops the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        public void Stop(T key)
        {
            var model = this.timers[key];
            if (!model.Enabled)
            {
                return;
            }
            model.Timer.Stop();
        }

        /// <summary>
        /// Stops the timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        public void Stop(T[] keys)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                if (!model.Enabled)
                {
                    continue;
                }
                model.Timer.Stop();
            }
        }

        /// <summary>
        /// Resets the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        public void Reset(T key)
        {
            this.Reset(key, false);
        }

        /// <summary>
        /// Resets the timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        public void Reset(T[] keys)
        {
            this.Reset(keys, false);
        }

        /// <summary>
        /// Resets the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <param name="resetCount">If true the start count for the timer will be set to 0.</param>
        public void Reset(T key, bool resetCount)
        {
            var model = this.timers[key];
            model.Timer.Reset();
            if (resetCount)
            {
                model.Count = 0;
            }
        }

        /// <summary>
        /// Resets the timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <param name="resetCounts">If true the start count for the timers will be set to 0.</param>
        public void Reset(T[] keys, bool resetCounts)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                model.Timer.Reset();
                if (resetCounts)
                {
                    model.Count = 0;
                }
            }
        }

        /// <summary>
        /// Resets the timer start count to 0.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        public void ResetCount(T key)
        {
            var model = this.timers[key];
            model.Count = 0;
        }

        /// <summary>
        /// Resets each timer start count to 0.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        public void ResetCount(T[] keys)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                model.Count = 0;
            }
        }

        /// <summary>
        /// Restarts the timer.
        /// </summary>
        /// <param name="key">The key to the timer information.</param>
        /// <param name="resetCount">If true the start count for the timer will be set to 0.</param>
        /// <remarks>The timer will be reset then started again.</remarks>
        public void Restart(T key, bool resetCount)
        {
            var model = this.timers[key];
            model.Timer.Reset();
            if (resetCount)
            {
                model.Count = 0;
            }

            this.Start(key);
        }

        /// <summary>
        /// Restarts the specified timers.
        /// </summary>
        /// <param name="keys">The keys to the timer information.</param>
        /// <param name="resetCounts">If true the start count for the timers will be set to 0.</param>
        /// <remarks>Each timer will be reset then started again.</remarks>
        public void Restart(T[] keys, bool resetCounts)
        {
            foreach (var key in keys)
            {
                var model = this.timers[key];
                model.Timer.Reset();
                if (resetCounts)
                {
                    model.Count = 0;
                }
            }

            this.Start(keys);
        }

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

Setting up a performance counter

#if PERFORMANCE
            var perf = PerformanceTesting<string>.Instance;
            perf.Create("ScanLines");
#endif

A sample usage

#if PERFORMANCE
                var perf = PerformanceTesting<string>.Instance;
                perf.Reset("ScanLines");
                perf.Start("ScanLines");
#endif
                this.scanner.Calculate(this.points);
#if PERFORMANCE
                perf.Stop("ScanLines");
#endif

How to report the results

#if PERFORMANCE
        public static void ReportPerformanceTimes()
        {
            var perf = PerformanceTesting<string>.Instance;
            foreach (var value in perf.GetKeys())
            {
                Debug.Log(string.Format("{0} - Total: {1}ms Average: {2} Count: {3}", value, perf.TotalMilliseconds(value), perf.AverageMilliseconds(value), perf.GetStartCount(value)));
            }

            Debug.Log(string.Format("Total Performance Times - Total: {0}ms", perf.TotalMilliseconds("ScanLines")));
        }
#endif

Unity 101 Tip #59 – PingObject

Published 3/25/2013 by createdbyx in Unity | News
Tags: ,

Ever wonder how unity makes assets in the project window pop out when you select a link to them in the inspector? See the EditorGUIUtility.PingObject method.

PingObject

[MenuItem("Examples/Ping Selected")]
static void Ping()
{
    if (!Selection.activeObject)
    {
        Debug.LogError("Select an object to ping");
        return;
    }
    EditorGUIUtility.PingObject(Selection.activeObject);
}

I have been wondering how my Compaq nx9420 holds up to the Microsoft Surface Pro as I have been contemplating purchasing one. Even though it’s pushing 6 or 7 years old now my nx9420 is still a decent machine to work with Unity3D, Visual Studio, and to play older games on but the battery no longer holds a charge, the 5200 rpm 100Gb hard drive slows it down a lot compared to the 256Gb Samsung 830 SSD (replaced by the 840 series) I recently purchased for my desktop.

It’s at a point where given it’s age I feel it’s too old to sell, especially when you can get a decent machine for like $600 now a days. And I am hesitant to put any more money into it like a new battery and a ssd to try and extend it’s life a little more.

So I started to crunch some numbers and look around for performance comparisons I could do and I have documented the few comparisons I have done below.

Based off the Window Experience Index numbers alone it seems comparable to a Surface Pro, but after running the peacemaker browser benchmark there started to be a noticeable performance gap. Running PCMark7 there was a huge performance gap. I know the surface pro is more powerful but I am still trying to get a sense of just how much more powerful it actually is compared to my nx9420.

My laptop used to be my main machine for a number of years until I bought my desktop, And even though I hardly ever use my laptop anymore it sure is nice to have a mobile computer to pack around on long trips and when I am away from home.

The single biggest reason I am holding off getting a Surface Pro is the fact I cannot replace the battery. Even after 6+ years I can still buy a new $40 battery for my nx9420 off eBay. Given that I rarely have a use for a portable machine I will undoubtedly have the Surface Pro for a good 6+ years and if I can’t replace the battery, it no longer becomes a portable device seeing as how it will need to be plugged in all the time. Thus defeating the purpose of a mobile computer.

So without further adieu lets compare numbers shall we !

Windows Experience Index numbers are as follows. Keep in mind the math play below is simply a numerical comparison on the windows experience index numbers alone and does not reflect actual hardware features like the nx9420’s x1600 graphics with DX9 v2 vertex & pixel shader support compared to the HD4000’s (I’m guessing) DX11 v3 or v4 vertex & pixel shader support.

Win 7 experience index 1.0 to 7.9
Win 8 experience index 1.0 to 9.9
7.9 / 9.9 * 100 - 100 = 20.20% diff

Microsoft Surface Pro
Processor: 6.9 / 9.9 * 100 = 69.9% (core i5 third gen)
Memory: 5.9 / 9.9 * 100 = 59.59% (4gb)
Graphics: 5.6 / 9.9 * 100 = 56.56%
Gaming: 6.4 / 9.9 * 100 = 64.64% (HD4000 integrated)
Hard Disk: 8.1 / 9.9 * 100 = 81.81% (ssd)

Compaq nx9420
Processor: 3.7 / 7.9 * 100 = 46.83% (Core i2 dual core T7200 2.0Ghz)
Memory: 4.4 / 7.9 * 100 = 55.69% (4gb but intel chipset only recognizes 3gb)
Graphics: 4.2 / 7.9 * 100 = 51.16%
Gaming: 4.3 / 7.9 * 100 = 54.43% (ati x1600 discrete)
Hard Disk: 4.4 / 7.9 * 100 = 55.69% (hdd 5200rpm)

nx9420 Win7 Experience Index scaled values to Win8 Experience Index (0.202 = 20.20%)
Processor: 46.83% * 0.202 = 9.45%  adjusted result is 56.28%
Memory: 55.69% * 0.202 = 11.24% adjusted result is 66.93%
Graphics: 51.16% * 0.202 = 10.33% adjusted result is 61.49%
Gaming: 54.43% * 0.202 = 10.99% adjusted result is 65.42%
Hard Disk: 55.69% * 0.202 = 11.24% adjusted result is 66.93%

nx9420 verses Surface Pro relative windows experience index
Processor: 56.28% -> 69.9%  = -13.2% slower
Memory: 66.93% -> 59.59% = +7.34% faster
Graphics: 61.49% -> 56.56% = +4.93% faster
Gaming: 65.42% -> 64.64% = +0.78% faster 
Hard Disk: 66.93% -> 81.81% = -14.88% slower

Compared the the performance numbers from the other tests listed below if I was to compare my nx9420 to the Surface Pro based on the windows experience index numbers alone one could get the false impression that the Surface Pro is not that much of a upgrade. Memory, Graphics and Gaming numbers are almost identical when comparing the Win 7 Experience index numbers relative to Win 8’s Experience index numbers.

Peacemaker browser benchmark test results

Microsoft Surface Pro IE10

surface pro on youtube small

nx9420 Laptop Chrome v24.0

chrome small

nx9420 Laptop IE9 64bit

IE9 64bit small

nx9420 Laptop IE9 32bit

IE9 32bit small

nx9420 Laptop Firefox 19

fiewfox small

My desktop win 8 pro (IE10 desktop mode 32bit)

desktop ie10 32bit

My desktop win 8 pro Firefox 19

desktop firefox

PCMark 7 results

Microsoft Surface Pro Score: 4657 (paid version)

Source –> MobleTechReview website

pcmark07 small

Compaq nx9420 Score 844 (free version)

Link to the results are here –> http://www.3dmark.com/pcm7/569200

DOTA 2 results

The Youtube channel SurfaceProGaming has a number of games that show the gaming performance of the Surface Pro.

Dota 2 on Surface Pro runs at about an average of say 30 fps at resolution of 1280x720 (921,600 pixels) while recording fraps.

Using the same graphics settings with the closest resolution my nx9420 lets me select 1024x768 (786,432 pixels) I can run DOTA 2 at an average of 15 to 25 fps without recording fraps. Because of the 5200 rpm HDD is 80% full I only get 5 to 8 fps when fraps is recording.

In Conclusion

When comparing the Surface Pro to my nx9420 running DOTA 2 the Surface Pro has a noticeable increase in capability but in my opinion not enough to justify getting one. The fact that my nx9420 is 6+ years old and still “somewhat” keeps up with the Surface Pro in DOTA 2 is a strong indicator that my nx9420 is still a fairly decent system for what I use it for.

I don’t play too many games on my laptop other then simple games like Angry Birds, The Binding of Issac and older games like the legacy of kain series of games that I recently played on my laptop. For larger games like Starcraft 2, World of Warcraft, Skyrim, Crysis etc I will always play on my desktop.

If the Surface Pro showed a more drastic performance in DOTA 2 AND had a removable battery I would have given some serious thought into getting a Surface Pro but that’s not goanna happen after running these comparisons. Investing in a 128Gb SSD and a new battery for my nx9420 is a far more viable solution economically and strategically because after I decide to sell or retire my laptop I can always add the SDD to my desktop and a new battery is cheap at around $40.

I had also looked into the Razer Edge Pro but although it’s more graphically capable then the Surface Pro again it too has no removable battery, and a lower resolution the then Surface Pro, and no stylus pen.

My next portable computer will be a tablet but only if it has a discrete graphics chip, pen input, and a removable battery. I think I may be waiting quite a few years for that to happen.

Hope this gives some people a sense of how much of an upgrade buying a Surface Pro would be compared to using a older laptop.


This code uses a Point type that contains X/Y as integers similar to System.Drawing.Point. This class is used to calculate vertical scan lines for a polygon. It has been tested with 3 points but beyond that I am unsure if it will work.

using System;

/// <summary>
/// Helper class used to calculate scan lines of a triangle
/// </summary>
public class TriangleScanLineCalculator
{
    /// <summary>
    /// Provides a high low type for storing min & max values for scan lines.
    /// </summary>
    public struct HiLoTYPE
    {
        public int High;
        public int Low;
    }

    /// <summary>
    /// Holds scan line information.
    /// </summary>
    protected internal HiLoTYPE[] scanLines;

    /// <summary>
    /// Holds the calculated scan line count.
    /// </summary>
    protected internal int scanLineCount;

    /// <summary>
    /// Gets the right most side of the triangle.
    /// </summary>
    public int MaximumX { get; private set; }

    /// <summary>
    /// Gets the left most side of the triangle.
    /// </summary>
    public int MinimumX { get; private set; }

    /// <summary>
    /// Gets the number of scan lines.
    /// </summary>
    public int Count
    {
        get { return scanLineCount; }
    }

    /// <summary>
    /// Calculates the min & max y values for each scan line.
    /// </summary>
    /// <param name="points">The points that make up the triangle</param>
    public void Calculate(Point[] points)
    {
        var xMax = 0;
        var xMin = 0;
        float XDelta = 0;
        float YDelta = 0;
        // X/Y distance between 2 vertexes
        float YPos = 0;
        float YSlope = 0;
        var VertIndex1 = 0;
        var VertIndex2 = 0;
        var tempIndex = 0;

        // Step 1: Find the min and max 'X' dimensions of the polygon
        xMax = int.MinValue;
        xMin = int.MaxValue;
        for (var i = 0; i <= points.Length - 1; i++)
        {
            if (xMax < points[i].X)
            {
                xMax = points[i].X;
            }

            if (xMin > points[i].X)
            {
                xMin = points[i].X;
            }
        }
        this.MinimumX = xMin;
        this.MaximumX = xMax;
        this.scanLineCount = xMax - xMin;

        // Step 2: Resize scan line array to hold all the high and low x values
        if (this.scanLines == null || this.scanLines.Length < xMax - xMin)
        {
            // allocate 
            Array.Resize(ref this.scanLines, (xMax - xMin) + 100);
        }

        // Step3: Set the height value of all scan lines to there min or max value(s)
        for (var i = 0; i <= this.scanLines.Length - 1; i++)
        {
            this.scanLines[i].High = int.MinValue;
            this.scanLines[i].Low = int.MaxValue;
        }

        // Step 4: Set up the Y highs and lows for each X scan line between the min X and Max X points
        for (var i = 0; i < points.Length; i++)
        {
            // Step4a: Determine witch sides of the polygon we will be setting up
            VertIndex1 = i;
            VertIndex2 = i + 1;
            if (VertIndex2 == points.Length) VertIndex2 = 0;

            // Step4b: check if the first vertex if farther right then the second vertex
            // and if so swap vertex indexes
            if (points[VertIndex1].X > points[VertIndex2].X)
            {
                tempIndex = VertIndex1;
                VertIndex1 = VertIndex2;
                VertIndex2 = tempIndex;
            }

            // Step4c: Find the X/Y dist between vert1 and vert2
            XDelta = points[VertIndex2].X - points[VertIndex1].X;
            YDelta = points[VertIndex2].Y - points[VertIndex1].Y;

            // Step4d: Determine the Y slope to use.
            // YSlope determines how much to move down for every move we make to the right
            if (XDelta != 0)
            {
                YSlope = YDelta / XDelta;
            }
            else
            {
                YSlope = 0;
            }

            // Save the starting y position in YPos
            YPos = points[VertIndex1].Y;

            // Step4e: Process all of scan lines between vert1 and vert2
            for (var idy = points[VertIndex1].X; idy < points[VertIndex2].X; idy++)
            {
                // If the scan lines higher value has already been set then set the lower value
                // we use the formula 'idy - XMin' to determine what index into the scan line
                // array to use. (ScanLineIndex = AnyPositionBetweenVert1AndVert2 - LeftMostPartOfPoly)

                // Store the scan line index in the TmpIndex variable so we don't
                // have the overhead of doing 5 subtractions
                tempIndex = idy - xMin;

                var item = this.scanLines[tempIndex];

                // Check if Scan(TmpIndex).High has been set already
                if (item.High == int.MinValue)
                {
                    // High has not been set yet
                    item.High = (int)YPos;
                }
                else
                {
                    // High has been set yet so we set the low point
                    item.Low = (int)YPos;

                    // Ensure that the High is actually a higher value then low
                    if (item.High < item.Low)
                    {
                        var tempValue = item.High;
                        item.High = item.Low;
                        item.Low = tempValue;
                    }
                }
                this.scanLines[tempIndex] = item;

                // update the Y position
                YPos += YSlope;
            }
        }
    }

    /// <summary>
    /// Retrieves a scan line.
    /// </summary>
    /// <param name="index">The index of the scan line.</param>
    /// <returns>Returns information about the scan line.</returns>
    public HiLoTYPE ScanLine(int index)
    {
        return this.scanLines[index];
    }

    /// <summary>
    /// Gets all the scan lines.
    /// </summary>
    /// <returns>Returns all the scan line information.</returns>
    public HiLoTYPE[] GetScanLines()
    {
        // if there are no scan lines return null.
        if (this.scanLineCount == 0)
        {
            return null;
        }

        // resize the array 
        Array.Resize(ref this.scanLines, this.scanLineCount);
        return this.scanLines;
    }

    /// <summary>
    /// Calculates the scan lines for a triangle.
    /// </summary>
    /// <param name="points">The triangle points.</param>
    /// <param name="minXValue">Will return the minimum x value that the triangle starts at.</param>
    /// <returns>Returns the min max y values of the scanline.</returns>
    public static HiLoTYPE[] Calculate(Point[] points, out int minXValue)
    {
        // create scan line calculator and get values from it
        var scanner = new TriangleScanLineCalculator();
        scanner.Calculate(points);
        var items = scanner.GetScanLines();
        minXValue = scanner.MinimumX;
        return items;
    }
}

If you are writing UI code and you want the controls you are drawing to appear as if they are labels until they are in focus you can use  the EditorGUIUtility.LookLikeInspector & EditorGUIUtility.LookLikeControls methods.

LookLikeControls

public class LookLikeControlsInspector : EditorWindow
{
    private int integer1;
    float float1 = 5.5f;

    [MenuItem("Examples/Look Like Controls - Inspector")]
    static void Init()
    {
        var window = GetWindow<LookLikeControlsInspector>();
        window.Show();
    }

    void OnGUI()
    {
        EditorGUIUtility.LookLikeInspector();
        EditorGUILayout.TextField("Text Field:", "Hello There");
        EditorGUILayout.IntField("Int Field:", integer1);
        EditorGUILayout.FloatField("Float Field:", float1);
        EditorGUILayout.Space();
        EditorGUIUtility.LookLikeControls();
        EditorGUILayout.TextField("Text Field", "Hello There");
        EditorGUILayout.IntField("Int Field:", integer1);
        EditorGUILayout.FloatField("Float Field:", float1);
    }
}

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