Previously Part 4.

The Console is settling and getting to a point where I think I’m ready to split it out into it’s own repository and out of ‘under construction’.

It bothered me that my previous AddStaticTypeByString required you to know the assembly that the thing came from, so instead of using the System.Type version we grab all loaded assemblies and ask them in sequence if they have a type that matches the name.

The big thing I wanted to be able to do is map game data to the console. For me, in Unity, this is typically a ScriptableObject or something a bit fancier. The concept is the same though, there is some object with data that other classes refer to that defines common values. For this I needed to refactor my previous ConsoleHelper Add* to support object instances. Not a big deal, all the Reflection functions in use want an object, we’d just been giving them a null as everything was static previously, now it’s just lots of passing object down. This also meant that if you pass an object you don’t need to pass type and if you pass a type we could assume it was static if we wanted to.

In a more complex terminal window hitting tab repeatedly will cycle through all the possible things you could be trying to type. I have only attempted to get this working from within the App and not via the Browser. To achieve this I’m now keeping the result[] from a call to the complete function and if the autocomplete is requested again before changing the partial phrase we move the index forward through the cached result array and show that text. In the Unity Text we are replacing the text and keeping the current cursor position and setting the cursor end position to the end of the string. There are still some edge cases that are unhandled given my simplistic approach, like moving the cursor manually not resetting the array of autocompletes.

I wanted to be able to find a command without knowing where it might be. This I thought would be especially useful in one of the previously discussed situations where users customise their console but others might want to use it. E.g. you know that you can change gravity, but it isn’t where you thought it would be at UnityEngine.Physics.gravity. The find command then ‘needs’ to be able to traverse every node in the CommandTree but I didn’t want the find command to have to be overly aware of how CommandTree works nor did I want the CommandTree to have a slightly different version of Find that ignores heirarchy and can return more than 1 item. What solved it was a visitor pattern, c# closure (first result curiously not from msdn) and recursion.

We have something like this in CommandTree. Where each command will pass itself to the visitor and if the visitor requests it, then recur for all child commands.

public void Visit(System.Func<CommandTree,bool> visitor)
{
    if(visitor(this))
    {
        foreach (var item in SubCommands)
        {
            item.Visit(visitor);
        }
    }
}

and something like this to use it.

[Command("find", "Searches through all commands and conducts a partial match against the given string", false)]
public static void Find(string s)
{
    s = s.ToLower();

    StringBuilder sb = new StringBuilder();
    System.Func<CommandTree, bool> findNames = (CommandTree t) =>
    {
        if (t.Command != null && t.Command.localName.ToLower().Contains(s))
        {
            sb.AppendLine(t.FullCommandPath);
            return false;   //no point checking children as they will match this already
        }

        //try it's children
        return true;
    };

    instance.m_commands.Visit(findNames);

    Console.Log(sb.ToString());
}

This gets the job done in a way I can deal with, it also meant that I could remove CUDLR’s previous help command, as it built and stored a giant string as commands were added so it could display it later. I didn’t want that giant string to be around always. So now I have an all command, that uses the same visitor style.

In the previous post I mentioned that I profile system might be a useful thing people could create, well the idea stuck so I started adding it. I now store a ScriptableObject with a bunch of csv TextAssets in it.

Example csv

typeName,nameInConsole,bindingFlags,partsToBind
UnityEngine.Time,Unity.Time,"DeclaredOnly, Static, Public","Methods, Props, Fields"
UnityEngine.Physics,Unity.Physics,"DeclaredOnly, Static, Public","Methods, Props, Fields"
UnityEngine.Physics2D,Unity.Physics2D,"DeclaredOnly, Static, Public","Methods, Props, Fields"
UnityEngine.Application,Unity.Application,"DeclaredOnly, Static, Public","Methods, Props, Fields"

That gets converted to a List of.

[Flags]
public enum PartsToBind
{
	//0 is none
	Methods = (1 << 0),
	Props 	= (1 << 1),
	Fields 	= (1 << 2),
}

//typenames and namesinconsole must match length
[System.Serializable]
public class ClassByNameData
{
    public ClassByNameData() { }
    public ClassByNameData(string TypeName, string NameInConsole = null)
    {
        typeName = TypeName;
        nameInConsole = NameInConsole;
    }

    public string typeName = string.Empty;
	public string nameInConsole = null;
	public BindingFlags bindingFlags = PublicStatic;
	public PartsToBind partsToBind = PartsToBind.Methods | PartsToBind.Fields | PartsToBind.Props;
}

And here we are loading that example csv that is called Default in the console to add additional elements.

One other bit I found I needed was an attribute to flag, fields, props, methods or classes to be ignored by a AddAllToConsole. I created an attribute called CommandIgnore that I can apply to any thing and then my console helper variants will skip over it. The primary use I have for this is being able to have static classes or global data that I want in the console but that still allows me to refactor those classes in the usual way and not worry about exposing partial or useless functions to the console.