Add some basic features of this toolkit.

feat: Logg was almost done.
fix: Some impossible call was fixed in UnityObjectWarp.cs
feat: Boxing<T> was force to unbox by explicit cast operator.
This commit is contained in:
Ca2didi 2024-05-10 00:55:42 +08:00
parent 91c24b27e8
commit 0315862527
13 changed files with 491 additions and 520 deletions

View File

@ -7,28 +7,43 @@ namespace Ca2d.Toolkit
public static class AsDisposableExtension
{
/// <summary>
/// Convert an <see cref="System.Action"/> to a IDisposable callback.
/// </summary>
public static AsDisposable AsDisposable(Action callback)
{
return new AsDisposable(callback);
}
/// <summary>
/// Convert an <see cref="System.Action"/> to a IDisposable callback.
/// </summary>
public static AsAsyncDisposable AsAsyncDisposable(Func<UniTask> callback)
{
return new AsAsyncDisposable(callback);
}
/// <summary>
/// Convert an <see cref="System.Action"/> to a IDisposable callback in chain style.
/// </summary>
public static AsDisposable<T> AsDisposable<T>(this T target, Action<T> callback)
{
return new AsDisposable<T>(callback, target);
}
/// <summary>
/// Convert an <see cref="System.Action"/> to a IDisposable callback in chain style.
/// </summary>
public static AsAsyncDisposable<T> AsAsyncDisposable<T>(this T target, Func<T, UniTask> callback)
{
return new AsAsyncDisposable<T>(callback, target);
}
}
/// <summary>
/// An operation container for <see cref="IDisposable"/>.
/// </summary>
public readonly struct AsDisposable : IDisposable
{
private readonly Action m_callback;
@ -49,6 +64,10 @@ namespace Ca2d.Toolkit
}
}
/// <summary>
/// An operation container for <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TParam">Object select source.</typeparam>
public readonly struct AsDisposable<TParam> : IDisposable
{
private readonly Action<TParam> m_callback;
@ -73,6 +92,9 @@ namespace Ca2d.Toolkit
}
}
/// <summary>
/// An operation container for <see cref="IDisposable"/>.
/// </summary>
public readonly struct AsAsyncDisposable : IAsyncDisposable
{
private readonly Func<UniTask> m_callback;
@ -94,6 +116,10 @@ namespace Ca2d.Toolkit
}
}
/// <summary>
/// An operation container for <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TParam">Object select source.</typeparam>
public readonly struct AsAsyncDisposable<TParam> : IAsyncDisposable
{
private readonly Func<TParam, UniTask> m_callback;

View File

@ -1,6 +1,5 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace Ca2d.Toolkit
{
@ -18,7 +17,7 @@ namespace Ca2d.Toolkit
/// </summary>
public Boxing(Boxing<T> source)
{
m_value = source.Unbox;
m_value = source.m_value;
}
/// <summary>
@ -30,24 +29,15 @@ namespace Ca2d.Toolkit
}
/// <summary>
/// Create a empty boxing.
/// Create an empty boxing.
/// </summary>
public Boxing()
{}
/// <summary>
/// Get a copy of boxing value.
/// Access to the boxing value directly in reference mode.
/// </summary>
public T Unbox
{
get => m_value;
set => m_value = value;
}
/// <summary>
/// Access to the boxing value directly.
/// </summary>
public ref T Direct => ref m_value;
public ref T Ref => ref m_value;
/// <summary>
/// Clean this boxing container to the default value of boxing target.
@ -62,9 +52,9 @@ namespace Ca2d.Toolkit
return m_value.ToString();
}
public static implicit operator T(Boxing<T> wrapper)
public static explicit operator T(Boxing<T> wrapper)
{
return wrapper.Unbox;
return wrapper.m_value;
}
public static implicit operator Boxing<T>(T value)

View File

@ -0,0 +1,43 @@
using UnityEngine;
namespace Ca2d.Toolkit
{
public enum LoggType
{
/// <summary>
/// <para>LogType used for Errors.</para>
/// </summary>
Error,
/// <summary>
/// <para>LogType used for Asserts. (These could also indicate an error inside Unity itself.)</para>
/// </summary>
Assert,
/// <summary>
/// <para>LogType used for Warnings.</para>
/// </summary>
Warning,
/// <summary>
/// <para>LogType used for regular log messages.</para>
/// </summary>
Log,
/// <summary>
/// <para>LogType used for Exceptions.</para>
/// </summary>
Exception,
/// <summary>
/// <para>LogType used for Developers on testing.</para>
/// </summary>
Debug
}
public interface ILoggHandler : ILogHandler
{
public ILogHandler InnerLogHandler { get; }
public void LoggFormat(LoggType logType, Object context, string format, params object[] args);
public void SetLabel(string label);
public void ClearLabel();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfec301646cc46339921f4c1c9170412
timeCreated: 1715271270

View File

@ -1,497 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace Ca2d.Toolkit
{
public readonly struct Log : IDisposable
{
private const string kNoLabelAndContextName = "Anonymous";
private const string kFallbackLabelName = "Fallback";
private static HashSet<int> _loopDetector = new();
private static StringBuilder _sb = new();
#region ManagedRegionOperations
private static Core _fallbackCore = new Core
{
ValidateNumber = 0,
ParentIndex = 0,
ParentValidateNumber = 0,
Context = null,
Label = kFallbackLabelName
};
private static List<Core> _core = new()
{
// This is the first element of AdvDebugCore and also as fallback options.
_fallbackCore
};
#region Allocation
private static List<Vector2Int> m_allocMap = new()
{
// The first element must be allocated.
new Vector2Int(0, 1)
};
private static bool RangeOverlap(Vector2Int a, Vector2Int b)
{
var offset = a.x - b.x;
switch (offset)
{
case < 0:
{
var o = -offset;
var lenA = a.y - a.x;
return o < lenA;
}
case > 0:
{
var o = offset;
var lenB = b.y - b.x;
return o < lenB;
}
default:
return true;
}
}
private static bool IsIdUsed(int id)
{
return m_allocMap.Exists(range => id >= range.x && id < range.y);
}
private static int GetUnusedId()
{
var alloc = m_allocMap[0].y;
if (alloc == int.MaxValue) throw new IndexOutOfRangeException("No free index can be exposed!");
return alloc;
}
public static bool AllocId(int id)
{
if (id <= 0 || id == int.MaxValue) return false;
var range = new Vector2Int(id, id + 1);
for (var i = 1; i < m_allocMap.Count; i++)
{
var r = m_allocMap[i];
var insert = i + 1;
if (m_allocMap.Count != insert && RangeOverlap(r, range)) continue;
// Get the reaction on merge.
var mergePrev = false;
var mergeNext = false;
var before = m_allocMap[insert - 1];
if (before.y == range.x) mergePrev = true;
var after = m_allocMap.Count > insert ? m_allocMap[insert] : default;
if (after.y != 0 && after.x == range.y) mergeNext = true;
if (mergePrev & mergeNext)
{
m_allocMap[insert - 1] = new Vector2Int(before.x, after.y);
m_allocMap.RemoveAt(insert);
}
else if (mergePrev)
{
m_allocMap[insert - 1] = new Vector2Int(before.x, range.y);
}
else if (mergeNext)
{
m_allocMap[insert] = new Vector2Int(range.x, after.y);
}
else
{
m_allocMap.Insert(insert, range);
}
return true;
}
return false;
}
public static bool FreeId(int id)
{
if (id <= 0 || id == int.MaxValue) return false;
var range = new Vector2Int(id, id + 1);
var idx = m_allocMap.FindIndex(r => RangeOverlap(r, range));
if (idx < 0) return false;
// Get reaction on split
var insertLeft = true;
var insertRight = true;
var splitTarget = m_allocMap[id];
if (splitTarget.x == range.x) insertLeft = false;
if (splitTarget.y == range.y) insertRight = false;
if (insertLeft || insertRight)
{
if (insertLeft)
{
m_allocMap[idx] = new Vector2Int(splitTarget.x, range.x);
}
if (insertRight)
{
m_allocMap.Insert(idx + 1, new Vector2Int(range.y, splitTarget.y));
}
}
else
{
m_allocMap.RemoveAt(idx);
}
return true;
}
#endregion
private struct Capture
{
public readonly ulong CaptureValidateNumber;
public readonly int ResourceIndex;
public object Context
{
get
{
if (IsValid()) return _core[ResourceIndex].Context;
return _fallbackCore.Context;
}
}
public string Label
{
get
{
if (IsValid()) return _core[ResourceIndex].Label;
return _fallbackCore.Label;
}
}
public bool TryResolveParent(out Capture capture, bool includeFallback = false)
{
if (IsValid())
{
capture = new Capture(_core[ResourceIndex].ParentIndex);
if (capture.IsFallback()) return includeFallback;
return capture.CaptureValidateNumber == _core[ResourceIndex].ParentValidateNumber;
}
capture = default;
return includeFallback;
}
public Capture(int idx)
{
if (idx >= _core.Count || idx <= 0)
{
CaptureValidateNumber = default;
ResourceIndex = default;
return;
}
ResourceIndex = idx;
CaptureValidateNumber = _core[idx].ValidateNumber;
}
public bool IsFallback()
{
return ResourceIndex == 0;
}
public bool IsValid()
{
var resIdx = ResourceIndex;
if (resIdx == 0) return true;
if (resIdx >= _core.Count || resIdx < 0) return false;
return _core[resIdx].ParentValidateNumber == CaptureValidateNumber && IsIdUsed(resIdx);
}
}
private struct Core
{
public ulong ValidateNumber;
public ulong ParentValidateNumber;
public int ParentIndex;
public object Context;
public string Label;
}
private static bool RequestCore(out Capture capture, string label, object context, int parentId)
{
var id = GetUnusedId();
if (AllocId(id))
{
// Make sure there has valid space to allocate.
while (_core.Count - 1 < id) _core.Add(default);
var c = _core[id];
// Set context of current Debug.
c.Context = context;
c.Label = label;
// Check is given parent id is valid and fill result.
c.ParentIndex = parentId != 0 && IsIdUsed(parentId) ? parentId : 0;
c.ParentValidateNumber = c.ParentIndex == 0 ? 0 : _core[c.ParentIndex].ValidateNumber;
_core[id] = c;
capture = new Capture(id);
return true;
}
capture = default;
return false;
}
private static bool ReturnCore(Capture capture)
{
if (capture.IsFallback() || !capture.IsValid()) return false;
if (!FreeId(capture.ResourceIndex)) return false;
var core = _core[capture.ResourceIndex];
core.ValidateNumber += 1;
core.ParentValidateNumber = default;
core.ParentIndex = default;
core.Context = null;
core.Label = kFallbackLabelName;
_core[capture.ResourceIndex] = core;
return true;
}
#endregion
#region ExposeValue
public bool IsFallback => m_capture.IsFallback();
public string Label => m_capture.Label;
public object Context => m_capture.Context;
public Log? Parent
{
get
{
if (m_capture.TryResolveParent(out var cap, false))
return new Log(cap);
return null;
}
}
#endregion
#region Lifecircle
private readonly Capture m_capture;
private Log(Capture capture)
{
m_capture = capture;
}
public Log(Log parent = default)
{
if (!RequestCore(out var capture, kNoLabelAndContextName, null, parent.m_capture.ResourceIndex))
{
m_capture = default;
return;
}
m_capture = capture;
}
public Log(string label, Log parent = default)
{
if (string.IsNullOrWhiteSpace(label)) label = kNoLabelAndContextName;
if (!RequestCore(out var capture, label, null, parent.m_capture.ResourceIndex))
{
m_capture = default;
return;
}
m_capture = capture;
}
public Log(object context, Log parent = default)
{
var label = context == null ? kNoLabelAndContextName : context.GetType().Name;
if (!RequestCore(out var capture, label, null, parent.m_capture.ResourceIndex))
{
m_capture = default;
return;
}
m_capture = capture;
}
public Log(string label, object context, Log parent = default)
{
if (string.IsNullOrWhiteSpace(label)) label = context == null ? kNoLabelAndContextName : context.GetType().Name;
if (!RequestCore(out var capture, label, null, parent.m_capture.ResourceIndex))
{
m_capture = default;
return;
}
m_capture = capture;
}
public void Dispose()
{
if (!FreeId(m_capture.ResourceIndex) || !ReturnCore(m_capture)) throw new InvalidOperationException(
"You can not trying to dispose an outdated or default AdvLogger!");
}
#endregion
public void Debug(string text)
{
}
public void Info(string text)
{}
public void Warning(string text)
{}
public void Error(string text)
{}
public void Error(Exception err)
{}
}
public static class QLog
{
#region (string)
public static void Debug(string text)
{
default(Log).Debug(text);
}
public static void Info(string text)
{
default(Log).Info(text);
}
public static void Warning(string text)
{
default(Log).Warning(text);
}
public static void Error(string text)
{
default(Log).Error(text);
}
#endregion
#region (string, string)
public static void Debug(string text, string label)
{
using var logger = new Log(label);
logger.Debug(text);
}
public static void Info(string text, string label)
{
using var logger = new Log(label);
logger.Info(text);
}
public static void Warning(string text, string label)
{
using var logger = new Log(label);
logger.Warning(text);
}
public static void Error(string text, string label)
{
using var logger = new Log(label);
logger.Error(text);
}
#endregion
#region (string, object)
public static void Debug(string text, object context)
{
using var logger = new Log(context);
logger.Debug(text);
}
public static void Info(string text, object context)
{
using var logger = new Log(context);
logger.Info(text);
}
public static void Warning(string text, object context)
{
using var logger = new Log(context);
logger.Warning(text);
}
public static void Error(string text, object context)
{
using var logger = new Log(context);
logger.Error(text);
}
#endregion
#region (string, string, object)
public static void Debug(string text, string label, object context)
{
using var logger = new Log(label, context);
logger.Info(text);
}
public static void Info(string text, string label, object context)
{
using var logger = new Log(label, context);
logger.Info(text);
}
public static void Warning(string text, string label, object context)
{
using var logger = new Log(label, context);
logger.Warning(text);
}
public static void Error(string text, string label, object context)
{
using var logger = new Log(label, context);
logger.Error(text);
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3b5d1b48628646eebc9e64c30bd93ae8
timeCreated: 1713286134

282
Assets/Main/Scripts/Logg.cs Normal file
View File

@ -0,0 +1,282 @@
using System;
using System.Diagnostics;
using System.Text;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Ca2d.Toolkit
{
/// <summary>
/// Alternatives to the default Unity Logger.
/// </summary>
public struct Logg
{
#region BackendConfig
// ReSharper disable once MemberCanBePrivate.Global
public const string kDefaultLoggerLevelSplit = "::";
private static ILoggHandler _backend;
/// <summary>
/// Which backend will be used for logger?
/// </summary>
public static ILoggHandler Backend
{
get => _backend;
set => _backend = value ?? throw new NullReferenceException();
}
/// <summary>
/// Logger level name spliter sign.
/// </summary>
public static string LoggerLevelSplit { get; set; } = kDefaultLoggerLevelSplit;
#endregion
#region Constructor
private static readonly StringBuilder kStringBuilder = new();
private readonly bool m_useLabel;
private readonly string m_label;
/// <summary>
/// Create a logger with first-level namespace
/// </summary>
/// <param name="ns">Namespace</param>
public Logg(string ns)
{
if (string.IsNullOrWhiteSpace(ns))
{
m_useLabel = false;
m_label = null;
}
else
{
m_useLabel = true;
m_label = ns.Trim();
}
}
/// <summary>
/// Create a copy of source logger.
/// </summary>
/// <param name="source">Source logger.</param>
public Logg(Logg source)
{
if (source.m_useLabel)
{
m_useLabel = true;
m_label = source.m_label;
}
else
{
m_useLabel = false;
m_label = null;
}
}
/// <summary>
/// Create a nested logger with given namespace.
/// </summary>
/// <param name="source">Nest source.</param>
/// <param name="ns">Namespace</param>
public Logg(Logg source, string ns)
{
if (string.IsNullOrWhiteSpace(ns))
{
if (source.m_useLabel)
{
m_useLabel = true;
m_label = source.m_label;
}
else
{
m_useLabel = false;
m_label = null;
}
}
else
{
m_useLabel = true;
if (source.m_useLabel)
{
kStringBuilder.Clear();
m_label = kStringBuilder.AppendJoin(LoggerLevelSplit, source.m_label, ns.Trim()).ToString();
}
else
{
m_label = ns.Trim();
}
}
}
#endregion
#region EnvOverrider
private static bool _init = false;
#if UNITY_EDITOR
[InitializeOnLoadMethod]
private static void LoggInitEditor()
{
LoggInit();
}
#else
[RuntimeInitializeOnLoadMethod]
private static void RuntimeLoggInit()
{
LoggInit();
}
#endif
private static void LoggInit()
{
if (!_init)
{
var prevLogHandler = UnityEngine.Debug.unityLogger.logHandler;
if (prevLogHandler is ILoggHandler)
{
prevLogHandler.LogFormat(
LogType.Warning,
null,
"Logg seems to be inited before initialize?");
}
else
{
var l = new LoggHandler(prevLogHandler);
_backend = l;
UnityEngine.Debug.unityLogger.logHandler = l;
prevLogHandler.LogFormat(LogType.Log, null, "##### Debug.unityLogger was taken over by Logg #####");
}
_init = true;
}
}
#endregion
#region LogMethods
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Format(LoggType type, Object context, string format, params object[] args)
{
if (_backend != null)
{
if (m_useLabel) _backend.SetLabel(m_label);
_backend.LoggFormat(type, context, format, args);
_backend.ClearLabel();
}
else
{
// Use fallback logger if Logg is not ready.
UnityEngine.Debug.LogFormat((LogType) type, LogOption.NoStacktrace, context, format, args);
}
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Debug(string content, Object context = null)
{
Format(LoggType.Debug, context, content);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Info(string content, Object context = null)
{
Format(LoggType.Log, context, content);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Warning(string content, Object context = null)
{
Format(LoggType.Warning, context, content);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Error(string content, Object context = null)
{
Format(LoggType.Error, context, content);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void Error(Exception err, Object context = null)
{
Format(LoggType.Exception, context, err.ToString());
}
#endregion
}
/// <summary>
/// Quick log API for <see cref="Logg"/>.
/// </summary>
public static class DebugLogg
{
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Format(LoggType type, Object context, string format, params object[] args)
{
default(Logg).Format(type, context, format, args);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Debug(string content, Object context = null)
{
default(Logg).Debug(content, context);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Info(string content, Object context = null)
{
default(Logg).Info(content, context);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Warning(string content, Object context = null)
{
default(Logg).Warning(content, context);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Error(string content, Object context = null)
{
default(Logg).Error(content, context);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public static void Error(Exception err, Object context = null)
{
default(Logg).Error(err, context);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 59eb97c7769441599ea2569c2f5402b8
timeCreated: 1715264700

View File

@ -0,0 +1,102 @@
using System;
using System.Diagnostics;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Ca2d.Toolkit
{
public class LoggHandler : ILoggHandler
{
private readonly ILogHandler m_innerHandle;
private readonly StringBuilder m_sb = new();
private bool m_useLabel = false;
private string m_labelText = default;
#region PassThrough
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void LogFormat(LogType logType, Object context, string format, params object[] args)
{
LoggFormat((LoggType) logType, context, format, args);
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void LogException(Exception exception, Object context)
{
LoggFormat(LoggType.Exception, context, exception.ToString());
}
#endregion
private void GenerateLabel(StringBuilder sb, LoggType type)
{
var logTypeText = type switch
{
LoggType.Assert => "ASSERT",
LoggType.Exception => "EXCEPTION",
LoggType.Error => "Error",
LoggType.Warning => "Warning",
LoggType.Log => "Info",
_ => "Debug"
};
if (m_useLabel)
{
sb.AppendFormat("[{0:yyyy-M-d HH:mm:ss}] {1} ({2}) : ", DateTime.Now, logTypeText, m_labelText);
}
else
{
sb.AppendFormat("[{0:yyyy-M-d HH:mm:ss}] {1} : ", DateTime.Now, logTypeText);
}
}
[HideInCallstack]
[DebuggerHidden]
[DebuggerStepThrough]
public void LoggFormat(LoggType logType, Object context, string format, params object[] args)
{
m_sb.Clear();
// Write label to the front
GenerateLabel(m_sb, logType);
// Write format text after front
m_sb.AppendFormat(format, args);
// Redirect Debug to Log.
LogType finalType;
if (logType == LoggType.Debug) finalType = LogType.Log;
else finalType = (LogType) logType;
// Use inner handle to write.
m_innerHandle.LogFormat(finalType, context, m_sb.ToString());
}
public ILogHandler InnerLogHandler => m_innerHandle;
public void SetLabel(string label)
{
m_labelText = label;
m_useLabel = label != null;
}
public void ClearLabel()
{
m_labelText = null;
m_useLabel = false;
}
public LoggHandler(ILogHandler innerHandle)
{
m_innerHandle = innerHandle ?? throw new ArgumentNullException(nameof(innerHandle));
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f543cfba5cf04258b3ab59b75849e7a9
timeCreated: 1715271314

View File

@ -2,6 +2,9 @@ using System;
namespace Ca2d.Toolkit
{
/// <summary>
/// When Unity Requirements did not fit <see cref="Guard"/> wants, throw this.
/// </summary>
public class LoseRequirementException : Exception
{
public Type RequiredType { get; }

View File

@ -11,8 +11,14 @@ namespace Ca2d.Toolkit
[Serializable]
public struct Option<T>
{
/// <summary>
/// Is option set to fill.
/// </summary>
public bool Enabled;
/// <summary>
/// Value inside of option
/// </summary>
public T Value;
public T ValueOrDefault()

View File

@ -15,16 +15,26 @@ namespace Ca2d.Toolkit
/// <summary>
/// Is this warp reference to a valid target?
/// </summary>
public bool Valid => m_referencedObject is T;
public bool Is => m_referencedObject is T;
/// <summary>
/// Trying to get the actual object of this reference.
/// </summary>
public T Object => m_referencedObject as T;
public T As => m_referencedObject as T;
public static implicit operator T(UnityObjectWarp<T> wrapper)
/// <summary>
/// Is this warp reference to a valid target? If this is valid, give the casting result.
/// </summary>
public bool IsAs(out T val)
{
return wrapper.Object;
if (m_referencedObject is T referenced)
{
val = referenced;
return true;
}
val = null;
return false;
}
}
}