Results 1 to 10 of 10
  1. #1
    Join Date
    Jun 2004
    Location
    The Netherlands
    Posts
    153

    Working on a debug console feature.

    Working on a console feature for R&D'ing so I can dynamicly turn on/off features, change variables at runtime. This would make debugging and such what easier as I dont have to stop unity, change code, rebuild and run unity again.

    Early preview:


    Will post code later this week as I implemented it fully, wanna see if I can make use of the buildin Boo language so you can actually create and execute scripts ingame
    Bug? That's not a bug, that's a feature.
    LinkedIn - Twitter (dutch) - Work (dutch)

  2. #2
    Join Date
    Mar 2007
    Location
    New York
    Posts
    33
    That's great, looking forward to you releasing the code for this.

    Quote Originally Posted by Project-A View Post
    Will post code later this week as I implemented it fully, wanna see if I can make use of the buildin Boo language so you can actually create and execute scripts ingame
    Is that what Boo is good for? I know nothing about Boo.

  3. #3
    Join Date
    Jun 2004
    Location
    The Netherlands
    Posts
    153
    Well Boo is also an option to program aganist Unity just as C# or Javascript. The nice thing of BOO is that its dynamic, so can be (easier) evalutated at runtime.
    http://boo.codehaus.org/
    Bug? That's not a bug, that's a feature.
    LinkedIn - Twitter (dutch) - Work (dutch)

  4. #4
    Join Date
    Nov 2001
    Location
    Dickson, TN
    Posts
    5,649
    Project-A, I just wanted to let you know that I really enjoy reading all of your R & D posts! Good stuff man! Keep it up!

    Buzz

  5. #5
    Join Date
    Jun 2004
    Location
    The Netherlands
    Posts
    153
    I hope I can post the whole source code tonight or tomorrow, I am working on RichTextArea for inside Unity3D, so the console looks somewhat prettier. Besides that I need to optimize it loads, using lots of strings and those are evil for memory! But the concept I have fully working in Unity3D:

    Dynamic Properties:
    Implementation:
    By adding the attribute [DynamicValue] attribute above a property you make that property changeable in game, the first parameter is the name of the setting you wanna bind to it for ingame. the second if the property is readonly (default false).
    This will make it able to change the value of that property at runtime of the game through the console. Static properties will be added to console automaticly.

    Usage ingame:
    set [name] [value]
    show [name]

    Command Execution:
    Implementation:
    By adding the attribute [ExecuteCommand] you can make a method executable from ingame. The first parameter you pass is an Regex to which the command input line should match. This is nice for loading another level or re-executing terrain generation or whatever you'd like.
    Static methods wont need to me initialized or added to the console, when console features loads it goes through the AppDomain and through all types looking for static methods using that attribute. All magic

    Usage ingame:
    It will be executed when the input matches the regex passed in the attribute.

    Example code:

    2 random classes, could be that Demo or Feature class, whatever you'd like:
    Code:
    public class Test1
    {
        [DynamicValue("bool")]
        public bool BooleanTest { get; set; }
    
        [DynamicValue("string", true)]
        public string StringTest { get; set; }
    
        [ExecuteCommand("^hello ")]
        public string ExecuteCommand(string input)
        {
            string name = input.Substring(6, input.Length-6);
            return "Hello, " + name + "!";
        }
    }
    
    public class Test2
    {
        [DynamicValue("static")]
        public static string Peter { get; set; }
    
        [ExecuteCommand("^loadlevel ")]
        public static string ExecuteCommand(string input)
        {
            // loading level ???
    
            return "I could have loaded a level!";
        }
    }
    This is a windows console implemenation I used for testing:
    Code:
    ConsoleFeature console = new ConsoleFeature();
    // Adding an instance of a class to the console feature. This is needed for non-static properties / methods.
    Test1 test = new Test1();
    console.AddInstance(test);
         
    // Initalize the class.
    console.Initialize();
          
    // Testing input!
    console.ExecuteCommand(" set bool true");
    console.ExecuteCommand("show bool ");
    console.ExecuteCommand("set bool 0 ");
    console.ExecuteCommand("show bool ");
    console.ExecuteCommand("set string 'string 2' ");
    console.ExecuteCommand("set static 'saasdh 2' ");
    console.ExecuteCommand("hello Peter");
    console.ExecuteCommand("loadlevel 1");
    And the result is:
    Code:
    Setting 'bool' was set to 'true'.
    Setting 'bool' is 'True'
    Setting 'bool' was set to 'false'.
    Setting 'bool' is 'False'
    Setting 'StringTest' is read-only.
    Setting 'static' was set to 'saasdh 2'.
    Hello, Peter!
    I could have loaded a level!
    Quote Originally Posted by busbyj View Post
    Project-A, I just wanted to let you know that I really enjoy reading all of your R & D posts! Good stuff man! Keep it up!

    Buzz
    Thanks, really enjoying it but need to finish the terrain video's before I run behind. After 3 days still stuck on the first one because I wonder off to other things
    Last edited by Project-A; 02-17-2011 at 11:39 AM.
    Bug? That's not a bug, that's a feature.
    LinkedIn - Twitter (dutch) - Work (dutch)

  6. #6
    Join Date
    Jun 2004
    Location
    The Netherlands
    Posts
    153
    Since my design of the DemoFramework is a bit different from what Lee is using, I will just post the POC (proof-of-concept) as I made it in a console application here in that state. Once I have the mindmap tool in a good state, I will go throught the terrain video's again and make a version what will work in the class framework too.

    You'll have to make your own GUI inside Unity for this, with a textarea for outputting stuff and a textfield for input and intergrate this in the ConsoleFeature. If anyone has trouble integrating it in Unity they can PM me if they like.

    This is un-optimized code, stuff like the response strings and such should be static fields and such so you dont recreate new strings each time. But other hand, when console if visible you could also pause the game. And before leaving console just do a GC.Collect(); A small hickup of few miliseconds wont matter at that point.

    The ConsoleFeature class.
    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Text.RegularExpressions;
    
    namespace ConsoleApplication1.Console
    {
        public class ConsoleFeature
        {
            private bool _initiated;
            public Dictionary<string, Func<string, string>> _commands;
            public List<object> _loadedObjects;
            public Dictionary<string, SettingStaticInfo> _settings;
    
            public ConsoleFeature()
            {
                _commands = new Dictionary<string, Func<string, string>>();
                _loadedObjects = new List<object>();
                _settings = new Dictionary<string, SettingStaticInfo>();
    
                this.AddInstance(this);
            }
    
            public void Initialize()
            {
                if (_initiated)
                    return;
    
                _initiated = true;
    
                // Handling instance variables.
                foreach (object obj in _loadedObjects)
                {
                     foreach (MethodInfo methodInfo in obj.GetType().GetMethods())
                    {
                        ExecuteCommandAttribute[] attributes = (ExecuteCommandAttribute[])methodInfo.GetCustomAttributes(typeof(ExecuteCommandAttribute), false);
                        if (attributes.Length > 0)
                        {
                            foreach (ExecuteCommandAttribute attribute in attributes)
                            {
                                _commands.Add(attribute.RegexInput, (Func<string, string>)Delegate.CreateDelegate(typeof(Func<string, string>), obj, methodInfo));
                            }
                        }
                    }
    
                    foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
                    {
                        DynamicValueAttribute[] attributes = (DynamicValueAttribute[])propertyInfo.GetCustomAttributes(typeof(DynamicValueAttribute), false);
                        if (attributes.Length > 0)
                        {
                            foreach (DynamicValueAttribute attribute in attributes)
                            {
                                if (!_settings.ContainsKey(attribute.SettingsName))
                                    _settings.Add(attribute.SettingsName, new SettingInstanceInfo(attribute.SettingsName, attribute.ReadOnly, true, propertyInfo.Name, obj));
                            }
                        }
                    }
                }
    
                // Handling static types.
                foreach (Type type in (this).GetType().Assembly.GetTypes())
                {
                    foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
                    {
                        ExecuteCommandAttribute[] attributes = (ExecuteCommandAttribute[])methodInfo.GetCustomAttributes(typeof(ExecuteCommandAttribute), false);
                        if (attributes.Length > 0)
                        {
                            foreach (ExecuteCommandAttribute attribute in attributes)
                            {
                                _commands.Add(attribute.RegexInput, (Func<string, string>)Delegate.CreateDelegate(typeof(Func<string, string>), methodInfo));
                            }
                        }
                    }
    
                    foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Static | BindingFlags.Public))
                    {
                        DynamicValueAttribute[] attributes = (DynamicValueAttribute[])propertyInfo.GetCustomAttributes(typeof(DynamicValueAttribute), false);
                        if (attributes.Length > 0)
                        {
                            foreach (DynamicValueAttribute attribute in attributes)
                            {
                                if (!_settings.ContainsKey(attribute.SettingsName))
                                    _settings.Add(attribute.SettingsName, new SettingStaticInfo(attribute.SettingsName, attribute.ReadOnly, true, propertyInfo.Name, type));
                            }
                        }
                    }
                }
            }
    
            public void AddInstance(object obj)
            {
                if (_initiated)
                    return;
    
                if (!_loadedObjects.Contains(obj))
                    _loadedObjects.Add(obj);
            }
    
            public string ExecuteCommand(string command)
            {
                command = command.Trim();
                foreach (KeyValuePair<string, Func<string, string>> kvp in _commands)
                {
                    if (Regex.IsMatch(command, kvp.Key, RegexOptions.IgnoreCase))
                    {
                        return kvp.Value(command);
                    }
                }
    
                return "Invalid command.";
            }
    
            static string getCommandSplitRegex = "^(show) +([^ ]+)$";
            [ExecuteCommand("^show ")]
            public string GetCommand(string input)
            {
                // layout of set commands is [set] [settingsname] [value]
                GroupCollection collection = Regex.Match(input, getCommandSplitRegex, RegexOptions.IgnoreCase).Groups;
    
                if (collection.Count == 3)
                {
                    // 0: whole string
                    // 1: set
                    // 2: settingname
                    if (_settings.ContainsKey(collection[2].Value))
                    {
                        SettingStaticInfo info = _settings[collection[2].Value];
                        object instance = null;
    
                        if (info.GetType() == typeof(SettingInstanceInfo))
                            instance = (info as SettingInstanceInfo).Instance;
    
                        Type propertyType = info.Type.GetProperty(info.Name).PropertyType;
    
                        try
                        {
                            object value = info.Type.GetProperty(info.Name).GetValue(instance, null);
                            return String.Format("Setting '{0}' is '{1}'", info.SettingName, value);
                        }
                        catch
                        {
                            return String.Format("Error showing '{0}'.", collection[2].Value);
                        }
                    }
                    return String.Format("Setting '{0}' was not found.", collection[2].Value);
                }
    
                return "Invalid show command.";
            }
    
            static string setCommandSplitRegex = "^(set) +([^ ]+) +(.+)$";
            [ExecuteCommand("^set ")]
            public string SetCommand(string input)
            {
                // layout of set commands is [set] [settingsname] [value]
                GroupCollection collection = Regex.Match(input, setCommandSplitRegex, RegexOptions.IgnoreCase).Groups;
    
                if (collection.Count == 4)
                {
                    // 0: whole string
                    // 1: set
                    // 2: settingname
                    // 3: value
                    if (_settings.ContainsKey(collection[2].Value))
                    {
                        SettingStaticInfo info = _settings[collection[2].Value];
    
                        if (info.ReadOnly)
                            return String.Format("Setting '{0}' is read-only.", info.Name);
    
                        object instance = null;
    
                        if (info.GetType() == typeof(SettingInstanceInfo))
                            instance = (info as SettingInstanceInfo).Instance;
    
                        Type propertyType = info.Type.GetProperty(info.Name).PropertyType;
                        string value = collection[3].Value;
    
                        // Prepare value for types.
                        if (propertyType == typeof(string))
                        {
                            if (value[0] == '\'' && value[value.Length - 1] == '\'')
                            {
                                value = value.Substring(1, value.Length - 2);
                            }
                            else
                                return "Input was wrong.";
                        }
                        else if (propertyType == typeof(bool))
                        {
                            if (String.Compare(value, "0", true) == 0 || String.Compare(value, "off", true) == 0)
                                value = "false";
                            else if (String.Compare(value, "1", true) == 0 || String.Compare(value, "on", true) == 0)
                                value = "true";
                        }
    
                        // Try cast input value to right type.
                        try
                        {
                            object o = Convert.ChangeType(value, propertyType);
                            info.Type.GetProperty(info.Name)
                                .SetValue(instance, o, null);
    
                            return String.Format("Setting '{0}' was set to '{1}'.", info.SettingName, value);
                        }
                        catch
                        {
                            return "Input was wrong.";
                        }
                    }
                    else
                        return String.Format("Setting '{0}' found.", collection[2].Value);
                }
    
                return "Invalid set command.";
            }
        }
    }
    DynamicValueAttribute class:
    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1.Console
    {
        [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
        public class DynamicValueAttribute : Attribute
        {
            #region Fields
    
            private string _settingsName;
            private bool _readOnly;
    
            #endregion
    
            #region Constructors
    
            /// <summary>
            /// Creates a new instance of <see cref="DynamicValueAttribute"/>.
            /// </summary>
            /// <param name="settingName">The name of the setting.</param>
            public DynamicValueAttribute(string settingName)
            {
                _settingsName = settingName;
            }
    
            /// <summary>
            /// Creates a new instance of <see cref="DynamicValueAttribute"/>.
            /// </summary>
            /// <param name="settingName">The name of the setting.</param>
            /// <param name="readOnly">Whether the property is read-only.</param>
            public DynamicValueAttribute(string settingName, bool readOnly)
                : this(settingName)
            {
                _readOnly = readOnly;
            }
    
            #endregion
    
            #region Properties
    
            /// <summary>
            /// Gets or sets the name of the setting.
            /// </summary>
            public string SettingsName
            {
                get { return _settingsName; }
                set { _settingsName = value; }
            }
    
            /// <summary>
            /// Gets or sets whether the setting is read-only.
            /// </summary>
            public bool ReadOnly
            {
                get { return _readOnly; }
                set { _readOnly = value; }
            }
    
            #endregion
        }
    }
    ExecuteCommandAttribute class
    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    
    namespace ConsoleApplication1.Console
    {
        [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
        public class ExecuteCommandAttribute : Attribute
        {
            #region Fields
            
            private string _regexInput;
    
            #endregion 
    
            #region Constructors
    
            /// <summary>
            /// Initializes a new instance of <see cref="ExecuteCommandAttribute"/>.
            /// </summary>
            /// <param name="regexInput">The regex pattern for matching console input.</param>
            public ExecuteCommandAttribute(string regexInput)
            {
                _regexInput = regexInput;
            }
    
            #endregion
    
            #region Properties
    
            /// <summary>
            /// Gets or sets the regex pattern for matching console input.
            /// </summary>
            public string RegexInput
            {
                get { return _regexInput; }
                set { _regexInput = value; }
            }
    
            #endregion
        }
    }
    SettingsInfo class
    Code:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1.Console
    {
        public class SettingStaticInfo
        {
            #region Fields
    
            public string SettingName;
            public bool ReadOnly;
            public bool IsProperty;
            public string Name;
            public Type Type;
            
            #endregion 
    
            #region Constructors
    
            /// <summary>
            /// Creates an new instance of <see cref="SettingStaticInfo"/>.
            /// </summary>
            /// <param name="settingName">The name of the setting.</param>
            /// <param name="readOnly">Whether the setting is read-only.</param>
            /// <param name="isProperty">Whether the setting is a property.</param>
            /// <param name="name">The name of the property of field.</param>
            /// <param name="type">The type of the class.</param>
            public SettingStaticInfo(string settingName, bool readOnly, bool isProperty, string name, Type type)
            {
                SettingName = settingName;
                ReadOnly = readOnly;
                IsProperty = isProperty;
                Name = name;
                Type = type;
            }
    
            #endregion
        }
    
        public class SettingInstanceInfo : SettingStaticInfo
        {
            #region Fields
    
            public object Instance;
    
            #endregion
    
            #region Constructors
    
            /// <summary>
            /// Creates an new instance of <see cref="SettingInstanceInfo"/>.
            /// </summary>
            /// <param name="settingName">The name of the setting.</param>
            /// <param name="readOnly">Whether the setting is read-only.</param>
            /// <param name="isProperty">Whether the setting is a property.</param>
            /// <param name="name">The name of the property of field.</param>
            /// <param name="type">The type of the class.</param>
            /// <param name="instance">An instance of the object of the property.</param>
            public SettingInstanceInfo(string settingName, bool readOnly, bool isProperty, string name, object instance)
                : base(settingName, readOnly, isProperty, name, instance.GetType())
            {
                Instance = instance;
            }
    
            #endregion
        }
    }
    The console application, for testing the stuff
    Code:
    using System;
    using ConsoleApplication1.Console;
    
    namespace ConsoleApplication1
    {
        public class Test1
        {
            [DynamicValue("bool")]
            public bool BooleanTest { get; set; }
    
            [DynamicValue("string", true)]
            public string StringTest { get; set; }
    
            [ExecuteCommand("^hello ")]
            public string ExecuteCommand(string input)
            {
                string name = input.Substring(6, input.Length - 6);
                return "Hello, " + name + "!";
    
            }
        }
    
        public class Test2
        {
            [DynamicValue("static")]
            public static string Peter { get; set; }
    
            [ExecuteCommand("^loadlevel ")]
            public static string ExecuteCommand(string input)
            {
                // loading level ???
    
                return "I could have loaded a level!";
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                ConsoleFeature console = new ConsoleFeature();
                Test1 test = new Test1();
                console.AddInstance(test);
                console.Initialize();
    
                System.Console.WriteLine(console.ExecuteCommand(" set bool true"));
                System.Console.WriteLine(console.ExecuteCommand("show bool "));
                System.Console.WriteLine(console.ExecuteCommand("set bool 0 "));
                System.Console.WriteLine(console.ExecuteCommand("show bool "));
                System.Console.WriteLine(console.ExecuteCommand("set string 'string 2' "));
                System.Console.WriteLine(console.ExecuteCommand("set static 'saasdh 2' "));
                System.Console.WriteLine(console.ExecuteCommand("hello adsfjhsak"));
                System.Console.WriteLine(console.ExecuteCommand("loadlevel 1"));
            }
        }
    }
    Last edited by Project-A; 02-24-2011 at 11:50 AM. Reason: Bug fixes
    Bug? That's not a bug, that's a feature.
    LinkedIn - Twitter (dutch) - Work (dutch)

  7. #7
    Join Date
    Jan 2008
    Posts
    10
    Interesting thread.
    Just scimming your code and wondering if these couple of things are typos? or perhaps i'm wrong and I missed the reasoning for them this way...

    In ConsoleFeature class:

    should
    Code:
                    if (_loadedTypes.Contains(obj.GetType()))
                        _loadedTypes.Add(obj.GetType());
    actually be
    Code:
                    if (!_loadedTypes.Contains(obj.GetType()))
                        _loadedTypes.Add(obj.GetType());
    otherwise I cannot see how this could ever evaluate to true.


    And should
    Code:
                         // Prepare value for types.
                        if (propertyType == typeof(string))
                        {
                            if (value[0] == '\'' && value[value.Length - 1] == '\'')
                            {
                                value = value.Substring(1, value.Length - 3);
                            }
    Actually be
    Code:
                         // Prepare value for types.
                        if (propertyType == typeof(string))
                        {
                            if (value[0] == '\'' && value[value.Length - 1] == '\'')
                            {
                                value = value.Substring(1, value.Length - 2);
                            }

    Shoot me if I'm wrong. Anyhoo love the idea of this.

  8. #8
    Join Date
    Jun 2004
    Location
    The Netherlands
    Posts
    153
    You are totally right, I fixed those already in my implementation but this was older code from my console. The _loadedTypes variable can be removed completely even. Edited the code in the post. Nicely catched!
    Last edited by Project-A; 02-24-2011 at 11:51 AM.
    Bug? That's not a bug, that's a feature.
    LinkedIn - Twitter (dutch) - Work (dutch)

  9. #9
    Join Date
    Feb 2011
    Location
    Vancouver, BC
    Posts
    24
    I am officially jealous of Project-A, fantastic work! I wish I had the time to be working on R&D projects as well.

  10. #10
    Join Date
    Mar 2007
    Location
    New York
    Posts
    33
    Quote Originally Posted by Waeric View Post
    I am officially jealous of Project-A, fantastic work! I wish I had the time to be working on R&D projects as well.
    That makes two of us.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •