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

Source - http://stackoverflow.com/questions/11518935/mvc-redirect-to-default-route/11520787#11520787

Redirect route handler

public class RedirectRouteHandler : IRouteHandler
{
    private string _redirectUrl;

    public RedirectRouteHandler(string redirectUrl)
    {
        _redirectUrl = redirectUrl;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        if (_redirectUrl.StartsWith("~/"))
        {
            string virtualPath = _redirectUrl.Substring(2);
            Route route = new Route(virtualPath, null);
            var vpd = route.GetVirtualPath(requestContext,
                requestContext.RouteData.Values);
            if (vpd != null)
            {
                _redirectUrl = "~/" + vpd.VirtualPath;
            }
        }

        return new RedirectHandler(_redirectUrl, false);
    } 
}

Redirect http handler

public class RedirectHandler : IHttpHandler
{
    private readonly string _redirectUrl;

    public RedirectHandler(string redirectUrl, bool isReusable)
    {
        _redirectUrl = redirectUrl;
        IsReusable = isReusable;
    }

    public bool IsReusable { get; private set; }

    public void ProcessRequest(HttpContext context)
    {
        context.Response.Status = "301 Moved Permanently";
        context.Response.StatusCode = 301;
        context.Response.AddHeader("Location", _redirectUrl);
    }
}

Extension methods

public static class RouteExtensions
{
    public static void Redirect(this RouteCollection routes, string url, string redirectUrl)
    {
        routes.Add(new Route(url, new RedirectRouteHandler(redirectUrl)));
    }
}

The code below allows you to copy a directory structure with or without sub folders including files. Also includes a callback that can be used to filter out files or folders and reports the copy progress.

/// <summary>
/// Copies a directory structure to the destination.
/// </summary>
/// <param name="source">The directory structure to be copied.</param>
/// <param name="destination">The destination where the directory structure will be copied to.</param>
/// <param name="copySubDirectories">true to copy all subdirectories.</param>
/// <param name="overwriteFiles">true if the destination files can be overwritten; otherwise, false.</param>
/// <param name="callback">Provides a callback function for reporting progress. </param>
/// <remarks><p>The callback invoked just before a file copy occurs providing a way of being notified.</p>
/// <p>The callback parameter order is source file, destination file, progress.</p>
/// <p>If the callback is specified it should return true to allow the file copy to occur.</p> 
/// <p>The progress parameter reports progress from 0 to 100. Values to the left of the decimal represent folder copy progress and values to the
/// right of the decimal from 0.000 to 0.99 represent the current file copy progress for the folder that is being copied.</p>
/// <p>To get the current file copy progress as a value from 0 to 100 use the formula fileProgress = progress - 100 * 100.</p></remarks>
public static void CopyDirectories(string source, string destination, bool copySubDirectories, bool overwriteFiles, Func<string, string, float, bool> callback)
{
    // ensure source folder exists
    if (!Directory.Exists(source))
    {
        throw new DirectoryNotFoundException("The path specified in source is invalid (for example, it is on an unmapped drive).");
    }

    // create destination folder
    Directory.CreateDirectory(destination);

    // get all files in source and copy them to destination folder
    var files = Directory.GetFiles(source);
    var progress = 0f; // used to report the progress from 0 to 100

    // set up action to copy files
    var fileProcessor = new Action<float, string[], string>((folderProgress, filesToCopy, folder) =>
        {
            // copy files
            for (var i = 0; i < filesToCopy.Length; i++)
            {
                // get file
                var file = filesToCopy[i];

                // set default result
                var result = true;

                // build destination filename
                var fileName = Path.GetFileName(file);
                if (fileName == null) // should never happen
                {
                    return;
                }

                fileName = Path.Combine(folder, fileName);

                // check if callback specified
                if (callback != null)
                {
                    // store result from callback
                    result = callback(file, fileName, progress);
                }

                // if result is true we are allowed to copy the file
                if (result)
                {
                    File.Copy(file, fileName, overwriteFiles);
                }

                // (folder progress * 100) + file progress
                progress = folderProgress + ((float)i / filesToCopy.Length);
            }
        });

    // copy initial files
    fileProcessor(0, files, destination);

    // check to copy sub directories
    if (!copySubDirectories)
    {
        return;
    }

    // get the folder tree for the source folder
    var folders = Directory.GetDirectories(source, "*.*", SearchOption.AllDirectories);

    // process each sub folder
    for (var index = 0; index < folders.Length; index++)
    {
        // get folder and increment index
        var folder = folders[index];

        // get files
        files = Directory.GetFiles(folder);

        // crop source root from destination and build destination folder path
        folder = folder.Remove(0, source.Length);
        folder = Path.Combine(destination, folder);

        // create destination folder
        Directory.CreateDirectory(folder);

        // process file copying
        fileProcessor((index / folders.Length) * 100, files, folder);
    }
}

Some .net/mono frameworks do not support the IsNullOrWhiteSpace method. The code below provides a simple replacement.

/// <summary>
/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
/// </summary>
/// <param name="value">value: The string to test.</param>
/// <returns>true if the value parameter is null or System.String.Empty, or if value consists exclusively of white-space characters.</returns>
public static bool IsNullOrWhiteSpace(this string value)
{
    if (value == null)
    {
        return true;
    }

    var index = 0;
    while (index < value.Length)
    {
        if (char.IsWhiteSpace(value[index]))
        {
            index++;
        }
        else
        {
            return false;
        }
    }

    return true;
}

Some .net/mono frameworks do not support the 3 argument GetDirectories method. The code below provides a simple replacement.

/// <summary>
/// Builds an array of folders & sub folders.
/// </summary>
/// <param name="path">The path to search.</param>
/// <param name="pattern">The search string to match against the names of files in path. The parameter cannot end in two periods 
/// ("..") or contain two periods ("..") followed by System.IO.Path.DirectorySeparatorChar or System.IO.Path.AltDirectorySeparatorChar,
/// nor can it contain any of the characters in System.IO.Path.InvalidPathChars.
///</param>
/// <param name="searchOptions">One of the System.IO.SearchOption values that specifies whether the search operation should include 
/// all subdirectories or only the current directory.
///</param>
/// <returns>A String array of directories that match the search pattern.</returns>
public static string[] GetDirectories(string path, string pattern, SearchOption searchOptions)
{
    // check if searching for all directories
    if (searchOptions == SearchOption.AllDirectories)
    {
        // add start paths to list
        var list = Directory.GetDirectories(path, pattern);
        var index = 0;
        var count = list.Length;

        // process list and add folders to end of list
        while (index < count)
        {
            var directories = Directory.GetDirectories(list[index++], pattern);
            if (directories.Length > 0)
            {
                // check if we need more space to store the directories
                if (count + directories.Length > list.Length - 1)
                {
                    Array.Resize(ref list, list.Length + directories.Length + 1000);
                }

                // add directories to end of the list
                foreach (var directory in directories)
                {
                    list[count++] = directory;
                }
            }

            // trim unused index from end of the array
            if (list.Length > count)
            {
                Array.Resize(ref list, count);
            }
        }

        return list;
    }

    // just return initial list of folder with no sub folders
    return Directory.GetDirectories(path, pattern);
}

/// <summary>
/// Provides a game object that automatically self terminates.
/// </summary>
[ExecuteInEditMode]
public class SelfTerminatingObject : MonoBehaviour
{
    /// <summary>
    /// Holds a reference to a callback that will be called just before termination.
    /// </summary>
    public Action Callback;

    /// <summary>
    /// Holds the time delay in seconds before object self terminates.
    /// </summary>
    public float Delay;

    /// <summary>
    /// Holds a time value used to determine whether it's time to self terminate.
    /// </summary>
    private DateTime lastTime;

    /// <summary>
    /// Called by Unity to update the object.
    /// </summary>
    public void Update()
    {
        this.PerformCheck();
    }

    /// <summary>
    /// Called by Unity to draw the object's GUI.
    /// </summary>
    public void OnGui()
    {
        this.PerformCheck();
    }

    /// <summary>
    /// Determines whether it's time to terminate.
    /// </summary>
    private void PerformCheck()
    {
        // check if specified time span has elapsed
        if (DateTime.Now > this.lastTime + TimeSpan.FromSeconds(this.Delay))
        {
            // run callback is specified
            if (this.Callback != null)
            {
                this.Callback();
            }

            // destroy object
#if UNITY_EDITOR
        DestroyImmediate(this.gameObject, true);
#else
            Destroy(this.gameObject);
#endif
        }
    }

    /// <summary>
    /// Creates a self terminating game object.
    /// </summary>
    /// <param name="callback">The method to be called before the object terminates.</param>
    /// <returns>Returns a reference to the game object that will self terminate.</returns>
    public static GameObject CreateUpdateCallback(Action callback)
    {
        return CreateUpdateCallback(callback, 0);
    }

    /// <summary>
    /// Creates a self terminating game object.
    /// </summary>
    /// <param name="callback">The method to be called before the object terminates.</param>
    /// <param name="delay">Specifies a delay value in seconds before the object will self terminate.</param>
    /// <returns>Returns a reference to the game object that will self terminate.</returns>
    public static GameObject CreateUpdateCallback(Action callback, float delay)
    {
        var obj = new GameObject();
        var com = obj.AddComponent<SelfTerminatingObject>();
        com.lastTime = DateTime.Now;
        com.Delay = delay;
        com.Callback = callback;

        return obj;
    }
}

File Backup Solutions

Published 3/1/2013 by createdbyx in News
Tags:

I have been procrastinating whether or not to write my own open source Google Drive / DropBox clone in C# for some time now. I’ve been looking for a cloud based backup solution but am to cheap to pay and have only used the free storage that DropBox, Google Drive & SkyDrive offer. Google Drive & DropBox work awesome and seamlessly but I have had various “issues” with SkyDrive.

The reason I wanted to write my own clone of these cloud based storage solutions is because I am already paying for “Unlimited” storage with my web hosting account that this site and all my other web sites are hosted from. So for me to pay extra for storage on SkyDrive, DropBox etc doesn't make much sense.

I used to use SyncBack for a while but stopped using it. I started looking around for free alternatives and came across Duplicati. Duplicati is almost what I had planed on making myself. it’s open source, written in C# and can back up files to a multitude of storage destinations like SkyDrive, Google Drive, FTP etc. It’s multi platform (Mac, Win, Linux) & it supports encryption, compression & incremental backups! It’s pretty much everything I could have hoped for and a little bit more.

Although I have not used it for very long I think I may have found a backup solution I can live with.


There is no Unity specific API for getting the project folder, but you can use System.IO.Directory.GetCurrentDirectory. The unity editor expects the current folder to be set to the project folder at all times.

If you are changing the current directory in your editor scripts using System.IO.Directory.SetCurrentDirectory you need to restore the directory back to the project folder when you are done. Otherwise the next time unity compiles your scripts it will prompt you with a dialog box and a message stating you need to restore the current directory back to the project directory and only gives you a “Quit” button to click on that will quit unity.

    // save project folder
    var projectFolder = System.IO.Directory.GetCurrentDirectory();
                   
    // set current directory to a folder of your choosing
    System.IO.Directory.SetCurrentDirectory("c:\\some folder name");
                  
    // do what you need to do
    // ...

    // restore current folder back the the project folder 
    System.IO.Directory.SetCurrentDirectory(projectFolder);

This code is designed to hook into unity’s EditorApplication.update delegate.

IRun.cs

namespace CBX.CoreProjectCode.Interfaces
{
    using CBX.CoreProjectCode.Models;

    /// <summary>
    /// Provides a interface for running a <see cref="CallbackModel{T}"/> type.
    /// </summary>
    public interface IRun
    {
        /// <summary>
        /// Runs the <see cref="CallbackModel{T}"/> type.
        /// </summary>
        void Run();                    
    }
}

CallbackModel.cs

namespace CBX.CoreProjectCode.Models
{
    using System;

    using CBX.CoreProjectCode.Interfaces;

    /// <summary>
    /// Provides a modal for callbacks.
    /// </summary>
    /// <typeparam name="T">The generic type used to represent the data type.</typeparam>
    public class CallbackModel<T> : IRun
    {
        /// <summary>
        /// A reference to a callback method.
        /// </summary>
        public Action<T> Callback;

        /// <summary>
        /// A reference to some data that will be passed to the callback method.
        /// </summary>
        public T Data;

        /// <summary>
        /// Implements <see cref="IRun.Run"/> to run the callback. 
        /// </summary>
        public void Run()
        {
            this.Callback(this.Data);
        }
    }
}

EditorCallbackService.cs

namespace CBX.CoreProjectCode.Services
{
    using System;
    using System.Collections.Generic;

    using CBX.CoreProjectCode.Interfaces;
    using CBX.CoreProjectCode.Models;

    /// <summary>
    /// The editor callback service.
    /// </summary>
    public class EditorCallbackService
    {
        /// <summary>
        /// Holds a singleton instance of the <see cref="EditorCallbackService"/> type.
        /// </summary>
        private static EditorCallbackService service;

        /// <summary>
        /// Holds a list of <see cref="CallbackModel{T}"/> types that implement <see cref="IRun"/>.
        /// </summary>
        private readonly Stack<IRun> callbacks;

        /// <summary>
        /// Initializes a new instance of the <see cref="EditorCallbackService"/> class.
        /// </summary>
        public EditorCallbackService()
        {
            this.callbacks = new Stack<IRun>();
        }

        /// <summary>
        /// Returns a singleton instance of the <see cref="EditorCallbackService"/> type.
        /// </summary>
        public static EditorCallbackService Instance
        {
            get
            {
                // if no service yet exists create one
                return service ?? (service = new EditorCallbackService());
            }
        }

        /// <summary>
        /// Runs any callbacks that have been registered.
        /// </summary>
        public void Run()
        {
            while (this.callbacks.Count > 0)
            {
                var cb = this.callbacks.Pop();
                cb.Run();
            }
        }

        /// <summary>
        /// Registers a <see cref="Action{T}"/> callback.
        /// </summary>
        /// <typeparam name="T">The type of data that the <see cref="callback"/> takes as a parameter.</typeparam>
        /// <param name="callback">A reference to a callback.</param>
        public void Register<T>(Action<T> callback)
        {
            this.Register(callback, default(T));
        }

        /// <summary>
        /// Registers a <see cref="Action{T}"/> callback.
        /// </summary>
        /// <typeparam name="T">
        /// The type of data that the <see cref="callback"/> takes as a parameter.
        /// </typeparam>
        /// <param name="callback">
        /// A reference to a callback.
        /// </param>
        /// <param name="data">
        /// The data that will be passed as a parameter when the <see cref="callback"/> is invoked.
        /// </param>
        public void Register<T>(Action<T> callback, T data)
        {
            if (callback == null)
            {
                throw new ArgumentNullException("callback");
            }

            var modal = new CallbackModel<T> { Callback = callback, Data = data };
            this.callbacks.Push(modal);
        }

        /// <summary>
        /// Registers a <see cref="Action"/> callback.
        /// </summary>
        /// <param name="callback">A reference to a callback.</param>
        public void Register(Action callback)
        {
            this.Register<object>(x => callback(), null);
        }
    }
}

Usage scenario …

    [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)
            {
                // setup the settings system
                SetupSettings();

                // load localization strings
                LoadLocalizationData();

                ranOnce = true;
                return;
            }

            // invoke callbacks from editor callback service     
            EditorCallbackService.Instance.Run();
        }
    }

CBX.GridMapping v2.0.1 Released

Published 2/26/2013 by createdbyx in News | Unity
Tags: ,

See the project page at http://www.codefarts.com/CBXGridMapping/

Current Features

  • Can use your own prefabs
  • Includes simple prefab shapes
  • Customizable prefab quick select grid
  • Recently used lists for quick re-selection
  • Support for layering
  • Can name, lock, show/hide, reorder layers
  • Structured prefab naming convention
  • Preset rotations when drawing
  • 3 draw tools Pencil/Rectangle/Fill Rectangle
  • Supports Undo
  • Full off line documentation included
  • Built in feedback tool
  • Includes preliminary API & developer tools
  • Can auto scale and auto center prefabs
  • Lots of settings for customization
  • Fully commented & organized C# source code included
  • 95+% of source code conforms to default StyleCop settings

NOTE: The current version of this tool does not contain a grid or tile mapping/management system. It is a in editor creation tool designed to make it easy to rapidly place prefabs within a grid based layout. A future update will include editor & run time API's.

Version Notes

Items starting with "Completed" are items that have been fully implemented as they were intended
Items starting with "Partial" are items that have only been partially implemented.

v2.0.1

v2.0

  • Completed - Fixed bug if build settings were set to WebPlayer. File.GetLastWriteTime was not available under WebPlayer.
  • Completed - Complete reshuffling of the code base to make it easier to publish and the code is more organized. The code base now lives in it's own folder under "CBX Game\CBX.GridMapping".
  • Completed - All CBX.GridMapping specific code now lives under the CBX.GridMapping name space.
  • Completed - Added additional prefabs to the quick prefab selection drop down.
  • Completed - Fixed issue with the 2D mesh plane prefab. Collider is now properly aligned to the mesh.
  • Completed - Temporarily removed the Quick Tools Beta preview from the code base. It will make an reappearance in a future update.
  • Completed - Removed dead code from numerous code files.
  • Completed - Numerous improvements to the layers system.
  • Partial - Automatic Material Creation window is partially implemented but not recommended for production use just yet. It pretty much works as intended but there is no way to save the materials it creates internally as actual unity *.mat files.
  • Completed - Ability to hide and show individual layers
  • Completed - Have setting to hide show the foldout control for hiding map information
  • Completed - Ability to lock a layer so it can not be drawn or erased
  • Completed - Ability to change the order of layers in the layer list
  • Completed - Ability to delete a specific layer
  • Completed - Added 2 additional draw modes Rectangle & FillRectangle
  • Completed - Added additional settings for more customization
  • Completed - Tile Material Creation, Scene view auto pan, & Hidden Game object tools are no longer part of CBX.GridMapping but instead have migrated over to a free utility library called CBX.GeneralTools available at the Codefarts website.
  • Completed - Fixed bug when changing cell size. The map grid did not automatically update.
  • Completed - Fixed bug where setting the cell size to a non square value prefabs that were drawn were scaled to the minimum dimensions of the cell.

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