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.

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();

Simple solution to get a MIME type from a file extension. Source –> http://stackoverflow.com/a/7161265/341706

public static class MIMEAssistant
{
  private static readonly Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string>
  {
    {"ai", "application/postscript"},
    {"aif", "audio/x-aiff"},
    {"aifc", "audio/x-aiff"},
    {"aiff", "audio/x-aiff"},
    {"asc", "text/plain"},
    {"atom", "application/atom+xml"},
    {"au", "audio/basic"},
    {"avi", "video/x-msvideo"},
    {"bcpio", "application/x-bcpio"},
    {"bin", "application/octet-stream"},
    {"bmp", "image/bmp"},
    {"cdf", "application/x-netcdf"},
    {"cgm", "image/cgm"},
    {"class", "application/octet-stream"},
    {"cpio", "application/x-cpio"},
    {"cpt", "application/mac-compactpro"},
    {"csh", "application/x-csh"},
    {"css", "text/css"},
    {"dcr", "application/x-director"},
    {"dif", "video/x-dv"},
    {"dir", "application/x-director"},
    {"djv", "image/vnd.djvu"},
    {"djvu", "image/vnd.djvu"},
    {"dll", "application/octet-stream"},
    {"dmg", "application/octet-stream"},
    {"dms", "application/octet-stream"},
    {"doc", "application/msword"},
    {"docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
    {"docm","application/vnd.ms-word.document.macroEnabled.12"},
    {"dotm","application/vnd.ms-word.template.macroEnabled.12"},
    {"dtd", "application/xml-dtd"},
    {"dv", "video/x-dv"},
    {"dvi", "application/x-dvi"},
    {"dxr", "application/x-director"},
    {"eps", "application/postscript"},
    {"etx", "text/x-setext"},
    {"exe", "application/octet-stream"},
    {"ez", "application/andrew-inset"},
    {"gif", "image/gif"},
    {"gram", "application/srgs"},
    {"grxml", "application/srgs+xml"},
    {"gtar", "application/x-gtar"},
    {"hdf", "application/x-hdf"},
    {"hqx", "application/mac-binhex40"},
    {"htm", "text/html"},
    {"html", "text/html"},
    {"ice", "x-conference/x-cooltalk"},
    {"ico", "image/x-icon"},
    {"ics", "text/calendar"},
    {"ief", "image/ief"},
    {"ifb", "text/calendar"},
    {"iges", "model/iges"},
    {"igs", "model/iges"},
    {"jnlp", "application/x-java-jnlp-file"},
    {"jp2", "image/jp2"},
    {"jpe", "image/jpeg"},
    {"jpeg", "image/jpeg"},
    {"jpg", "image/jpeg"},
    {"js", "application/x-javascript"},
    {"kar", "audio/midi"},
    {"latex", "application/x-latex"},
    {"lha", "application/octet-stream"},
    {"lzh", "application/octet-stream"},
    {"m3u", "audio/x-mpegurl"},
    {"m4a", "audio/mp4a-latm"},
    {"m4b", "audio/mp4a-latm"},
    {"m4p", "audio/mp4a-latm"},
    {"m4u", "video/vnd.mpegurl"},
    {"m4v", "video/x-m4v"},
    {"mac", "image/x-macpaint"},
    {"man", "application/x-troff-man"},
    {"mathml", "application/mathml+xml"},
    {"me", "application/x-troff-me"},
    {"mesh", "model/mesh"},
    {"mid", "audio/midi"},
    {"midi", "audio/midi"},
    {"mif", "application/vnd.mif"},
    {"mov", "video/quicktime"},
    {"movie", "video/x-sgi-movie"},
    {"mp2", "audio/mpeg"},
    {"mp3", "audio/mpeg"},
    {"mp4", "video/mp4"},
    {"mpe", "video/mpeg"},
    {"mpeg", "video/mpeg"},
    {"mpg", "video/mpeg"},
    {"mpga", "audio/mpeg"},
    {"ms", "application/x-troff-ms"},
    {"msh", "model/mesh"},
    {"mxu", "video/vnd.mpegurl"},
    {"nc", "application/x-netcdf"},
    {"oda", "application/oda"},
    {"ogg", "application/ogg"},
    {"pbm", "image/x-portable-bitmap"},
    {"pct", "image/pict"},
    {"pdb", "chemical/x-pdb"},
    {"pdf", "application/pdf"},
    {"pgm", "image/x-portable-graymap"},
    {"pgn", "application/x-chess-pgn"},
    {"pic", "image/pict"},
    {"pict", "image/pict"},
    {"png", "image/png"}, 
    {"pnm", "image/x-portable-anymap"},
    {"pnt", "image/x-macpaint"},
    {"pntg", "image/x-macpaint"},
    {"ppm", "image/x-portable-pixmap"},
    {"ppt", "application/vnd.ms-powerpoint"},
    {"pptx","application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {"potx","application/vnd.openxmlformats-officedocument.presentationml.template"},
    {"ppsx","application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
    {"ppam","application/vnd.ms-powerpoint.addin.macroEnabled.12"},
    {"pptm","application/vnd.ms-powerpoint.presentation.macroEnabled.12"},
    {"potm","application/vnd.ms-powerpoint.template.macroEnabled.12"},
    {"ppsm","application/vnd.ms-powerpoint.slideshow.macroEnabled.12"},
    {"ps", "application/postscript"},
    {"qt", "video/quicktime"},
    {"qti", "image/x-quicktime"},
    {"qtif", "image/x-quicktime"},
    {"ra", "audio/x-pn-realaudio"},
    {"ram", "audio/x-pn-realaudio"},
    {"ras", "image/x-cmu-raster"},
    {"rdf", "application/rdf+xml"},
    {"rgb", "image/x-rgb"},
    {"rm", "application/vnd.rn-realmedia"},
    {"roff", "application/x-troff"},
    {"rtf", "text/rtf"},
    {"rtx", "text/richtext"},
    {"sgm", "text/sgml"},
    {"sgml", "text/sgml"},
    {"sh", "application/x-sh"},
    {"shar", "application/x-shar"},
    {"silo", "model/mesh"},
    {"sit", "application/x-stuffit"},
    {"skd", "application/x-koan"},
    {"skm", "application/x-koan"},
    {"skp", "application/x-koan"},
    {"skt", "application/x-koan"},
    {"smi", "application/smil"},
    {"smil", "application/smil"},
    {"snd", "audio/basic"},
    {"so", "application/octet-stream"},
    {"spl", "application/x-futuresplash"},
    {"src", "application/x-wais-source"},
    {"sv4cpio", "application/x-sv4cpio"},
    {"sv4crc", "application/x-sv4crc"},
    {"svg", "image/svg+xml"},
    {"swf", "application/x-shockwave-flash"},
    {"t", "application/x-troff"},
    {"tar", "application/x-tar"},
    {"tcl", "application/x-tcl"},
    {"tex", "application/x-tex"},
    {"texi", "application/x-texinfo"},
    {"texinfo", "application/x-texinfo"},
    {"tif", "image/tiff"},
    {"tiff", "image/tiff"},
    {"tr", "application/x-troff"},
    {"tsv", "text/tab-separated-values"},
    {"txt", "text/plain"},
    {"ustar", "application/x-ustar"},
    {"vcd", "application/x-cdlink"},
    {"vrml", "model/vrml"},
    {"vxml", "application/voicexml+xml"},
    {"wav", "audio/x-wav"},
    {"wbmp", "image/vnd.wap.wbmp"},
    {"wbmxl", "application/vnd.wap.wbxml"},
    {"wml", "text/vnd.wap.wml"},
    {"wmlc", "application/vnd.wap.wmlc"},
    {"wmls", "text/vnd.wap.wmlscript"},
    {"wmlsc", "application/vnd.wap.wmlscriptc"},
    {"wrl", "model/vrml"},
    {"xbm", "image/x-xbitmap"},
    {"xht", "application/xhtml+xml"},
    {"xhtml", "application/xhtml+xml"},
    {"xls", "application/vnd.ms-excel"},                        
    {"xml", "application/xml"},
    {"xpm", "image/x-xpixmap"},
    {"xsl", "application/xml"},
    {"xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {"xltx","application/vnd.openxmlformats-officedocument.spreadsheetml.template"},
    {"xlsm","application/vnd.ms-excel.sheet.macroEnabled.12"},
    {"xltm","application/vnd.ms-excel.template.macroEnabled.12"},
    {"xlam","application/vnd.ms-excel.addin.macroEnabled.12"},
    {"xlsb","application/vnd.ms-excel.sheet.binary.macroEnabled.12"},
    {"xslt", "application/xslt+xml"},
    {"xul", "application/vnd.mozilla.xul+xml"},
    {"xwd", "image/x-xwindowdump"},
    {"xyz", "chemical/x-xyz"},
    {"zip", "application/zip"}
  };

  public static string GetMIMEType(string fileName)
  {
    if (Path.GetExtension(fileName).Length > 1 && MIMETypesDictionary.ContainsKey(Path.GetExtension(fileName).Remove(0, 1)))
    {
      return MIMETypesDictionary[Path.GetExtension(fileName).Remove(0, 1)];
    }
    return "unknown/unknown";
  }
}

Unity comes with a helper method for generating a unique asset paths called AssetDatabase.GenerateUniqueAssetPath. The documentation for this method is rather minimal but the code example below should help you understand how to use it a little better.

GenerateUniqueAssetPath

[MenuItem("CBX/Test")]
public static void Test()
{
    // outputs "Assets/Rules.xml" if "Assets/Rules.xml" file does not already exist.
    // if "Assets/Rules.xml" already exists it outputs "Assets/Rules 1.xml"  
    Debug.Log(AssetDatabase.GenerateUniqueAssetPath("Assets/Rules.xml"));

    // expects path to start with "Assets/" & outputs a console error in the console "pathName.find("assets/") == 0"  
    // then outputs "Rules.xml" 
    Debug.Log(AssetDatabase.GenerateUniqueAssetPath("Rules.xml")); 
}

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 ...

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

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;
    }
}

I recently came across a strange behavior while loading text resource assets. In particular the Resources.LoadAll method does not accept Path.DirectorySeparatorChar characters in a path. In fact it only accepts Path.AltDirectorySeparatorChar characters. This behavior is different then standard .net file/folder methods that accept either Path.AltDirectorySeparatorChar or Path.DirectorySeparatorChar without distinction. What this means is that you can’t directly use Path.Combine to build a path and pass it to the Resources.LoadAll method you first have to replace any Path.DirectorySeparatorChar characters with Path.AltDirectorySeparatorChar characters.

The documentation for Resources.Load also does not mention this behavior.

I have submitted a bug report here –> https://fogbugz.unity3d.com/default.asp?533268_jgvrk2lbu1qm398e

    using System.IO;

    using UnityEditor;

    using UnityEngine;

    /// <summary>
    /// Handles settings registration.
    /// </summary>
    [InitializeOnLoad]
    public class EditorInitialization
    {
        /// <summary>
        /// Holds a value indicating whether the RunCallbacks method has been called at least once before.
        /// </summary>
        private static bool ranOnce;

        /// <summary>
        /// Initializes static members of the <see cref="EditorInitialization"/> class.
        /// </summary>
        static EditorInitialization()
        {
            EditorApplication.update += RunCallbacks;
        }

        private static void RunCallbacks()
        {
            if (!ranOnce)
            {
                // try to load resource
                var path = Path.Combine("Test/SubFolder", "testfile"); // result is Test/SubFolder\testfile

                // var data = Resources.LoadAll("Test/SubFolder/testfile", typeof(TextAsset)); // this line is successful
                var data = Resources.LoadAll(path, typeof(TextAsset));  // this line fails

                if (data != null && data.Length != 0)
                {
                    Debug.Log("found it");
                }
                else
                {
                    Debug.Log("not found! " + path);
                }

                ranOnce = true;
                return;
            }

            // do stuff 
        }
    }

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