diff --git a/.idea/.idea.ca2d-unity-toolkit/.idea/vcs.xml b/.idea/.idea.ca2d-unity-toolkit/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.ca2d-unity-toolkit/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Editor.meta b/Assets/Toolkit/Toolkit/Editor.meta
new file mode 100644
index 0000000..a55e6e2
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9fbae8b6b0a64df885641b3584fcb15b
+timeCreated: 1719991315
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Editor/Scripts.meta b/Assets/Toolkit/Toolkit/Editor/Scripts.meta
new file mode 100644
index 0000000..cecb9f4
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor/Scripts.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ce8a0f7441b34ce9a8f891b87fbaad91
+timeCreated: 1719991325
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs b/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs
new file mode 100644
index 0000000..3aa6de6
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs
@@ -0,0 +1,105 @@
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+
+namespace Ca2d.Toolkit.Editor
+{
+ public static class AssetDatabaseUtility
+ {
+ ///
+ /// Check if this path contains a valid asset.
+ ///
+ /// Target path
+ /// True that path is an asset
+ public static bool PathIsValidAsset(string path)
+ {
+ return AssetDatabase.IsValidFolder(path) == false &&
+ string.IsNullOrWhiteSpace(AssetDatabase.AssetPathToGUID(path)) == false;
+ }
+
+ ///
+ /// Create a folder in a path if this folder has not been created.
+ ///
+ /// Target folder path
+ /// Is this path is ready to be a folder?
+ public static bool CreateFolderIfNotExist(string folderPath)
+ {
+ if (PathIsValidAsset(folderPath)) return false;
+ if (AssetDatabase.IsValidFolder(folderPath)) return true;
+
+ var pathVector = folderPath.Split('/');
+ if (pathVector.Length == 0) return false;
+ if (pathVector.Length == 1) return AssetDatabase.IsValidFolder(pathVector[0]);
+ if (pathVector.Length < 2)
+ {
+ if (AssetDatabase.IsValidFolder($"{pathVector[0]}/{pathVector[1]}")) return true;
+ return string.IsNullOrEmpty(AssetDatabase.CreateFolder(pathVector[0], pathVector[1])) == false;
+ }
+
+ var sb = new StringBuilder(pathVector[0]);
+ var prev = pathVector[0];
+ for (var i = 1; i < pathVector.Length; i++)
+ {
+ var tk = pathVector[i];
+ if (i == pathVector.Length - 1 && tk.Contains('.')) continue;
+ sb.Append('/').Append(tk);
+
+ var cur = sb.ToString();
+ if (AssetDatabase.IsValidFolder(cur) == false)
+ {
+ if (string.IsNullOrEmpty(AssetDatabase.CreateFolder(prev, tk)))
+ return false;
+ }
+
+ prev = cur;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Create asset at path with everything done (include create path and create asset.).
+ ///
+ /// The folder path this file will be create to.
+ /// The type of asset.
+ /// If asset path is valid, return new asset.
+ public static T CreateAssetAtPath(string path) where T : ScriptableObject
+ {
+ if (CreateFolderIfNotExist(path) == false) return null;
+ var asset = ScriptableObject.CreateInstance();
+
+ AssetDatabase.CreateAsset(asset, $"{path}");
+
+ return asset;
+ }
+
+ ///
+ /// Create asset at path with everything done (include create path and create asset.).
+ ///
+ /// The folder path this file will be create to.
+ /// The file name this asset will be.
+ /// The type of asset.
+ /// If asset path is valid, return new asset.
+ public static T CreateAssetAtPath(string path, string name) where T : ScriptableObject
+ {
+ if (CreateFolderIfNotExist(path) == false) return null;
+ var asset = ScriptableObject.CreateInstance();
+
+ if (path.EndsWith('/')) AssetDatabase.CreateAsset(asset, $"{path}{name}.asset");
+ else AssetDatabase.CreateAsset(asset, $"{path}/{name}.asset");
+
+ return asset;
+ }
+
+ ///
+ /// Trim an editor path to resource path.
+ ///
+ /// Editor path
+ public static void AssetPathTrimmer(ref string path)
+ {
+ path = path.Replace("Assets/Resources/", "");
+ path = path.Replace(".asset", "");
+ path = path.Replace(".prefab", "");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs.meta b/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs.meta
new file mode 100644
index 0000000..1e7e6da
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor/Scripts/AssetDatabaseUtility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 97def5fd9acbf7db4a7e5cee4537f1c5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs b/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs
new file mode 100644
index 0000000..942e8e8
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs
@@ -0,0 +1,35 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace Ca2d.Toolkit.Editor
+{
+ [CustomPropertyDrawer(typeof(Option<>))]
+ public class OptionDrawer : PropertyDrawer
+ {
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ return EditorGUI.GetPropertyHeight(property.FindPropertyRelative("Value")); ;
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ var value = property.FindPropertyRelative("Value");
+ var enable = property.FindPropertyRelative("Enabled");
+
+ var toggleRect = position;
+ toggleRect.height = toggleRect.width = 18;
+
+ enable.boolValue = EditorGUI.Toggle(toggleRect, GUIContent.none, enable.boolValue);
+ var prev = GUI.enabled;
+ GUI.enabled = enable.boolValue;
+
+ var propertyRect = position;
+ propertyRect.x += 18;
+ propertyRect.width -= 18;
+ EditorGUI.PropertyField(propertyRect, value, label, true);
+ GUI.enabled = prev;
+
+ if (value.serializedObject.hasModifiedProperties) value.serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs.meta b/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs.meta
new file mode 100644
index 0000000..110390b
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Editor/Scripts/OptionDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f242342c346b40fb2836611534f4e953
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Toolkit/Toolkit/Scripts/Guard.cs b/Assets/Toolkit/Toolkit/Scripts/Guard.cs
index fa98e80..3e1c8c3 100644
--- a/Assets/Toolkit/Toolkit/Scripts/Guard.cs
+++ b/Assets/Toolkit/Toolkit/Scripts/Guard.cs
@@ -11,7 +11,9 @@ namespace Ca2d.Toolkit
private static void IgnoreNoTarget()
{
+#if UNITY_EDITOR
Debug.LogError("Ignore Exception should run with executor!");
+#endif
}
#region IgnoreException(Action)
diff --git a/Assets/Toolkit/Toolkit/Scripts/ILoggHandler.cs b/Assets/Toolkit/Toolkit/Scripts/ILoggHandler.cs
index 27043ea..51ae23d 100644
--- a/Assets/Toolkit/Toolkit/Scripts/ILoggHandler.cs
+++ b/Assets/Toolkit/Toolkit/Scripts/ILoggHandler.cs
@@ -1,3 +1,4 @@
+#if ENABLE_LOGG
using UnityEngine;
namespace Ca2d.Toolkit
@@ -40,4 +41,5 @@ namespace Ca2d.Toolkit
public void ClearLabel();
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/Logg.cs b/Assets/Toolkit/Toolkit/Scripts/Logg.cs
index ea40fb4..9d53772 100644
--- a/Assets/Toolkit/Toolkit/Scripts/Logg.cs
+++ b/Assets/Toolkit/Toolkit/Scripts/Logg.cs
@@ -1,3 +1,4 @@
+#if ENABLE_LOGG
using System;
using System.Diagnostics;
using System.Text;
@@ -279,4 +280,5 @@ namespace Ca2d.Toolkit
default(Logg).Error(err, context);
}
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/LoggHandler.cs b/Assets/Toolkit/Toolkit/Scripts/LoggHandler.cs
index add3dbd..f9aa1b9 100644
--- a/Assets/Toolkit/Toolkit/Scripts/LoggHandler.cs
+++ b/Assets/Toolkit/Toolkit/Scripts/LoggHandler.cs
@@ -1,3 +1,4 @@
+#if ENABLE_LOGG
using System;
using System.Diagnostics;
using System.Text;
@@ -99,4 +100,5 @@ namespace Ca2d.Toolkit
m_innerHandle = innerHandle ?? throw new ArgumentNullException(nameof(innerHandle));
}
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/ResLock.cs b/Assets/Toolkit/Toolkit/Scripts/ResLock.cs
new file mode 100644
index 0000000..4473cf9
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Scripts/ResLock.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Ca2d.Toolkit
+{
+
+ public delegate void ResLockStateChanged(ResLock resLock, uint notionId, bool notionSelfState);
+
+ public class ResLock
+ {
+ private struct ResourceNotionCore : IDisposable
+ {
+
+ #region Allocator
+
+ private static SortedSet m_allocateMap = new();
+
+ private static uint AllocateId()
+ {
+ // Id should start at 1 to avoid 0 as default.
+ uint id = 1;
+ foreach (var u in m_allocateMap)
+ {
+ if (id < u) break;
+ id = u + 1;
+ }
+
+ m_allocateMap.Add(id);
+ return id;
+ }
+
+ private static void FreeId(uint id)
+ {
+ m_allocateMap.Remove(id);
+ }
+
+ #endregion
+
+ #region Notification
+
+
+ private static bool _LockStateNotifyGuard = false;
+
+ private static void NotifyLoopDetect()
+ {
+ if (_LockStateNotifyGuard) throw new InvalidOperationException();
+ }
+
+ private static void Notify(ResLock target, ref ResourceNotionCore core)
+ {
+ _LockStateNotifyGuard = true;
+
+ try
+ {
+ _LockStateEvents?.Invoke(target, core.Id, core.Target.m_resNotions.Contains(core.Id));
+ }
+ catch (Exception e)
+ {
+ Debug.LogError(e);
+ }
+
+ _LockStateNotifyGuard = false;
+ }
+
+
+ #endregion
+
+ public readonly uint Id;
+
+ public readonly ResLock Target;
+
+ public ResourceNotionCore(ResLock target)
+ {
+ Target = target;
+ Id = AllocateId();
+#if DEBUG
+ Debug.LogFormat("Resource Notion '{0}' was allocated for {1}", Id, target.FriendlyName);
+#endif
+ }
+
+ public bool SetState(bool enabled)
+ {
+ NotifyLoopDetect();
+
+ var result = enabled ? Target.m_resNotions.Add(Id) : Target.m_resNotions.Remove(Id);
+ Notify(Target, ref this);
+
+ return result;
+ }
+
+ public void Dispose()
+ {
+ FreeId(Id);
+#if DEBUG
+ Debug.LogFormat("Resource Notion '{0}' was released from {1}", Id, Target.FriendlyName);
+#endif
+ }
+ }
+
+ public struct LiteResourceNotion : IDisposable
+ {
+ private ResourceNotionCore m_core;
+
+ internal LiteResourceNotion(ResLock parent)
+ {
+ m_core = new ResourceNotionCore(parent);
+ m_core.SetState(true);
+ }
+
+ public void Dispose()
+ {
+ if (m_core.Target == null)
+ throw new InvalidOperationException("Can not dispose a disposed Resource Notion!");
+
+ m_core.SetState(false);
+ m_core.Dispose();
+ m_core = default;
+ }
+ }
+
+
+ public class ResourceNotion : IDisposable
+ {
+ private ResourceNotionCore m_core;
+
+ ///
+ /// Event when locking state changed.
+ ///
+ public event Action OnStateChanged;
+
+ ///
+ /// The locking state of this notion.
+ ///
+ public bool LockStateSelf { get; private set; }
+
+ ///
+ /// The parent of this notion.
+ ///
+ public ResLock Parent => m_core.Target;
+
+ internal ResourceNotion(ResLock parent)
+ {
+ m_core = new ResourceNotionCore(parent);
+ LockStateSelf = false;
+ _LockStateEvents += NotifyListener;
+ }
+
+ ///
+ /// Lock this notion
+ ///
+ public bool Lock()
+ {
+ Guard();
+ if (m_core.SetState(true))
+ {
+ LockStateSelf = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Unlock this notion.
+ ///
+ public bool Unlock()
+ {
+ Guard();
+ if (m_core.SetState(false))
+ {
+ LockStateSelf = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void Dispose()
+ {
+ if (m_core.Target == null)
+ throw new InvalidOperationException("Can not dispose a disposed Resource Notion!");
+
+ m_core.SetState(false);
+ _LockStateEvents -= NotifyListener;
+ LockStateSelf = false;
+ m_core.Dispose();
+ m_core = default;
+ }
+
+ private void NotifyListener(ResLock target, uint id, bool selfState)
+ {
+ if (target == Parent)
+ {
+ OnStateChanged?.Invoke(target.LockState);
+ }
+ }
+
+ private void Guard()
+ {
+ if (m_core.Target == null)
+ throw new InvalidOperationException("Can not operate a disposed Resource Notion!");
+ }
+ }
+
+ private static event ResLockStateChanged _LockStateEvents
+#if DEBUG
+ = (locker, id, selfState) =>
+ {
+ var name = locker.FriendlyName;
+ var state = locker.LockState;
+
+ using (StringBuilderPool.Get(out var sb))
+ {
+ sb.AppendFormat(
+ "Resource Notion '{0}' changes to {1} made Locker '{2}' state changed to {3} ",
+ id,
+ selfState ? "True" : "False",
+ name,
+ state ? "True" : "False");
+
+ sb.AppendFormat("(limit {0}:, used: {1})", locker.m_resCount, locker.m_resNotions.Count);
+
+ Debug.Log(sb);
+ }
+ }
+#endif
+ ;
+
+ private uint m_resCount;
+
+ private HashSet m_resNotions = new ();
+
+ public ResLock(uint resCount = 0, string friendlyName = default)
+ {
+ m_resCount = resCount;
+ FriendlyName = friendlyName;
+ }
+
+ ///
+ /// User friendly name for ResLock
+ ///
+ public string FriendlyName { get; }
+
+ ///
+ /// State of this lock itself
+ ///
+ public bool LockState => m_resNotions.Count > m_resCount;
+
+ ///
+ /// Do locking on this locker
+ ///
+ /// Should require this lock is free
+ /// Locker Notion can be used in using block
+ /// Lock can not being operated.
+ public LiteResourceNotion Lock(bool requireLockStateIsFree = true)
+ {
+ if (requireLockStateIsFree && LockState)
+ {
+ if (string.IsNullOrWhiteSpace(FriendlyName)) throw new InvalidOperationException(
+ "Can not lock Resource Lock due to free state is required.");
+ else throw new InvalidOperationException(
+ $"Can not lock Resource Lock '{FriendlyName}' due to free state is required.");
+ }
+
+ return new LiteResourceNotion(this);
+ }
+
+ ///
+ /// 获取一个锁注解
+ ///
+ /// 该注解的起始锁定状态
+ public ResourceNotion GetNotion(bool initialLock = false)
+ {
+ var n = new ResourceNotion(this);
+ if (initialLock) n.Lock();
+
+ return n;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/ResLock.cs.meta b/Assets/Toolkit/Toolkit/Scripts/ResLock.cs.meta
new file mode 100644
index 0000000..56458fb
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Scripts/ResLock.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 28e7e62b954e414f80499b70d8e3dcf3
+timeCreated: 1719992451
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs b/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs
new file mode 100644
index 0000000..bfc4579
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs
@@ -0,0 +1,18 @@
+using System.Text;
+using UnityEngine.Pool;
+
+namespace Ca2d.Toolkit
+{
+ public static class StringBuilderPool
+ {
+ private static readonly ObjectPool s_Pool = new (
+ () => new StringBuilder(),
+ sb => sb.Clear());
+
+ public static StringBuilder Get() => s_Pool.Get();
+
+ public static PooledObject Get(out StringBuilder value) => s_Pool.Get(out value);
+
+ public static void Release(StringBuilder toRelease) => s_Pool.Release(toRelease);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs.meta b/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs.meta
new file mode 100644
index 0000000..b290bc5
--- /dev/null
+++ b/Assets/Toolkit/Toolkit/Scripts/StringBuilderPool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 41341c104f749b437ad0e367dd9a0d0e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs b/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs
deleted file mode 100644
index d19c76c..0000000
--- a/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using UnityEngine;
-
-namespace Ca2d.Toolkit
-{
- ///
- /// An utility which allows you to reference an via it's interface.
- ///
- /// The type of class which you are willing to reference as.
- [Serializable]
- public struct UnityObjectWarp where T : class
- {
- [SerializeField] private UnityEngine.Object m_referencedObject;
-
- ///
- /// Is this warp reference to a valid target?
- ///
- public bool Is => m_referencedObject is T;
-
- ///
- /// Trying to get the actual object of this reference.
- ///
- public T As => m_referencedObject as T;
-
- ///
- /// Is this warp reference to a valid target? If this is valid, give the casting result.
- ///
- public bool IsAs(out T val)
- {
- if (m_referencedObject is T referenced)
- {
- val = referenced;
- return true;
- }
-
- val = null;
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs.meta b/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs.meta
deleted file mode 100644
index ca3a245..0000000
--- a/Assets/Toolkit/Toolkit/Scripts/UnityObjectWarp.cs.meta
+++ /dev/null
@@ -1,3 +0,0 @@
-fileFormatVersion: 2
-guid: 94c950f0a2014abc9e3f97281d149318
-timeCreated: 1713286510
\ No newline at end of file
diff --git a/Assets/Toolkit/package.json b/Assets/Toolkit/package.json
index 8f28573..8a8f22d 100644
--- a/Assets/Toolkit/package.json
+++ b/Assets/Toolkit/package.json
@@ -1,6 +1,6 @@
{
"name": "xyz.ca2didi.unity.toolkit",
- "version": "0.1.0",
+ "version": "0.1.1",
"displayName": "Ca2D Toolkit",
"description": "Set of toolkits which can boost your develop experience and speed in Unity.",
"unity": "2021.3",
diff --git a/Ca2d.Toolkit.csproj.DotSettings b/Ca2d.Toolkit.csproj.DotSettings
index 05c8e83..6e6c12c 100644
--- a/Ca2d.Toolkit.csproj.DotSettings
+++ b/Ca2d.Toolkit.csproj.DotSettings
@@ -4,6 +4,7 @@
True
True
True
+ True
True
True
True
\ No newline at end of file