Remove UnityObjectWarp and add gui for Option

This commit is contained in:
Ca2didi 2024-07-03 16:11:27 +08:00
parent f320b479ea
commit 3e8f62f5cf
19 changed files with 500 additions and 47 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9fbae8b6b0a64df885641b3584fcb15b
timeCreated: 1719991315

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ce8a0f7441b34ce9a8f891b87fbaad91
timeCreated: 1719991325

View File

@ -0,0 +1,105 @@
using System.Text;
using UnityEditor;
using UnityEngine;
namespace Ca2d.Toolkit.Editor
{
public static class AssetDatabaseUtility
{
/// <summary>
/// Check if this path contains a valid asset.
/// </summary>
/// <param name="path">Target path</param>
/// <returns>True that path is an asset</returns>
public static bool PathIsValidAsset(string path)
{
return AssetDatabase.IsValidFolder(path) == false &&
string.IsNullOrWhiteSpace(AssetDatabase.AssetPathToGUID(path)) == false;
}
/// <summary>
/// Create a folder in a path if this folder has not been created.
/// </summary>
/// <param name="folderPath">Target folder path</param>
/// <returns>Is this path is ready to be a folder?</returns>
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;
}
/// <summary>
/// Create asset at path with everything done (include create path and create asset.).
/// </summary>
/// <param name="path">The folder path this file will be create to.</param>
/// <typeparam name="T">The type of asset.</typeparam>
/// <returns>If asset path is valid, return new asset.</returns>
public static T CreateAssetAtPath<T>(string path) where T : ScriptableObject
{
if (CreateFolderIfNotExist(path) == false) return null;
var asset = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(asset, $"{path}");
return asset;
}
/// <summary>
/// Create asset at path with everything done (include create path and create asset.).
/// </summary>
/// <param name="path">The folder path this file will be create to.</param>
/// <param name="name">The file name this asset will be.</param>
/// <typeparam name="T">The type of asset.</typeparam>
/// <returns>If asset path is valid, return new asset.</returns>
public static T CreateAssetAtPath<T>(string path, string name) where T : ScriptableObject
{
if (CreateFolderIfNotExist(path) == false) return null;
var asset = ScriptableObject.CreateInstance<T>();
if (path.EndsWith('/')) AssetDatabase.CreateAsset(asset, $"{path}{name}.asset");
else AssetDatabase.CreateAsset(asset, $"{path}/{name}.asset");
return asset;
}
/// <summary>
/// Trim an editor path to resource path.
/// </summary>
/// <param name="path">Editor path</param>
public static void AssetPathTrimmer(ref string path)
{
path = path.Replace("Assets/Resources/", "");
path = path.Replace(".asset", "");
path = path.Replace(".prefab", "");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97def5fd9acbf7db4a7e5cee4537f1c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f242342c346b40fb2836611534f4e953
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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)

View File

@ -1,3 +1,4 @@
#if ENABLE_LOGG
using UnityEngine;
namespace Ca2d.Toolkit
@ -40,4 +41,5 @@ namespace Ca2d.Toolkit
public void ClearLabel();
}
}
}
#endif

View File

@ -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);
}
}
}
}
#endif

View File

@ -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));
}
}
}
}
#endif

View File

@ -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<uint> 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;
/// <summary>
/// Event when locking state changed.
/// </summary>
public event Action<bool> OnStateChanged;
/// <summary>
/// The locking state of this notion.
/// </summary>
public bool LockStateSelf { get; private set; }
/// <summary>
/// The parent of this notion.
/// </summary>
public ResLock Parent => m_core.Target;
internal ResourceNotion(ResLock parent)
{
m_core = new ResourceNotionCore(parent);
LockStateSelf = false;
_LockStateEvents += NotifyListener;
}
/// <summary>
/// Lock this notion
/// </summary>
public bool Lock()
{
Guard();
if (m_core.SetState(true))
{
LockStateSelf = true;
return true;
}
return false;
}
/// <summary>
/// Unlock this notion.
/// </summary>
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<uint> m_resNotions = new ();
public ResLock(uint resCount = 0, string friendlyName = default)
{
m_resCount = resCount;
FriendlyName = friendlyName;
}
/// <summary>
/// User friendly name for ResLock
/// </summary>
public string FriendlyName { get; }
/// <summary>
/// State of this lock itself
/// </summary>
public bool LockState => m_resNotions.Count > m_resCount;
/// <summary>
/// Do locking on this locker
/// </summary>
/// <param name="requireLockStateIsFree">Should require this lock is free</param>
/// <returns>Locker Notion can be used in using block</returns>
/// <exception cref="InvalidOperationException">Lock can not being operated.</exception>
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);
}
/// <summary>
/// 获取一个锁注解
/// </summary>
/// <param name="initialLock">该注解的起始锁定状态</param>
public ResourceNotion GetNotion(bool initialLock = false)
{
var n = new ResourceNotion(this);
if (initialLock) n.Lock();
return n;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 28e7e62b954e414f80499b70d8e3dcf3
timeCreated: 1719992451

View File

@ -0,0 +1,18 @@
using System.Text;
using UnityEngine.Pool;
namespace Ca2d.Toolkit
{
public static class StringBuilderPool
{
private static readonly ObjectPool<StringBuilder> s_Pool = new (
() => new StringBuilder(),
sb => sb.Clear());
public static StringBuilder Get() => s_Pool.Get();
public static PooledObject<StringBuilder> Get(out StringBuilder value) => s_Pool.Get(out value);
public static void Release(StringBuilder toRelease) => s_Pool.Release(toRelease);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41341c104f749b437ad0e367dd9a0d0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,40 +0,0 @@
using System;
using UnityEngine;
namespace Ca2d.Toolkit
{
/// <summary>
/// An utility which allows you to reference an <see cref="UnityEngine.Object"/> via it's interface.
/// </summary>
/// <typeparam name="T">The type of class which you are willing to reference as.</typeparam>
[Serializable]
public struct UnityObjectWarp<T> where T : class
{
[SerializeField] private UnityEngine.Object m_referencedObject;
/// <summary>
/// Is this warp reference to a valid target?
/// </summary>
public bool Is => m_referencedObject is T;
/// <summary>
/// Trying to get the actual object of this reference.
/// </summary>
public T As => m_referencedObject as T;
/// <summary>
/// Is this warp reference to a valid target? If this is valid, give the casting result.
/// </summary>
public bool IsAs(out T val)
{
if (m_referencedObject is T referenced)
{
val = referenced;
return true;
}
val = null;
return false;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 94c950f0a2014abc9e3f97281d149318
timeCreated: 1713286510

View File

@ -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",

View File

@ -4,6 +4,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Cpackage/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Cpackage_005Ctoolkit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Cpackage_005Ctoolkit_005Cscripts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Ctoolkit_005Ctoolkit_005Ceditor_005Cscripts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Ctoolkit_005Ctoolkit_005Cscripts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Ctoolkit_005Ctoolkit/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=assets_005Ctoolkit/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>