2020/DebugConsole

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
m (Anticipated missuses of the code with empty or null filter options, at the start or during application execution)
m (anticipated the need for users to add log entry fields (perf: log entry by ref) + enforced robustness in regard to null or empty filter options because they will inevitably toy with it when doing their own versions)
 
Line 173: Line 173:
 
public struct Entry
 
public struct Entry
 
{
 
{
public int     category;
+
public int   category;
  
public string   date;
+
public string date;
  
public string   msg;
+
public string msg;
 
}
 
}
  
Line 255: Line 255:
  
  
             m_entries[ m_end ].category = category;
+
             ref Entry e = ref m_entries[ m_end ];
 +
 
 +
            e.category = category;
 
          
 
          
             m_entries[ m_end ].date     = date;
+
             e.date     = date;
  
             m_entries[ m_end ].msg     = msg;
+
             e.msg       = msg;
 
         }
 
         }
  
Line 266: Line 268:
 
         //************************************************************************************************
 
         //************************************************************************************************
  
         public Entry this[ int index ] { get { return m_entries[ index & ( m_capacity - 1 ) ]; } }
+
         public ref Entry this[ int index ] { get { return ref m_entries[ index & ( m_capacity - 1 ) ]; } }
 
     }
 
     }
  
Line 379: Line 381:
 
             if( m_expanded )
 
             if( m_expanded )
 
             {
 
             {
                 m_scrollRect = new Rect ( rect.x, rect.y + rect.height, rect.width, m_nbDisplayed * LINE_H );
+
                 if( m_nbOptions > 0 )
 +
                {
 +
                    m_scrollRect = new Rect ( rect.x, rect.y + rect.height, rect.width, m_nbDisplayed * LINE_H );
  
                m_viewRect  = new Rect ( 0.0f, 0.0f, m_scrollRect.width - ( m_nbDisplayed < m_nbOptions ? 15.0f : 0.0f ), m_nbOptions * LINE_H );
+
                    m_viewRect  = new Rect ( 0.0f, 0.0f, m_scrollRect.width - ( m_nbDisplayed < m_nbOptions ? 15.0f : 0.0f ), m_nbOptions * LINE_H );
 +
                }
 +
                else
 +
                {
 +
                    m_expanded = false;
 +
                }
 
             }
 
             }
 
         }
 
         }
Line 544: Line 553:
 
               public enum CATEGORY { INFO, WARNING, ERROR, SYSTEM, COUNT }
 
               public enum CATEGORY { INFO, WARNING, ERROR, SYSTEM, COUNT }
  
               private const string STR_HIDE    = "HIDE";
+
               private const string     STR_HIDE    = "HIDE";
                                               
+
                                                     
               private const string STR_SHOW    = "SHOW";
+
               private const string     STR_SHOW    = "SHOW";
 
+
                                       
               private const string STR_SORT_ASC = "RECENT FIRST";
+
               private const string     STR_SORT_ASC = "RECENT FIRST";
 
+
                                       
               private const string STR_SORT_DSC = "OLDER FIRST";
+
               private const string     STR_SORT_DSC = "OLDER FIRST";
 
+
                                       
               private const string STR_CLEAR    = "CLEAR";
+
               private const string     STR_CLEAR    = "CLEAR";
 
+
                                       
               private const int   CLIPING      = 1024;
+
               private const int         CLIPING      = 1024;
  
         static private string[]     SPLIT_SEMS  = new string[ 3 ] { "\r\n", "\r", "\n" };
+
         static private readonly string[] SPLIT_SEMS  = new string[ 3 ] { "\r\n", "\r", "\n" };
  
 
         //************************************************************************************************
 
         //************************************************************************************************
Line 562: Line 571:
 
         //************************************************************************************************
 
         //************************************************************************************************
  
         static private Color  m_colDate = new Color( 0.7f, 0.7f, 0.7f, 1.0f );
+
         static private         Color  m_colDate = new Color( 0.7f, 0.7f, 0.7f, 1.0f );
 +
                               
 +
        static private          Color  m_colInfo = new Color( 1.0f, 1.0f, 1.0f, 1.0f );
 +
                               
 +
        static private          Color  m_colWarn = new Color( 0.8f, 0.8f, 0.4f, 1.0f );
 +
                               
 +
        static private          Color  m_colErr  = new Color( 1.0f, 0.4f, 0.4f, 1.0f );
 +
                               
 +
        static private          Color  m_colSys  = new Color( 0.7f, 0.7f, 1.0f, 1.0f );
  
         static private Color  m_colInfo = new Color( 1.0f, 1.0f, 1.0f, 1.0f );
+
         static private readonly Color[] m_cols    = new Color[ ( int )CATEGORY.COUNT ] { m_colInfo, m_colWarn, m_colErr, m_colSys };
 
+
        static private Color  m_colWarn = new Color( 0.8f, 0.8f, 0.4f, 1.0f );
+
 
+
        static private Color  m_colErr  = new Color( 1.0f, 0.4f, 0.4f, 1.0f );
+
 
+
        static private Color  m_colSys  = new Color( 0.7f, 0.7f, 1.0f, 1.0f );
+
 
+
        static private Color[] m_cols    = new Color[ ( int )CATEGORY.COUNT ] { m_colInfo, m_colWarn, m_colErr, m_colSys };
+
  
 
         //************************************************************************************************
 
         //************************************************************************************************
Line 578: Line 587:
 
         //************************************************************************************************
 
         //************************************************************************************************
  
         static private object   m_lock      = new object();
+
         static private object           m_lock      = new object();
  
         static private Buffer   m_buffer    = new Buffer();
+
         static private readonly Buffer   m_buffer    = new Buffer();
  
         static private Buffer[] m_catBuffers = new Buffer[ ( int )CATEGORY.COUNT ] { new Buffer(), new Buffer(), new Buffer(), new Buffer() };
+
         static private readonly Buffer[] m_catBuffers = new Buffer[ ( int )CATEGORY.COUNT ] { new Buffer(), new Buffer(), new Buffer(), new Buffer() };
  
 
         //************************************************************************************************
 
         //************************************************************************************************
Line 588: Line 597:
 
         //************************************************************************************************
 
         //************************************************************************************************
  
         static private EventSystem m_eventSystem  = null;
+
         static private EventSystem         m_eventSystem  = null;
                                                 
+
                                                         
         static private GUISkin     m_skin        = null;
+
         static private GUISkin             m_skin        = null;
                                                 
+
                                                         
         static private GUISkin     m_skin_dd      = null;
+
         static private GUISkin             m_skin_dd      = null;
 
+
                                           
         static private bool         m_layout_dirty = false;
+
         static private bool                 m_layout_dirty = false;
                                   
+
                                           
         static private Vector2     m_pos          = Vector2.zero;
+
         static private Vector2             m_pos          = Vector2.zero;
                                                 
+
                                                         
         static private Vector2     m_size        = Vector2.zero;
+
         static private Vector2             m_size        = Vector2.zero;
                                                 
+
                                                         
         static private Vector2     m_headerSize  = Vector2.zero;
+
         static private Vector2             m_headerSize  = Vector2.zero;
                                                 
+
                                                         
         static private Vector2     m_scrollSize  = Vector2.zero;
+
         static private Vector2             m_scrollSize  = Vector2.zero;
                                                 
+
                                                         
         static private Vector2     m_scrollPos    = Vector2.zero;
+
         static private Vector2             m_scrollPos    = Vector2.zero;
                                                 
+
                                                         
         static private Rect         m_globalRect  = default( Rect );
+
         static private Rect                 m_globalRect  = default( Rect );
                                                 
+
                                                         
         static private Rect         m_headerRect  = default( Rect );
+
         static private Rect                 m_headerRect  = default( Rect );
                                                 
+
                                                         
         static private Rect         m_scrollRect  = default( Rect );
+
         static private Rect                 m_scrollRect  = default( Rect );
                                                 
+
                                                         
         static private bool         m_expanded    = true;
+
         static private bool                 m_expanded    = true;
                                                 
+
                                                         
         static private bool         m_ascending    = true;
+
         static private bool                 m_ascending    = true;
                                                 
+
                                                         
         static private CATEGORY     m_filter      = CATEGORY.COUNT;
+
         static private CATEGORY             m_filter      = CATEGORY.COUNT;
                                                 
+
                                               
         static private GUIDropDown m_dropDown   = new GUIDropDown( "INFOS", "WARNINGS", "ERRORS", "SYSTEM", "ALL" );
+
         static private readonly GUIDropDown m_dropDown     = new GUIDropDown( "INFOS", "WARNINGS", "ERRORS", "SYSTEM", "ALL" );
  
 
         //************************************************************************************************
 
         //************************************************************************************************
Line 874: Line 883:
 
                 for( int entry = firstVisible; entry <= lastVisible; ++entry )
 
                 for( int entry = firstVisible; entry <= lastVisible; ++entry )
 
                 {
 
                 {
                     dateRect.y = lineRect.y = entry * lineH;
+
                     dateRect.y     = lineRect.y = entry * lineH;
  
                     int   index = ( m_ascending ) ? start + ( ( count - 1 ) - entry ) : start + entry;
+
                     int       index = ( m_ascending ) ? start + ( ( count - 1 ) - entry ) : start + entry;
  
                     Entry Entry = buffer[ index ];
+
                     ref Entry ent  = ref buffer[ index ];
  
                     GUI.color   = m_colDate;                       GUI.Label( dateRect, Entry.date );
+
                     GUI.color       = m_colDate;                     GUI.Label( dateRect, ent.date );
  
                     GUI.color   = m_cols[ ( int )Entry.category ]; GUI.Label( lineRect, Entry.msg  );
+
                     GUI.color       = m_cols[ ( int )ent.category ]; GUI.Label( lineRect, ent.msg  );
 
                 }
 
                 }
  

Latest revision as of 14:36, 4 May 2020

Author: Serge Billault

Contents

[edit] Description

A more up to date, as of 2020 (Unity 5.6.1f1 through 2019.3.8.f1), a little more professional, minimal debug console.

[edit] Archive File

UnityDebugConsole

[edit] Archive content

A c# script.
A Resources folder containing a GUI skin.

[edit] Usage

. Installation: Unzip the archive content in your Standard Assets folder (required).


. Setup: no action required.


. Scripting: no action required.


. Unity Legacy Compatibility (old VS new Unity Input System): no action required.

[edit] Professionals

. Net compatibility level: 2.0 minimum - no dynamic methods.


. Unsafe code: None.


. Reflectivity: Some.


. Networking: None.


. QA Teams functions support: None.


. Advanced features: None.


. Performances: Suitable for productions (translation for students: High).

[edit] C# - DebugConsole.cs

The script must be named DebugConsole.cs. It has no real use. It is for compliance with other wiki articles only. The script is available through the archive linked at the top of this page.


using System.Reflection;
using System.Diagnostics;
using System.Collections.Generic;
 
//********************************************************************************************************
//
//********************************************************************************************************
 
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
 
//********************************************************************************************************
//
//********************************************************************************************************
 
namespace DEBUG
{
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    public enum TRACE { NONE, SHORT, FULL, DEFAULT = NONE }
 
	//****************************************************************************************************
	//
	//****************************************************************************************************
 
	static public class Debug
	{
		//************************************************************************************************
		//
		//************************************************************************************************
 
		private const string FMT_TRACE  = "  * {0} : line {1} : {2}()\r\n";
 
		private const char   MEMBER_SEP = '.';
 
		//************************************************************************************************
		//
		//************************************************************************************************
 
		static private string QualifiedMethodName( StackFrame frame )
		{
			return frame.GetMethod().DeclaringType.Name + MEMBER_SEP + frame.GetMethod().Name;
		}
 
		//************************************************************************************************
		//
		//************************************************************************************************
 
		static private string Msg( string msg, TRACE trace = TRACE.DEFAULT )
		{
			switch( trace )
			{
				//****************************************************************************************
				//
				//****************************************************************************************
 
				case TRACE.FULL:
				{
					StackTrace   full   = new StackTrace ( 2 , true );
 
					StackFrame[] frames = full.GetFrames();
 
					string       output = string.Empty;
 
					for( int frame = frames.Length - 1; frame >= 0; --frame )
					{
						StackFrame Frame = frames[ frame ];
 
						output += string.Format( FMT_TRACE, Frame.GetFileName(), Frame.GetFileLineNumber(), QualifiedMethodName( Frame ) );
					}
 
					return output + msg;
				}
 
				//****************************************************************************************
				//
				//****************************************************************************************
 
				case TRACE.SHORT:
				{
					StackTrace stack = new StackTrace( 2 , true );
 
					StackFrame Frame = stack.GetFrame( 0 );
 
					return string.Format( FMT_TRACE, Frame.GetFileName(), Frame.GetFileLineNumber(), QualifiedMethodName( Frame ) ) + msg;
				}
 
				//****************************************************************************************
				//
				//****************************************************************************************
 
				default :
				{
					return msg;
				}
			}
		}
 
		//************************************************************************************************
		//
		//************************************************************************************************
 
		static public void Log       ( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.Log       ( Msg( msg.ToString(), trace ) ); }
 
		static public void LogWarning( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogWarning( Msg( msg.ToString(), trace ) ); }
 
		static public void LogError  ( object msg, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogError  ( Msg( msg.ToString(), trace ) ); }
 
		//************************************************************************************************
		//
		//************************************************************************************************
 
		static public void Log       ( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.Log       ( Msg( msg.ToString(), trace ), context ); }
 
		static public void LogWarning( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogWarning( Msg( msg.ToString(), trace ), context ); }
 
		static public void LogError  ( object msg, UnityEngine.Object context, TRACE trace = TRACE.DEFAULT ) { if( UnityEngine.Debug.isDebugBuild ) UnityEngine.Debug.LogError  ( Msg( msg.ToString(), trace ), context ); }
	}
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
	public struct Entry
	{
		public int    category;
 
		public string date;
 
		public string msg;
	}
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    public class Buffer
    {
        //************************************************************************************************
        //
        //************************************************************************************************
 
        private Entry[] m_entries  = null;
 
        private int     m_capacity = 0;
 
        private int     m_start    = 0;
 
        private int     m_end      = 0;
 
        private int     m_count    = 0;
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public int capacity { get { return m_capacity; } }
 
        public int start    { get { return m_start;    } }
 
        public int end      { get { return m_end;      } }
 
        public int count    { get { return m_count;    } }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static public ulong Align( ulong s, ulong a ) { return ( s + ( a - 1 ) ) & ~( a - 1 ); }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public Buffer( int paramCapacity = 0 )
        {
            m_capacity = ( paramCapacity != 0 ) ? ( int )Align( ( ulong )Mathf.Max( 4, paramCapacity ), 4 ) : 1024;
 
            m_entries  = new Entry[ m_capacity ];
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public void Clear( )
        {
            m_start = 0;
 
            m_end   = 0;
 
            m_count = 0;
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public void Push( string date, string msg, int category )
        {
            if( m_count < m_capacity ) ++m_count;
 
            else      m_start = ( m_start + 1 )   & ( m_capacity - 1 );
 
            m_end = ( m_start + ( m_count - 1 ) ) & ( m_capacity - 1 );
 
 
            ref Entry e = ref m_entries[ m_end ];
 
            e.category  = category;
 
            e.date      = date;
 
            e.msg       = msg;
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public ref Entry this[ int index ] { get { return ref m_entries[ index & ( m_capacity - 1 ) ]; } }
    }
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    static public class GUIStacks
    {
        static private readonly Stack< GUISkin > m_skin  = new Stack< GUISkin >( 16 );
 
        static private readonly Stack< Color   > m_color = new Stack< Color   >( 16 );
 
        static private readonly Stack< Color   > m_cont  = new Stack< Color   >( 16 );
 
        static private readonly Stack< Color   > m_bgnd  = new Stack< Color   >( 16 );
 
 
        static public void PushSkin           ( GUISkin skin  ) { m_skin.Push ( GUI.skin );            GUI.skin            = skin;  }
 
        static public void PushColor          ( Color   color ) { m_color.Push( GUI.color );           GUI.color           = color; }
 
        static public void PushContentColor   ( Color   color ) { m_cont.Push ( GUI.contentColor );    GUI.contentColor    = color; }
 
        static public void PushBackgroundColor( Color   color ) { m_bgnd.Push ( GUI.backgroundColor ); GUI.backgroundColor = color; }
 
        static public void PushSkin           () { m_skin.Push ( GUI.skin );            }
 
        static public void PushColor          () { m_color.Push( GUI.color );           }
 
        static public void PushContentColor   () { m_cont.Push ( GUI.contentColor );    }
 
        static public void PushBackgroundColor() { m_bgnd.Push ( GUI.backgroundColor ); }
 
        static public void PopSkin            () { GUI.skin            = m_skin.Pop (); }
 
        static public void PopColor           () { GUI.color           = m_color.Pop(); }
 
        static public void PopContentColor    () { GUI.contentColor    = m_cont.Pop (); }
 
        static public void PopBackgroundColor () { GUI.backgroundColor = m_bgnd.Pop (); }
    }
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    public class GUIDropDown
    {
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private Color COLOR_NORMAL   = new Color( 0.6f, 0.6f, 0.6f, 1.0f );
 
        static private Color COLOR_SELECTED = new Color( 1.0f, 1.0f, 1.0f, 1.0f );
 
        private const int    MAX_LINES      = 8;
 
        private const float  LINE_H         = 32.0f;
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        private Rect     m_selectionRect = default( Rect );
 
        private Rect     m_scrollRect    = default( Rect );
 
        private Rect     m_viewRect      = default( Rect );
 
        private object[] m_options       = null;
 
        private int      m_nbOptions     = 0;
 
        private int      m_nbDisplayed   = 0;
 
        private bool     m_expanded      = false;
 
        private Vector2  m_scroll        = Vector2.zero;
 
        private int      m_selected      = 0;
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public GUIDropDown( params object[] options )
        {
            m_options = options;
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        private void UpdateLayout( Rect rect, int selected, object[] options )
        {
            if( Event.current.type != EventType.Layout ) return;
 
            m_options       = options;
 
            m_nbOptions     = ( options != null ) ? options.Length : 0;
 
            m_nbDisplayed   = Mathf.Min( m_nbOptions, MAX_LINES );
 
            m_selected      = Mathf.Min( m_nbOptions - 1, selected );
 
            m_selectionRect = rect;
 
 
            if( m_expanded )
            {
                if( m_nbOptions > 0 )
                {
                    m_scrollRect = new Rect ( rect.x, rect.y + rect.height, rect.width, m_nbDisplayed * LINE_H );
 
                    m_viewRect   = new Rect ( 0.0f, 0.0f, m_scrollRect.width - ( m_nbDisplayed < m_nbOptions ? 15.0f : 0.0f ), m_nbOptions * LINE_H );
                }
                else
                {
                    m_expanded = false;
                }
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        private void DrawOptions( GUISkin skin )
        {
            if( m_expanded )
            {
                GUI.skin = skin;
 
                int firstVisible = ( int )( m_scroll.y / LINE_H );
 
                int lastVisible  = Mathf.Clamp( firstVisible + m_nbDisplayed, 0, m_nbOptions - 1 );
 
 
                GUIStacks.PushContentColor();
 
                m_scroll = GUI.BeginScrollView( m_scrollRect, m_scroll, m_viewRect, false, false );
 
                for( int option = firstVisible; option <= lastVisible; ++option )
                {
                    GUI.contentColor = ( option == m_selected ) ? COLOR_SELECTED : COLOR_NORMAL;
 
                    if( GUI.Button( new Rect( 0.0f, option * LINE_H, m_viewRect.width, LINE_H ), m_options[ option ].ToString() ) )
                    {
                        m_selected = option;
 
                        m_expanded = false;
                    }
                }
 
                GUI.EndScrollView();
 
                GUIStacks.PopContentColor();
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        private void DrawSelection( GUISkin skin )
        {
            GUI.skin = skin;
 
            object selection    = ( m_nbOptions  >  0 ) ? m_options[ m_selected ]  : null;
 
            string selectionStr = ( selection != null ) ? selection.ToString() : "<NULL>";
 
            if( GUI.Button( m_selectionRect, selectionStr ) && ( m_nbOptions > 0 ) )
            {
                m_expanded = ! m_expanded;
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        public int Draw( GUISkin btn_skin, GUISkin drop_down_skin, Rect rect, int selected, object[] options = null )
        {
            GUIStacks.PushSkin();
 
                UpdateLayout ( rect, selected, ( options != null ) ? options : m_options );
 
                DrawSelection( btn_skin );
 
                DrawOptions  ( drop_down_skin );
 
            GUIStacks.PopSkin();
 
            return m_selected;
        }
    }
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    static public class Console
    {
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static public List< MethodInfo > GetApplicationProperties()
        {
            List< MethodInfo > result = new List< MethodInfo >( 64 );
 
            System.Type        type   = typeof( Application );
 
            PropertyInfo[]     props  = type.GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static );
 
            for( int pr = 0; pr < props.Length; ++pr )
            {
                PropertyInfo prop = props[ pr ];
 
                MethodInfo   func = prop.GetGetMethod();
 
                if( func != null ) result.Add( func );
            }
 
            return result;
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        [ RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad ) ] static private void OnBeforeSceneLoadRuntimeMethod()
        {
            if( UnityEngine.Debug.isDebugBuild )
            {
                SceneManager.sceneLoaded += OnSceneLoaded;
 
 
 
                Scene  scene      = SceneManager.GetActiveScene();
 
                string scene_name = ( scene != null ) ? scene.name : "None";
 
                Console.LogSystem( string.Format( "Console init from scene: {0}", scene_name ) );
 
 
 
                List< MethodInfo > properties = GetApplicationProperties();
 
                for( int pr = 0; pr < properties.Count; ++pr )
                {
                    MethodInfo  prop  = properties[ pr ];
 
                    string      value = prop.Invoke( null, null ).ToString();
 
                    Console.LogSystem( string.Format( "Application.{0}: {1}", prop.Name.Substring( 4 ), value ) );
                }
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void OnSceneLoaded( Scene scene, LoadSceneMode mode )
        {
            if( DebugConsole.Instance == null )
            {
                GameObject console = new GameObject( "DebugConsole", typeof( DebugConsole ) );
 
                if( console != null ) UnityEngine.Object.DontDestroyOnLoad( console );
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
               public enum CATEGORY { INFO, WARNING, ERROR, SYSTEM, COUNT }
 
               private const string      STR_HIDE     = "HIDE";
 
               private const string      STR_SHOW     = "SHOW";
 
               private const string      STR_SORT_ASC = "RECENT FIRST";
 
               private const string      STR_SORT_DSC = "OLDER FIRST";
 
               private const string      STR_CLEAR    = "CLEAR";
 
               private const int         CLIPING      = 1024;
 
        static private readonly string[] SPLIT_SEMS   = new string[ 3 ] { "\r\n", "\r", "\n" };
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private          Color   m_colDate = new Color( 0.7f, 0.7f, 0.7f, 1.0f );
 
        static private          Color   m_colInfo = new Color( 1.0f, 1.0f, 1.0f, 1.0f );
 
        static private          Color   m_colWarn = new Color( 0.8f, 0.8f, 0.4f, 1.0f );
 
        static private          Color   m_colErr  = new Color( 1.0f, 0.4f, 0.4f, 1.0f );
 
        static private          Color   m_colSys  = new Color( 0.7f, 0.7f, 1.0f, 1.0f );
 
        static private readonly Color[] m_cols    = new Color[ ( int )CATEGORY.COUNT ] { m_colInfo, m_colWarn, m_colErr, m_colSys };
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private object            m_lock       = new object();
 
        static private readonly Buffer   m_buffer     = new Buffer();
 
        static private readonly Buffer[] m_catBuffers = new Buffer[ ( int )CATEGORY.COUNT ] { new Buffer(), new Buffer(), new Buffer(), new Buffer() };
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private EventSystem          m_eventSystem  = null;
 
        static private GUISkin              m_skin         = null;
 
        static private GUISkin              m_skin_dd      = null;
 
        static private bool                 m_layout_dirty = false;
 
        static private Vector2              m_pos          = Vector2.zero;
 
        static private Vector2              m_size         = Vector2.zero;
 
        static private Vector2              m_headerSize   = Vector2.zero;
 
        static private Vector2              m_scrollSize   = Vector2.zero;
 
        static private Vector2              m_scrollPos    = Vector2.zero;
 
        static private Rect                 m_globalRect   = default( Rect );
 
        static private Rect                 m_headerRect   = default( Rect );
 
        static private Rect                 m_scrollRect   = default( Rect );
 
        static private bool                 m_expanded     = true;
 
        static private bool                 m_ascending    = true;
 
        static private CATEGORY             m_filter       = CATEGORY.COUNT;
 
        static private readonly GUIDropDown m_dropDown     = new GUIDropDown( "INFOS", "WARNINGS", "ERRORS", "SYSTEM", "ALL" );
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static Console()
        {
            LoadResources();
 
            if( UnityEngine.Debug.isDebugBuild )
            {
                Application.logMessageReceivedThreaded += LogCallback;
 
                Application.lowMemory                  += LowMemoryCallback;
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void LoadResources()
        {
            if( m_skin    == null ) { m_skin    = Resources.Load< GUISkin >( "DebugConsoleSkin"         ); }
 
            if( m_skin_dd == null ) { m_skin_dd = Resources.Load< GUISkin >( "DebugConsoleDropDownSkin" ); }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void Clear()
        {
            lock( m_lock )
            {
                m_buffer.Clear();
 
                for( int buf = 0; buf < m_catBuffers.Length; ++buf ) m_catBuffers[ buf ].Clear();
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void LowMemoryCallback()
        {
            Log( "LOW MEMORY", CATEGORY.ERROR );
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void LogCallback( string condition, string stackTrace, LogType type )
        {
            bool   trace = ( type != LogType.Log );
 
            string entry = ( trace ) ? stackTrace + condition : condition;
 
            if     ( type == LogType.Log     ) Log( entry, CATEGORY.INFO    );
 
            else if( type == LogType.Warning ) Log( entry, CATEGORY.WARNING );
 
            else                               Log( entry, CATEGORY.ERROR   );
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static public void LogInfo   ( string entry ) { Log( entry, CATEGORY.INFO    ); }
 
        static public void LogWarning( string entry ) { Log( entry, CATEGORY.WARNING ); }
 
        static public void LogError  ( string entry ) { Log( entry, CATEGORY.ERROR   ); }
 
        static public void LogSystem ( string entry ) { Log( entry, CATEGORY.SYSTEM  ); }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static public void Log( string entry, CATEGORY category = CATEGORY.INFO )
        {
            if( UnityEngine.Debug.isDebugBuild == false ) return;
 
            if( string.IsNullOrEmpty( entry ) )           return;
 
            if( category >= CATEGORY.COUNT    )           return;
 
 
            string[] entries = entry.Split( SPLIT_SEMS, System.StringSplitOptions.RemoveEmptyEntries );
 
            for( int ent = 0; ent < entries.Length; ++ent ) LogEntry( entries[ ent ], category );
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void LogEntry( string entry, CATEGORY category )
        {
            if( entry.Length > CLIPING ) entry = entry.Substring( 0, CLIPING );
 
            string date = System.DateTime.Now.ToString();
 
 
            lock( m_lock )
            {
                m_buffer.Push                       ( date, entry, ( int )category );
 
                m_catBuffers[ ( int )category ].Push( date, entry, ( int )category );
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void ConsumeMouseEvents()
        {
            #if ENABLE_INPUT_SYSTEM
 
                Vector2 pointerPos = UnityEngine.InputSystem.Pointer.current.position.ReadValue();
 
            #else
 
                Vector2 pointerPos = Input.mousePosition;
 
            #endif
 
 
            Vector2 mousePos = new Vector2( pointerPos.x, Screen.height - pointerPos.y );
 
            bool    focused  = m_globalRect.Contains( mousePos );
 
            if( ( Event.current.isMouse ) && ( focused ) ) Event.current.Use();
 
 
            if( EventSystem.current != null ) m_eventSystem         = EventSystem.current;
 
            if( m_eventSystem       != null ) m_eventSystem.enabled = ( focused == false );
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void UpdateLayout()
        {
            if( Event.current.type != EventType.Layout ) return;
 
            Vector2 size = new Vector2( Screen.width * ( Application.isMobilePlatform ? 1.0f : 0.5f ), Screen.height * 0.5f );
 
            if( ( m_size != size ) || ( m_layout_dirty ) )
            {
                m_size         = size;
 
                m_layout_dirty = false;
 
 
                m_headerSize   = new Vector2( m_size.x, 24.0f );
 
                m_scrollSize   = new Vector2( m_size.x, ( m_expanded ) ? ( m_size.y - m_headerSize.y ) : 0.0f );
 
                m_pos          = new Vector2( 0.0f,     ( m_expanded ) ? ( Screen.height - m_size.y  ) : Screen.height - m_headerSize.y );
 
 
                m_headerRect   = new Rect   ( m_pos.x, m_pos.y,                  m_headerSize.x, m_headerSize.y );
 
                m_scrollRect   = new Rect   ( m_pos.x, m_pos.y + m_headerSize.y, m_scrollSize.x, m_scrollSize.y );
 
                m_globalRect   = new Rect   ( m_pos.x, m_pos.y, m_size.x, m_size.y );
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void DrawHeader()
        {
            const int nbButtons = 5;
 
            float     buttonsW  = m_headerSize.x / nbButtons;
 
            Rect      butRect   = new Rect( m_headerRect.x, m_headerRect.y, buttonsW, m_headerSize.y );
 
 
            if( GUI.Button( butRect, ( m_expanded ) ? STR_HIDE : STR_SHOW  ) ) 
            { 
                m_expanded = ( ! m_expanded ); 
 
                m_layout_dirty = true; 
            }
 
 
            if( m_expanded )
            {
                butRect.x += buttonsW; if( GUI.Button( butRect, STR_SORT_ASC ) ) m_ascending =  true;
 
                butRect.x += buttonsW; if( GUI.Button( butRect, STR_SORT_DSC ) ) m_ascending = false;
 
                butRect.x += buttonsW; if( GUI.Button( butRect, STR_CLEAR    ) ) Clear(); 
 
                butRect.x += buttonsW; 
 
 
                m_filter = ( CATEGORY )m_dropDown.Draw( m_skin, m_skin_dd, butRect, ( int )m_filter );
            }
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static private void DrawMessages()
        {
            const float dateW    = 140.0f;
 
            const float lineH    =  21.0f;
 
            Buffer      buffer   = ( ( m_filter < 0 ) || ( m_filter >= CATEGORY.COUNT ) ) ? m_buffer : m_catBuffers[ ( int )m_filter ];
 
            int         count    = buffer.count;
 
            Rect        viewRect = new Rect( 0.0f,  0.0f, Screen.width, lineH * count );
 
 
 
            GUI.Box( m_scrollRect, ( Texture )null );
 
            m_scrollPos = GUI.BeginScrollView( m_scrollRect, m_scrollPos, viewRect, false, true );
 
            if( Event.current.type == EventType.Repaint )
            {
                Rect  dateRect     = new Rect( 0.0f,  0.0f,                  dateW, lineH );
 
                Rect  lineRect     = new Rect( dateW, 0.0f, viewRect.width - dateW, lineH );
 
                int   maxVisibles  = ( int )( m_scrollRect.height / lineH ) + 1;
 
                int   firstVisible = ( int )( m_scrollPos.y       / lineH );
 
                int   lastVisible  = firstVisible + ( Mathf.Min( maxVisibles, count ) - 1 );
 
                int   start        = buffer.start;
 
 
                GUIStacks.PushColor();
 
                for( int entry = firstVisible; entry <= lastVisible; ++entry )
                {
                    dateRect.y      = lineRect.y = entry * lineH;
 
                    int       index = ( m_ascending ) ? start + ( ( count - 1 ) - entry ) : start + entry;
 
                    ref Entry ent   = ref buffer[ index ];
 
                    GUI.color       = m_colDate;                     GUI.Label( dateRect, ent.date );
 
                    GUI.color       = m_cols[ ( int )ent.category ]; GUI.Label( lineRect, ent.msg  );
                }
 
                GUIStacks.PopColor();
            }
 
            GUI.EndScrollView ();
        }
 
        //************************************************************************************************
        //
        //************************************************************************************************
 
        static public void OnGUI()
        {
            if( UnityEngine.Debug.isDebugBuild == false ) return;
 
            GUIStacks.PushSkin( m_skin );
 
            UpdateLayout();
 
            if( m_expanded )
            {
                lock( m_lock ) { DrawMessages(); }
            }
 
            DrawHeader();
 
            ConsumeMouseEvents();
 
            GUIStacks.PopSkin ();
        }
    }
 
    //****************************************************************************************************
    //
    //****************************************************************************************************
 
    public class DebugConsole : MonoBehaviour
    {
        static public DebugConsole Instance { get; private set; }
 
        private void Awake    () { if( Instance == null ) Instance = this; }
 
        private void OnDestroy() { if( Instance == this ) Instance = null; }
 
        public void  OnGUI    () { if( Instance == this ) DEBUG.Console.OnGUI(); }
    }
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox