using Convention;
using Convention.RScript;
using Convention.WindowsUI.Variant;
using Demo.Attr;
using Demo.Game;
using Dreamteck.Splines;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Unity.VisualScripting;
using UnityEngine;
namespace Demo
{
public interface IScriptableObject
{
ScriptableObject SharedInterfaceScriptObject { get; }
}
#region ScriptableObject Inside
namespace Attr
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
sealed class ScriptableAttribute : Attribute
{
public readonly string generaterName;
public ScriptableAttribute(string generaterName = "Make")
{
this.generaterName = generaterName;
}
}
}
public partial class ScriptableObject : IScriptableObject
{
///
/// 用于开放给倒置接口
///
public ScriptableObject SharedInterfaceScriptObject => this;
}
///
/// 初始化与对象设置
///
public partial class ScriptableObject
{
[Content, SerializeField]
private Vector3
EnterGameLocalPosition = Vector3.zero,
EnterGameEulerAngles = Vector3.zero,
EnterGameLocalScaling = Vector3.one;
[Content, SerializeField] private bool IsSetObjectDisable = false;
[Content] public int UpdatePerFrame = 1;
///
/// 设置坐标
///
///
///
///
[Convention.RScript.Variable.Attr.Method]
public void SetLocalPosition(float x, float y, float z)
{
EnterGameLocalPosition = new(x, y, z);
}
///
/// 设置欧拉角
///
///
///
///
[Convention.RScript.Variable.Attr.Method]
public void SetLocalEulerAngles(float x, float y, float z)
{
EnterGameEulerAngles = new(x, y, z);
}
///
/// 设置缩放
///
///
///
///
[Convention.RScript.Variable.Attr.Method]
public void SetLocalScaling(float x, float y, float z)
{
EnterGameLocalScaling = new(x, y, z);
}
///
/// 关闭该物体,
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
///
[Convention.RScript.Variable.Attr.Method]
public void SetObjectDisable()
{
IsSetObjectDisable = true;
}
///
/// 指定多少个状态的执行一次更新,不会影响到子物体
/// 属于性能优化的高级选项
///
/// 每frame帧更新一次, 等于0代表不会在状态运行
[Convention.RScript.Variable.Attr.Method]
public void SetUpdatePerFrame(int frame)
{
UpdatePerFrame = Mathf.Max(1, frame);
}
private void ScriptableObjectDoReset()
{
transform.localPosition = EnterGameLocalPosition;
transform.localEulerAngles = EnterGameEulerAngles;
transform.localScale = EnterGameLocalScaling;
gameObject.SetActive(IsSetObjectDisable == false);
}
}
///
/// 跨脚本上下文变量
///
public partial class ScriptableObject
{
public readonly Dictionary ScriptableObjectContents = new();
[Convention.RScript.Variable.Attr.Method]
public float GetContent(string key)
{
if (ScriptableObjectContents.TryGetValue(key, out var result))
{
return result;
}
throw new InvalidOperationException($"Key {key} is not find in contnet");
}
[Convention.RScript.Variable.Attr.Method]
public float SetContent(string key, float value)
{
ScriptableObjectContents[key] = value;
return value;
}
}
///
/// 基础信息, 父子关系与EnableScript
///
public partial class ScriptableObject
{
public static bool IsAutoPlay = false;
public static float OneBarTime = 60;
private bool isEnableScript = false;
public string ScriptName = "";
private string s_ScriptType = null;
public string m_ScriptType
{
get
{
if (s_ScriptType == null)
s_ScriptType = this.GetType().Name;
return s_ScriptType;
}
}
public ScriptableObject Parent;
public readonly List Childs = new();
///
/// 获取根脚本对象
///
///
[Convention.RScript.Variable.Attr.Method]
public RootObject GetRoot()
{
if (Parent == null)
return this as RootObject;
if (Parent is RootObject result)
return result;
else
return Parent.GetRoot();
}
public const string RootObjectQuickPath = "project/";
public ScriptableObject FindWithPath(string path, bool isMustExist = true)
{
if (string.IsNullOrEmpty(path))
{
if (isMustExist)
throw new Exception("path is null or empty");
else
return null;
}
// GetParent
ScriptableObject result = Parent;
if (path.Replace('\\', '/').ToLower().StartsWith(RootObjectQuickPath))
{
result = GetRoot();
path = path[RootObjectQuickPath.Length..];
}
if (Parent == null)
{
throw new InvalidOperationException($"Root is nosupport to {nameof(FindWithPath)}");
}
// Find
var components = path.Split('/', '\\');
components[^1] = new ToolFile(components[^1]).GetFilename(true);
foreach (var component in components)
{
if (component == "..")
result = result.Parent;
else if (component == "." || string.IsNullOrEmpty(component))
continue;
else
{
int index = 0;
string targetScriptObjectPath = component;
Regex regex = new(@"^(.*)\[(\d*)\]$");
var match = regex.Match(component);
if (match.Success)
{
targetScriptObjectPath = match.Groups[1].Value;
index = int.Parse(match.Groups[2].Value);
}
var target = result.Childs.FirstOrDefault(x =>
{
bool stats = x.ScriptName == targetScriptObjectPath;
if (index == 0)
return stats;
else if (stats)
index--;
return false;
});
if (target != null)
result = target;
else
{
if (isMustExist)
throw new Exception($"{component} in {path} is not found");
else
{
Debug.Log($"{component} in {path} is not found", this);
return null;
}
}
}
}
return result;
}
public void EnableScript(ScriptableObject parent)
{
if (isEnableScript)
{
Debug.LogError($"ScriptableObject is currently enableScript, start coroutine {nameof(UnloadScript)} to disable", this);
return;
}
this.Parent = parent;
if (parent != null)
this.ScriptableObjectContents.AddRange(parent.ScriptableObjectContents);
this.name = ScriptName;
isEnableScript = true;
// 只有RootObject的parent会是空的
if (parent != null)
{
MyHierarchyItem = parent.MyHierarchyItem.GetHierarchyItem().CreateSubPropertyItem(1)[0];
}
else
{
MyHierarchyItem = HierarchyWindow.instance.CreateRootItemEntryWithBinders(1)[0];
}
MyHierarchyItem.GetHierarchyItem().title = this.ScriptName + $"<{m_ScriptType}>";
MyHierarchyItem.GetHierarchyItem().target = this;
MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent().ScriptObjectMenu = OnHierarchyItemRightClick;
// 增数
{
EnableScriptableObjectCounter++;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}";
}
}
#region Hierarchy
public PropertiesWindow.ItemEntry MyHierarchyItem;
public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem;
public bool EnsureEnableScript()
{
if (isEnableScript == false)
{
Debug.LogError("ScriptableObject is currently disableScript", this);
}
return isEnableScript;
}
#endregion
}
///
/// 脚本解析与Update执行
///
public partial class ScriptableObject
{
public static Dictionary FastScriptableObjectTypen = new();
public static int EnableScriptableObjectCounter = 0;
public static int ApplyScriptableObjectCounter = 0;
public bool IsParseScript2Expr { get; private set; } = false;
protected virtual bool IsSelfEnableUpdate { get => true; }
#region LoadSubScript
internal static int LoadingTaskCoroutineCount { get; set; } = 0;
internal readonly static LinkedList WaitingTaskForLoadingCoroutine = new();
private readonly static WaitForEndOfFrame WaitForEndOfFrameInstance = new();
private const int MaxLoadingCoroutine = 100;
///
/// 创建基于子脚本的实例对象
///
///
///
///
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject LoadSubScript([In] string type, [In] string name, [In] string path)
{
// 判断类型是否合法
if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false)
{
Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this);
return null;
}
// 生成对象
var child = creater();
// 获取文件
var file = new ToolFile(GetRoot().SourcePath);
file = file | path;
// 找不到脚本
if (file.Exists() == false)
{
Debug.LogError($"{file}<{path}> is not found", this);
return null;
}
child.ScriptName = name;
child.transform.SetParent(this.transform);
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
child.transform.localScale = Vector3.one;
child.EnableScript(this);
// Add Child
Childs.Add(child);
// Load Child Script
IEnumerator Foo()
{
// 缓存并延迟运行
yield return WaitForEndOfFrameInstance;
yield return child.ParseFromScriptFile2Expr(file);
LoadingTaskCoroutineCount--;
}
child.IsParseScript2Expr = true;
WaitingTaskForLoadingCoroutine.AddLast(Foo());
while (LoadingTaskCoroutineCount < MaxLoadingCoroutine && WaitingTaskForLoadingCoroutine.Count > 0)
{
LoadingTaskCoroutineCount++;
ConventionUtility.StartCoroutine(WaitingTaskForLoadingCoroutine.First.Value);
WaitingTaskForLoadingCoroutine.RemoveFirst();
}
return child;
}
///
/// 创建不基于子脚本的实例对象
///
///
///
///
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject NewSubScript([In] string type, string name)
{
// 判断类型是否合法
if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false)
{
Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this);
return null;
}
// 生成对象
var child = creater();
child.ScriptName = name;
child.transform.SetParent(this.transform);
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
child.transform.localScale = Vector3.one;
child.EnableScript(this);
// Add Child
Childs.Add(child);
return child;
}
///
/// 获取已加载的脚本对象
///
/// 无法找到时将返回空
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject GetLoadedObject(string path)
{
return FindWithPath(path, false);
}
///
/// 获取父脚本对象
///
///
[Convention.RScript.Variable.Attr.Method]
public ScriptableObject GetParentObject()
{
return Parent;
}
#endregion
public enum TickType
{
Reset,
Pause,
Start,
Update
}
[Content, SerializeField] private int ScriptUpdateCounter = 0;
public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType)
{
if (IsScriptApply == false)
return;
if (tickType == TickType.Reset)
{
ResetEnterGameStatus();
foreach (var child in Childs)
{
child.ScriptUpdate(currentTime, deltaTime, tickType);
}
}
else
{
// UpdateTicks
if (ScriptUpdateCounter % UpdatePerFrame == 0)
UpdateTicks(currentTime, deltaTime, tickType);
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
}
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
}
///
/// 使用标记可编辑脚本所能够调用的函数
/// 使用标记派生类,并附加默认模板
///
public partial class ScriptableObject :
#if ENABLE_SerializedMonoBehaviour_CLASS
SerializedMonoBehaviour,
#else
MonoBehaviour,
#endif
IHierarchyItemClickEventListener
{
#if UNITY_EDITOR
internal static readonly HashSet s_DebugContainer = new();
private void Awake()
{
s_DebugContainer.Add(this);
}
private void OnDestroy()
{
s_DebugContainer.Remove(this);
}
#endif
protected virtual IEnumerator DoSomethingDuringApplyScript()
{
yield break;
}
[Content]
public bool IsScriptApply { get; private set; } = false;
///
/// 确认脚本已经完全执行完成, 允许其进入工作阶段
/// 注意不要使用this.ApplyScript, 将会导致
///
/// while (this.IsParseScript2Expr)
/// {
/// yield return null;
/// }
///
/// 永远等待
///
///
[Convention.RScript.Variable.Attr.Method]
public IEnumerator ApplyScript()
{
if (EnsureEnableScript() == false)
{
yield break;
}
if (IsScriptApply)
yield break;
// 等待自身脚本解析完毕
while (this.IsParseScript2Expr)
{
yield return this;
}
while (LoadingTaskCoroutineCount < MaxLoadingCoroutine && WaitingTaskForLoadingCoroutine.Count > 0)
{
LoadingTaskCoroutineCount++;
ConventionUtility.StartCoroutine(WaitingTaskForLoadingCoroutine.First.Value);
WaitingTaskForLoadingCoroutine.RemoveFirst();
}
yield return DoSomethingDuringApplyScript();
// 增数
{
ApplyScriptableObjectCounter++;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}";
}
// 统计更新能力
{
if (this.IsSelfEnableUpdate)
{
var type = this.GetType();
if (GetRoot().UpdateChilds.TryGetValue(type, out var scriptables))
scriptables.Add(this);
else
GetRoot().UpdateChilds[type] = new() { this };
}
}
// 释放资源
{
this.ScriptableObjectContents.Clear();
}
IsScriptApply = true;
}
public virtual IEnumerator UnloadScript()
{
if (IsScriptApply)
{
this.name = "";
foreach (var child in Childs)
{
ConventionUtility.StartCoroutine(child.UnloadScript());
}
yield return new WaitUntil(() => Childs.Any(x => x.isEnableScript == false) == false);
// 清理各种状态
IsScriptApply = false;
// 清理Cache
//
// 减数
ApplyScriptableObjectCounter--;
EnableScriptableObjectCounter--;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"SOC: {ApplyScriptableObjectCounter}/{EnableScriptableObjectCounter}";
if (MyHierarchyItem != null)
{
// 卸载UI
MyHierarchyItem.Release();
MyHierarchyItem = null;
}
this.isEnableScript = false;
this.Parent = null;
this.name = "";
}
}
public virtual void ResetEnterGameStatus()
{
ScriptableObjectDoReset();
}
public virtual void OnHierarchyItemRightClick(RectTransform item)
{
DefaultInstantiate.OpenInstantiateMenu(this, item);
}
public virtual void OnHierarchyItemClick(HierarchyItem item)
{
}
}
#endregion
public interface IAssetBundleLoader : IScriptableObject
{
public RootObject GetRoot();
}
public static class AssetBundlesLoadHelper
{
public struct Entry
{
public AssetBundle assetBundle;
public int referenceCounter;
public PropertiesWindow.ItemEntry entry;
public Entry(AssetBundle assetBundle, int referenceCounter = 0, PropertiesWindow.ItemEntry entry = null)
{
this.assetBundle = assetBundle;
this.referenceCounter = referenceCounter;
this.entry = entry;
}
}
public static Dictionary AssetBundleCounter = new();
private static Dictionary AssetBundleLoading = new();
private static PropertiesWindow.ItemEntry AssetBundlesItemEntry = null;
public static IEnumerator LoadAssetBundleAsync(string ab, Action callback)
{
if (AssetBundleCounter.TryGetValue(ab, out var result))
{
result.referenceCounter++;
callback?.Invoke(result.assetBundle);
yield break;
}
if (AssetBundleLoading.TryGetValue(ab, out var tir))
{
yield return tir;
if (AssetBundleCounter.TryGetValue(ab, out result))
{
result.referenceCounter++;
callback?.Invoke(result.assetBundle);
yield break;
}
}
var file = new ToolFile(PlatformIndicator.StreamingAssetsPath) | "AssetBundle/";
#if PLATFORM_WINDOWS
file = file | "Windows";
#endif
file = file | ab;
#if ENABLE_CLASS_Interaction
var downloader = new Interaction(file.GetFullPath());
#else
var downloader = new ToolFile(file.GetFullPath());
#endif
if (AssetBundlesItemEntry == null)
{
var hierarchy = HierarchyWindow.instance;
AssetBundlesItemEntry = hierarchy.CreateRootItemEntryWithBinders(nameof(AssetBundlesLoadHelper))[0];
AssetBundlesItemEntry.ref_value.GetComponent().title = nameof(AssetBundlesLoadHelper);
}
var abLoadingItem = AssetBundlesItemEntry.ref_value.GetComponent().CreateSubPropertyItem(1)[0];
var loadingHierarchyItem = abLoadingItem.ref_value.GetComponent();
loadingHierarchyItem.title = $"{ab}";
var ir = downloader.LoadAsAssetBundle(p =>
{
loadingHierarchyItem.title = $"{ab}";
}, x =>
{
if (x != null)
{
loadingHierarchyItem.title = $"{ab}";
var assets = x.GetAllAssetNames();
var subAssetsItems = loadingHierarchyItem.CreateSubPropertyItem(assets.Length);
for (int i = 0, e = assets.Length; i < e; i++)
{
subAssetsItems[i].ref_value.GetComponent().title = Path.GetFileName(assets[i]);
}
AssetBundleCounter[ab] = new(x, 1, abLoadingItem);
}
else
{
loadingHierarchyItem.title = $"{ab}";
}
AssetBundleLoading.Remove(ab);
callback?.Invoke(x);
});
AssetBundleLoading.Add(ab, ir);
yield return ir;
AssetBundleLoading.Remove(ab);
}
public static IEnumerator LoadAssetBundle(this IAssetBundleLoader self, string ab, Action callback)
{
yield return LoadAssetBundleAsync(ab, callback);
}
public static IEnumerator UnloadAssetBundle(this IAssetBundleLoader self, string ab)
{
// Editor中暂时忽略卸载功能
yield break;
}
}
public abstract class TimelineScriptObject : ScriptableObject
{
public static PropertiesWindow TimelineWindow;
private PropertiesWindow.ItemEntry MyTimelineEntry;
private Editor.UI.TimelineItem MyTimelineItem;
//[SerializeField] private bool IsTimelineItemShow = false;
private static List TimelineScriptObjectWhichOnShow = new();
private static UnityEngine.UI.Image CacheLastFocusImage;
private static Color CacheLastFocusImageOriginColor = new(1, 1, 1, 0.01f);
private static Color FocusImageColor = new(1f, 47f / 51f, 0.0156862754f, 0.1f);
[Content, SerializeField] private bool IsEnableTimelineItem = false;
[Convention.RScript.Variable.Attr.Method]
public void EnableTimelineItem()
{
IsEnableTimelineItem = true;
}
protected override IEnumerator DoSomethingDuringApplyScript()
{
yield return base.DoSomethingDuringApplyScript();
if (IsEnableTimelineItem)
{
if (MyTimelineEntry == null)
{
MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0];
MyTimelineItem = MyTimelineEntry.ref_value.GetComponent();
}
MyTimelineItem.title = ScriptName;
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
MyTimelineItem.AddListener(() =>
{
HierarchyWindow.instance.MakeFocusOn(MyHierarchyItem.GetHierarchyItem());
if (CacheLastFocusImage != null)
CacheLastFocusImage.color = CacheLastFocusImageOriginColor;
CacheLastFocusImage = MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent();
CacheLastFocusImage.color = FocusImageColor;
});
SetupTimelineItem(MyTimelineItem);
TimelineScriptObjectWhichOnShow.Add(this);
}
}
public override IEnumerator UnloadScript()
{
yield return base.UnloadScript();
// 这里的两处判空是因为如果抛出错误就会打断了逻辑, 所以这里需要判断
if (MyTimelineItem != null)
{
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
MyTimelineItem = null;
}
if (MyTimelineEntry != null)
{
MyTimelineEntry.Release();
MyTimelineEntry = null;
}
}
public override void OnHierarchyItemClick(HierarchyItem item)
{
}
private float UIResizeOnTimelineCount = 0;
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
base.UpdateTicks(currentTime, deltaTime, tickType);
// 存在严重的性能开销, 在解决之前将不会允许其快速自动更新
if (IsEnableTimelineItem)
{
if (UIResizeOnTimelineCount > 0.1 || tickType != TickType.Update)
{
UIResizeOnTimelineCount = 0;
MyTimelineItem.ResizeOnTimeline();
}
UIResizeOnTimelineCount += deltaTime;
}
}
protected abstract void SetupTimelineItem(Editor.UI.TimelineItem item);
protected virtual Color GetTimelineItemColor()
{
static float Foo(uint hash)
{
float a = (hash % 10) * 0.1f;
return a;
}
var a1 = (uint)this.GetType().GetHashCode();
var a2 = (uint)a1.ToString().GetHashCode();
var a3 = (uint)(a2.ToString().GetHashCode() >> 5);
var color = new Vector3(Foo(a1), Foo(a2), Foo(a3)).normalized;
return new(color.x, color.y, color.z);
}
}
}