Files
Convention-Unity-Demo/Assets/Scripts/Framework/ScriptableObject.cs

1398 lines
50 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Convention;
using Convention.WindowsUI.Variant;
using Demo.Game;
using Flee.PublicTypes;
using Sirenix.OdinInspector;
using Unity.Profiling;
using UnityEngine;
namespace Demo
{
public enum ProjectDefaultFileStyle
{
CPP,
PY
}
[System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class ScriptableCallAttribute : Attribute
{
public string Description;
public ScriptableCallAttribute(string description)
{
this.Description = description;
}
}
[System.AttributeUsage(AttributeTargets.Enum, Inherited = true, AllowMultiple = false)]
public sealed class ScriptableEnumAttribute : Attribute
{
public string Description;
public ScriptableEnumAttribute(string description)
{
this.Description = description;
}
}
public static class ScriptCallUtility
{
// 解析函数调用的方法,支持 with 子句
public static (string functionName, string[] arguments, Dictionary<string, string> withVariables) ParseFunctionCall(string input)
{
// 匹配函数名和参数部分,以及可选的 with 子句
Match match = Regex.Match(input, @"^(\w+)\s*\(\s*(.*?)\s*\)\s*(?:\s+with\s*\(\s*(.*?)\s*\))?\s*;?$");
if (!match.Success)
return (null, new string[0], new Dictionary<string, string>());
string functionName = match.Groups[1].Value;
string argumentsString = match.Groups[2].Value;
string withString = match.Groups[3].Value;
// 解析参数数组
string[] arguments = ParseArguments(argumentsString);
// 解析 with 子句的变量
Dictionary<string, string> withVariables = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(withString))
{
withVariables = ParseWithClause(withString);
}
return (functionName, arguments, withVariables);
}
// 解析 with 子句的方法
private static Dictionary<string, string> ParseWithClause(string withString)
{
var variables = new Dictionary<string, string>();
if (string.IsNullOrWhiteSpace(withString))
return variables;
int i = 0;
while (i < withString.Length)
{
// 跳过空白字符
while (i < withString.Length && char.IsWhiteSpace(withString[i]))
i++;
if (i >= withString.Length)
break;
// 解析变量名
int nameStart = i;
while (i < withString.Length && (char.IsLetterOrDigit(withString[i]) || withString[i] == '_'))
i++;
if (i == nameStart)
break; // 没有找到有效的变量名
string varName = withString.Substring(nameStart, i - nameStart);
// 跳过空白字符
while (i < withString.Length && char.IsWhiteSpace(withString[i]))
i++;
// 检查等号
if (i >= withString.Length || withString[i] != '=')
break; // 没有找到等号
i++; // 跳过等号
// 跳过空白字符
while (i < withString.Length && char.IsWhiteSpace(withString[i]))
i++;
// 解析变量值
string varValue = ExtractArgument(withString, ref i);
variables[varName] = varValue.Trim();
// 跳过空白字符
while (i < withString.Length && char.IsWhiteSpace(withString[i]))
i++;
// 检查逗号
if (i < withString.Length && withString[i] == ',')
{
i++; // 跳过逗号
}
else if (i < withString.Length)
{
break; // 期望逗号但找到了其他字符
}
}
return variables;
}
// 解析参数的方法
private static string[] ParseArguments(string argumentsString)
{
if (string.IsNullOrWhiteSpace(argumentsString))
return new string[0];
var arguments = new List<string>();
int i = 0;
while (i < argumentsString.Length)
{
// 跳过空白字符
while (i < argumentsString.Length && char.IsWhiteSpace(argumentsString[i]))
i++;
// 越界检查
if (i >= argumentsString.Length)
break;
string argument = ExtractArgument(argumentsString, ref i);
if (!string.IsNullOrEmpty(argument))
arguments.Add(argument.Trim());
// 跳过空白字符
while (i < argumentsString.Length && char.IsWhiteSpace(argumentsString[i]))
i++;
// 越界检查
if (i >= argumentsString.Length)
break;
// 必定是逗号, 否则异常情况
if (argumentsString[i] != ',')
throw new InvalidOperationException("Parser is invalid logic");
i++;
}
return arguments.ToArray();
}
// 提取单个参数的方法,支持花括号和字符串字面量
private static string ExtractArgument(string input, ref int index)
{
int start = index;
if (input[index] == '"')
{
// 处理字符串字面量
index++; // 跳过开始的引号
while (index < input.Length)
{
if (input[index] == '"' && (index == 0 || input[index - 1] != '\\'))
{
index++; // 跳过结束的引号
break;
}
index++;
}
}
else if (input[index] == '{')
{
// 处理花括号内的内容
int braceCount = 0;
while (index < input.Length)
{
if (input[index] == '{')
braceCount++;
else if (input[index] == '}')
braceCount--;
index++;
if (braceCount == 0)
break;
}
}
else
{
// 处理普通参数(直到遇到逗号)
while (index < input.Length && input[index] != ',')
index++;
}
return input[start..index];
}
}
[System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class DefaultScriptAttribute : Attribute
{
public string DefaultScript;
public DefaultScriptAttribute(string script)
{
this.DefaultScript = script;
}
}
public static class DefaultScriptUtility
{
public static void WriteStyleMethodHelper(StreamWriter stream,MethodInfo methodInfo)
{
}
public static void WriteDefaultScript(StreamWriter fs, Type type)
{
if (typeof(ScriptableObject).IsAssignableFrom(type) == false)
throw new InvalidOperationException($"{type.FullName} is not inherited from {typeof(ScriptableObject).FullName}");
var attr = type.GetCustomAttribute<DefaultScriptAttribute>();
var typeName = type.Name;
if (typeName.Contains('`'))
{
typeName = typeName[..typeName.LastIndexOf('`')];
}
switch (Editor.EditorController.instance.MainGameController.CurrentProjectDefaultFileStyle)
{
case ProjectDefaultFileStyle.CPP:
{
fs.WriteLine($"#include \"{typeName}.helper.h\"");
if (attr == null)
{
var scriptCalls = (from info in type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
where info is MethodInfo
where info.GetCustomAttribute<ScriptableCallAttribute>(false) != null
select info as MethodInfo).ToList();
if (scriptCalls.Count > 0)
{
foreach (var scriptCall in scriptCalls)
{
fs.WriteLine($"#ifndef {scriptCall.Name}");
fs.WriteLine($"#define {scriptCall.Name}({string.Join(',', (from param in scriptCall.GetParameters() select param.Name))})");
fs.WriteLine($"#endif");
}
}
}
else
{
var text = attr.DefaultScript;
fs.Write(text);
}
}
break;
case ProjectDefaultFileStyle.PY:
{
}
break;
}
}
public static void OpenScriptFile(ScriptableObject so)
{
string path = so.ScriptPath;
var ScriptEditor = so.GetRoot().RootGameController.WhichOpenScript;
try
{
System.Diagnostics.Process.Start(string.Format($"{ScriptEditor}", $"\"{path}\""));
}
catch (Exception ex)
{
Debug.LogError($"Cannt open {path}: {string.Format($"{ScriptEditor}", $"\"{path}\"")}", so);
Debug.LogException(ex, so);
}
}
}
public interface IScriptableObject
{
ScriptableObject SharedInterfaceScriptObject { get; }
}
#region ScriptableObject Inside
public partial class ScriptableObject : IScriptableObject
{
/// <summary>
/// 用于开放给倒置接口
/// </summary>
public ScriptableObject SharedInterfaceScriptObject => this;
}
public partial class ScriptableObject
{
// 时间点系统
public static Dictionary<string, float> TimePointDelta = new();
public static Dictionary<string, float> TimePoints = new();
/// <summary>
/// 重设指定时间线
/// </summary>
/// <param name="id">时间线ID若不存在则创建</param>
/// <param name="delta">当每次调用NextTimePoint函数时使用的单位值</param>
/// <param name="value">初始化时间</param>
[ScriptableCall(@"
<summary>
重设指定时间线
</summary>
<param name=""id"">时间线ID若不存在则创建</param>
<param name=""delta"">当每次调用NextTimePoint函数时使用的单位值</param>
<param name=""value"">初始化时间</param>
")]
public void ResetTimePoint(string id, string delta, string value)
{
TimePointDelta[id] = float.Parse(delta);
TimePoints[id] = float.Parse(value);
}
/// <summary>
/// 推动时间线前进
/// </summary>
/// <param name="id">时间线ID</param>
/// <param name="times">前进次数,最终时间的增量为前进次数乘该时间线的单位值</param>
[ScriptableCall(@"
<summary>
推动时间线前进
</summary>
<param name=""id"">时间线ID</param>
<param name=""times"">前进次数,最终时间的增量为前进次数乘该时间线的单位值</param>
")]
public void NextTimePoint(string id, string times)
{
TimePoints[id] += TimePointDelta[id] * float.Parse(times);
}
/// <summary>
/// 设置时间线的值
/// </summary>
/// <param name="id">时间线ID</param>
/// <param name="value">次数,时间线的值将被设置为次数乘该时间线的单位值</param>
[ScriptableCall(@"
<summary>
设置时间线的值
</summary>
<param name=""id"">时间线ID</param>
<param name=""value"">次数,时间线的值将被设置为次数乘该时间线的单位值</param>
")]
public void SetTimePoint(string id, string value)
{
TimePoints[id] = TimePointDelta[id] * float.Parse(value);
}
}
/// <summary>
/// 上下文系统
/// </summary>
public partial class ScriptableObject
{
[Content] public Dictionary<string, float> ScriptContextSpace = new();
public bool GetCompleteScriptContext(string key, out float value)
{
if (ScriptContextSpace.TryGetValue(key, out value) == false)
{
if (Parent == null)
return false;
return Parent.GetCompleteScriptContext(key, out value);
}
return true;
}
public void GetCompleteScriptContext(ref Dictionary<string, float> context)
{
var current = this;
while (current != null)
{
foreach (var key in current.ScriptContextSpace.Keys)
{
context[key] = current.ScriptContextSpace[key];
}
current = current.Parent;
}
}
/// <summary>
/// 设置局部上下文变量,将会传递给子物体使用
/// </summary>
/// <param name="name">字符串</param>
/// <param name="value">浮点数</param>
[ScriptableCall(@"
<summary>
设置局部上下文变量,将会传递给子物体使用
</summary>
<param name=""name"">字符串</param>
<param name=""value"">浮点数</param>
")]
public void SetContext(string name, string value)
{
ScriptContextSpace[name] = Parse(value);
}
}
/// <summary>
/// 数值解析工具
/// </summary>
public partial class ScriptableObject
{
private ExpressionContext ExpressionParserCreater()
{
ExpressionContext context = new();
context.Imports.AddType(typeof(Mathf));
Dictionary<string, float> vars = new();
GetCompleteScriptContext(ref vars);
foreach (var item in vars)
{
context.Variables[item.Key] = item.Value;
}
return context;
}
public static float OneBarTime = 1;
/// <summary>
/// <list type="bullet">从时间点列表<see cref="TimePoints"/>中获取</list>
/// <list type="bullet">或是从上下文变量<see cref="GetCompleteScriptContext"/>中获取</list>
/// <list type="bullet">或是从数据驱动对象<see cref="DDT{float}"/>的<see cref="float"/>泛型中获取</list>
/// <list type="bullet">或是直接调用<see cref="float.Parse(string)"/></list>
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public float Parse(string value)
{
value = value.Trim();
if(value.StartsWith("\"")&&value.EndsWith("\""))
{
value = value[1..^1];
}
if (TimePoints.TryGetValue(value, out var result))
return result;
if (GetCompleteScriptContext(value, out result))
return result;
if(value.EndsWith(']'))
{
{
Regex regex = new(@"^(.+)\[(.+)\]$");
var match = regex.Match(value);
if (match.Success)
{
return (FindWithPath(match.Groups[1].Value) as DDT).Datas[(int)this.Parse(match.Groups[2].Value)];
}
}
{
Regex regex = new(@"^(.+)\[\]$");
var match = regex.Match(value);
if (match.Success)
{
return (FindWithPath(match.Groups[1].Value) as DDT).Datas.Count;
}
}
throw new ArgumentException("value is end by ']' but not match on any invlid parse");
}
if (value.EndsWith('}'))
{
{
Regex regex = new(@"^\{\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\}$");
var match = regex.Match(value);
if (match.Success)
{
int barSplitTimes = int.Parse(match.Groups[1].Value);
int barCount = int.Parse(match.Groups[2].Value);
int tickCount = int.Parse(match.Groups[3].Value);
return (barCount + tickCount / (float)barSplitTimes) * OneBarTime;
}
}
throw new ArgumentException("value is end by '}' but not match on any invlid parse");
}
try
{
if (float.TryParse(value, out var _result))
return _result;
else
return ExpressionParserCreater().CompileGeneric<float>(value).Evaluate();
}
catch(Exception ex)
{
throw new FormatException($"{value} is not support any Parser", ex);
}
}
protected T ConvertValue<T>(string str)
{
return ConventionUtility.convert_xvalue<T>(str);
}
}
/// <summary>
/// 先天支持的工具函数
/// </summary>
public partial class ScriptableObject
{
[Content, SerializeField] private Vector3
EnterGameLocalPosition = Vector3.zero,
EnterGameEulerAngles = Vector3.zero,
EnterGameLocalScaling = Vector3.one;
[Content, SerializeField] private bool IsSetObjectDisable = false;
/// <summary>
/// 设置坐标
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置坐标
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalPosition(string x, string y, string z)
{
EnterGameLocalPosition = new(Parse(x), Parse(y), Parse(z));
}
/// <summary>
/// 设置欧拉角
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置欧拉角
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalEulerAngles(string x, string y, string z)
{
EnterGameEulerAngles = new(Parse(x), Parse(y), Parse(z));
}
/// <summary>
/// 设置缩放
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
[ScriptableCall(@"
<summary>
设置缩放
</summary>
<param name=""result""></param>
<param name=""y""></param>
<param name=""z""></param>
")]
public void SetLocalScaling(string x, string y, string z)
{
EnterGameLocalScaling = new(Parse(x), Parse(y), Parse(z));
}
/// <summary>
/// 关闭该物体,
/// 在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
/// </summary>
[ScriptableCall(@"
<summary>
关闭该物体,
在面对如多Game场景时关闭某些GameWorld中默认存在的全局灯光等场景时非常有用
</summary>
")]
public void SetObjectDisable()
{
IsSetObjectDisable = true;
}
private void ResetScriptableObjectEnterGameStatus()
{
transform.localPosition = EnterGameLocalPosition;
transform.localEulerAngles = EnterGameEulerAngles;
transform.localScale = EnterGameLocalScaling;
if (IsSetObjectDisable)
{
gameObject.SetActive(false);
}
}
}
/// <summary>
/// UpdatePerFrame相关
/// </summary>
public partial class ScriptableObject
{
[Content] public int UpdatePerFrame = 1;
/// <summary>
/// 指定多少个<see cref="TickType.Update"/>状态的<see cref="UpdateTicks(float, float, TickType)"/>执行一次更新,不会影响到子物体
/// 属于性能优化的高级选项
/// </summary>
/// <param name="frame">每frame帧更新一次, 等于0代表不会在<see cref="TickType.Update"/>状态运行</param>
[ScriptableCall(@"
<summary>
指定多少个Update状态的UpdateTicks执行一次更新,不会影响到子物体
属于性能优化的高级选项
</summary>
<param name=""frame"">每frame帧更新一次, 等于0代表不会在Update状态运行</param>
")]
public void SetUpdatePerFrame(string frame)
{
UpdatePerFrame = Mathf.Max(1, int.Parse(frame));
}
}
/// <summary>
/// EnableScript相关
/// </summary>
public partial class ScriptableObject
{
private bool isEnableScript = false;
public void EnableScript(string sourcePath, string scriptPath, string scriptType, ScriptableObject parent)
{
#if UNITY_EDITOR||true
s_PreparePerfMarker = new(ScriptName);
#endif
if (isEnableScript)
{
Debug.LogError($"ScriptableObject is currently enableScript, start coroutine {nameof(UnloadScript)} to disable", this);
return;
}
this.SourcePath = sourcePath;
this.ScriptPath = scriptPath;
this.ScriptTypename = scriptType;
this.Parent = parent;
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 + $"<{scriptType}>";
MyHierarchyItem.GetHierarchyItem().target = this;
MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent<Editor.UI.RightClick>().ScriptObjectMenu = OnHierarchyItemRightClick;
//var parentHierarchyItem = MyHierarchyItem.GetParent();
//if (parentHierarchyItem != null)
// parentHierarchyItem.GetPropertyListItem().RefreshChilds();
}
public bool EnsureEnableScript()
{
if (isEnableScript == false)
{
Debug.LogError("ScriptableObject is currently disableScript", this);
}
return isEnableScript;
}
}
/// <summary>
/// <para>使用<see cref="ScriptableCallAttribute"/>标记可编辑脚本所能够调用的函数,并附加注释</para>
/// <para>使用<see cref="DefaultScriptAttribute"/>标记派生类,并附加默认模板</para>
/// </summary>
public partial class ScriptableObject : SerializedMonoBehaviour, IHierarchyItemClickEventListener
{
public static Dictionary<string, Type> FastScriptableObjectTypen = new();
public static bool IsAutoPlay = false;
public string SourcePath = "";
public string ScriptName = "";
public string ScriptPath;
public string ScriptTypename;
#if UNITY_EDITOR||true
public ProfilerMarker s_PreparePerfMarker;
#endif
public ScriptableObject Parent;
public RootObject GetRoot()
{
if (Parent == null)
return this as RootObject;
if (Parent is RootObject result)
return result;
else return Parent.GetRoot();
}
public List<ScriptableObject> Childs = new();
// Hierarchy
public PropertiesWindow.ItemEntry MyHierarchyItem;
public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem;
public static int AllScriptableObjectCounter = 0;
// Cache
public static Dictionary<Type, Dictionary<string, MemberInfo>> MethodInvokerCache = new();
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 IEnumerator DoLoadSubScriptAsync([In] string type, [In] string path, [Opt] Action<ScriptableObject> callback)
{
// 判断类型是否合法
if (DefaultInstantiate.GetScriptableObjectInstantiate().TryGetValue(type, out var creater) == false)
{
Debug.LogError($"{type} is not exist or {type}'s Instantiater is not valid", this);
callback?.Invoke(null);
yield break;
}
// 生成对象
var child = creater();
// 路径预处理
if (path.Replace('\\', '/').ToLower().StartsWith(RootObjectQuickPath))
path = $"{new ToolFile(GetRoot().SourcePath) | path[RootObjectQuickPath.Length..]}";
// 获取文件
ToolFile file;
if (File.Exists(path))
file = new(path);
else
file = new ToolFile(SourcePath) | path;
// 找不到脚本
if (file.Exists() == false)
{
Debug.LogError($"{file}<{path}> is not found", this);
callback?.Invoke(null);
yield break;
}
child.ScriptName = file.GetName(true);
child.transform.SetParent(this.transform);
child.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
child.transform.localScale = Vector3.one;
child.EnableScript(file.GetCurrentDirName(), Path.Combine(file.GetCurrentDirName(), file.GetName(false)), type, this);
// Add Child
Childs.Add(child);
// Load Child Script
yield return child.LoadScript(file.LoadAsText());
callback?.Invoke(child);
}
/// <summary>
/// 加载子脚本
/// </summary>
/// <param name="type">指定类型</param>
/// <param name="path">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
[ScriptableCall(@"
<summary>
加载子脚本
</summary>
<param name=""type"">指定类型</param>
<param name=""path"">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
")]
public IEnumerator LoadSubScript([In] string type, [In] string path)
{
return DoLoadSubScriptAsync(type, path, null);
}
/// <summary>
/// 异步加载子脚本
/// </summary>
/// <param name="type">指定类型</param>
/// <param name="path">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
[ScriptableCall(@"
<summary>
异步加载子脚本
</summary>
<param name=""type"">指定类型</param>
<param name=""path"">指定脚本,可用决定路径或与当前脚本目录的相对路径</param>
")]
public void LoadSubScriptAsync([In] string type, [In] string path)
{
StartCoroutine(DoLoadSubScriptAsync(type, path, null));
}
private enum ParseStats
{
None,
Continue,
Break
}
// TODO : 过多的逻辑都挤在这里, 需要拆分
// TODO : 如何统计整个游戏关卡是否加载完成, 尤其是此处的resultEnumerator与ILoadAssetBundle, 将会同时存在多条异步加载的时间线
private IEnumerator ParseScript2Expr(string script)
{
// 预处理
var lines = script.Split('\n');
string preprocessing = "";
foreach (var line in lines)
{
var expr = line.Trim();
//注释语句(py风格与c风格)
if (expr.StartsWith('#') || expr.StartsWith("//"))
continue;
preprocessing += expr;
}
var exprs = preprocessing.Split(';');
ParseStats ParseCommandAndParamaterWords(int commandIterator, ref string command, ref string[] words, ref Dictionary<string,string> withVariables)
{
var expr = exprs[commandIterator].Trim();
//空语句
if (string.IsNullOrEmpty(expr))
return ParseStats.Continue;
//作用域标识符
if (expr.StartsWith('{') || expr.EndsWith('}'))
//忽略导入语句(py风格)
if (expr.StartsWith("import ") || expr.StartsWith("from "))
return ParseStats.Continue;
//进入主函数后中断读取
if (expr.StartsWith("if __name__") || expr.StartsWith("int main") || expr.StartsWith("void main"))
return ParseStats.Break;
if (MethodInvokerCache.ContainsKey(this.GetType()) == false)
{
MethodInvokerCache.Add(this.GetType(), new());
}
//函数解析
(command, words, withVariables) = ScriptCallUtility.ParseFunctionCall(expr);
if (string.IsNullOrEmpty(command))
return ParseStats.Continue;
for (int i = 0, e = words.Length; i < e; i++)
{
//去除
words[i] = words[i].Trim('\"');
}
return ParseStats.None;
}
// Runtime Structures
Dictionary<string, int> commandLabels = new();
for (int commandIterator = 0; commandIterator < exprs.Length; commandIterator++)
{
string command = null;
string[] words = null;
Dictionary<string, string> withVariables = new();
var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words,ref withVariables);
if (stats == ParseStats.Continue)
continue;
else if (stats == ParseStats.Break)
break;
var expr = exprs[commandIterator].Trim();
if (command=="label")
{
if(words.Length<1)
{
Debug.LogError($"in line \"{expr}\", label miss argument", this);
}
commandLabels[words[0]] = commandIterator;
}
}
// Main Loop
for (int commandIterator = 0; commandIterator < exprs.Length; commandIterator++)
{
// Run Stats
string command = null;
string[] words = null;
Dictionary<string, string> withVariables = new();
var stats = ParseCommandAndParamaterWords(commandIterator, ref command, ref words, ref withVariables);
if (stats == ParseStats.Continue)
continue;
else if (stats == ParseStats.Break)
break;
// Logic
if (command == "label")
{
continue;
}
var expr = exprs[commandIterator].Trim();
if (command == "goto")
{
if (words.Length < 3)
{
Debug.LogError($"in line \"{expr}\", goto need 3 arguments, but currently less", this);
yield break;
}
float a = Parse(words[0]), b = Parse(words[1]);
if (a > b)
{
commandIterator = commandLabels[words[2]];
}
continue;
}
// Functions
var paramsList = (from word in words where string.IsNullOrEmpty(word.Trim()) == false select (object)word.Trim()).ToArray();
if (MethodInvokerCache[this.GetType()].TryGetValue(command, out MemberInfo commandInfo) == false)
{
var commandInfo_try = ConventionUtility.SeekMemberInfo(this, new Type[] { typeof(ScriptableCallAttribute) }, null);
commandInfo_try.RemoveAll(x => x.Name != command);
if (commandInfo_try.Count <= 0)
{
Debug.LogError($"in line {expr}, {command} is unable to invoke, because the function is not found", this);
continue;
}
commandInfo = commandInfo_try[0];
MethodInvokerCache[this.GetType()].Add(command, commandInfo);
}
Debug.Log($"in line \"{expr}\" of \"{ScriptPath}\", {command} is try to invoke", this);
IEnumerator resultEnumerator = null;
// 处理 with 子句:保存当前变量状态并设置新变量
Dictionary<string, float> originalVariables = new Dictionary<string, float>();
if (withVariables.Count > 0)
{
foreach (var kvp in withVariables)
{
// 保存原始值(如果存在)
if (ScriptContextSpace.TryGetValue(kvp.Key, out var originalValue))
{
originalVariables[kvp.Key] = originalValue;
}
// 设置新值
ScriptContextSpace[kvp.Key] = Parse(kvp.Value);
}
}
try
{
// 调用成功
if (ConventionUtility.TryInvokeMember(commandInfo, this, out var invokeResult, paramsList) == true)
{
Debug.Log($"in line \"{expr}\" of \"{ScriptPath}\", {command} is invoke succeed", this);
// 尤其用于加载子类时
if (invokeResult != null && invokeResult is IEnumerator _resultEnumerator)
{
resultEnumerator = _resultEnumerator;
}
}
// 调用失败
else
{
MethodInvokerCache[this.GetType()].Remove(command);
var attr = commandInfo.GetCustomAttribute<ScriptableCallAttribute>();
if (attr == null)
Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command} is unable to invoke", this);
else
Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command} is failed to invoke, see: {attr.Description}", this);
}
}
catch (Exception ex)
{
Debug.LogError($"in line \"{expr}\" of \"{ScriptPath}\", {command}({string.Join(',', words)}) is failed to invoke , see: {ex.Message}", this);
Debug.LogException(ex, this);
yield break;
}
yield return resultEnumerator;
// 恢复 with 子句中的变量状态
if (withVariables.Count > 0)
{
foreach (var kvp in withVariables)
{
if (originalVariables.ContainsKey(kvp.Key))
{
// 恢复原始值
ScriptContextSpace[kvp.Key] = originalVariables[kvp.Key];
}
else
{
// 删除不存在的变量
ScriptContextSpace.Remove(kvp.Key);
}
}
}
}
}
[Content] private bool IsEnableUpdate = false;
public virtual IEnumerator LoadScript(string script)
{
if (EnsureEnableScript() == false)
{
yield break;
}
// 增数
{
AllScriptableObjectCounter++;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
}
yield return ParseScript2Expr(script);
IsEnableUpdate = true;
}
private void Reset()
{
StartCoroutine(UnloadScript());
}
public virtual IEnumerator UnloadScript()
{
if (EnsureEnableScript())
{
this.name = "<Unloading>";
try
{
foreach (var child in Childs)
{
yield return child.UnloadScript();
}
}
finally
{
// 清理各种状态
IsEnableUpdate = false;
if (MyHierarchyItem != null)
{
// 卸载UI
MyHierarchyItem.Release();
MyHierarchyItem = null;
}
this.isEnableScript = false;
this.Parent = null;
this.name = "<Unload>";
// 清理Cache
//
// 减数
AllScriptableObjectCounter--;
AllScriptableObjectCounterHierarchyItem.GetHierarchyItem().text = $"ScriptableObjectCount: {AllScriptableObjectCounter}";
}
}
}
public enum TickType
{
Reset,
Pause,
Start,
Update//,
//LateUpdate
}
[Content, SerializeField] private int ScriptUpdateCounter = 0;
public void ScriptUpdate(float currentTime, float deltaTime, TickType tickType)
{
if (IsEnableUpdate == false)
return;
if (gameObject.activeInHierarchy == false)
return;
#if UNITY_EDITOR||true
s_PreparePerfMarker.Begin(this);
#endif
if (tickType == TickType.Reset)
{
ResetEnterGameStatus();
}
// UpdateTicks
if (UpdatePerFrame > 0)
{
if (ScriptUpdateCounter % UpdatePerFrame == 0)
UpdateTicks(currentTime, deltaTime, tickType);
ScriptUpdateCounter += tickType == TickType.Update ? 1 : 0;
}
// Childs UpdateTicks
foreach (var child in Childs)
{
child.ScriptUpdate(currentTime, deltaTime, tickType);
}
#if UNITY_EDITOR||true
s_PreparePerfMarker.End();
#endif
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
public virtual void ResetEnterGameStatus()
{
ResetScriptableObjectEnterGameStatus();
}
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<string, Entry> AssetBundleCounter = new();
private static Dictionary<string, IEnumerator> AssetBundleLoading = new();
private static PropertiesWindow.ItemEntry AssetBundlesItemEntry = null;
public static IEnumerator LoadAssetBundleAsync(string ab, Action<AssetBundle> 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<HierarchyItem>().title = nameof(AssetBundlesLoadHelper);
}
var abLoadingItem = AssetBundlesItemEntry.ref_value.GetComponent<HierarchyItem>().CreateSubPropertyItem(1)[0];
var loadingHierarchyItem = abLoadingItem.ref_value.GetComponent<HierarchyItem>();
loadingHierarchyItem.title = $"{ab}<Loading>";
var ir = downloader.LoadAsAssetBundle(p =>
{
loadingHierarchyItem.title = $"{ab}<Loading{(int)(p * 100)}%>";
}, 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<HierarchyItem>().title = Path.GetFileName(assets[i]);
}
AssetBundleCounter[ab] = new(x, 1, abLoadingItem);
}
else
{
loadingHierarchyItem.title = $"{ab}<Failed>";
}
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<AssetBundle> callback)
{
Debug.Log($"{self.SharedInterfaceScriptObject.ScriptName}.{nameof(LoadAssetBundle)}({ab})", self.SharedInterfaceScriptObject);
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<TimelineScriptObject> TimelineScriptObjectWhichOnShow = new();
private static UnityEngine.UI.Image CacheLastFocusImage;
private static Color CacheLastFocusImageOriginColor = new(1, 1, 1, 0.01f);
private static Color FocusImageColor = new Color(1f, 47f / 51f, 0.0156862754f, 0.1f);
public override IEnumerator LoadScript(string script)
{
MyTimelineEntry = TimelineWindow.CreateRootItemEntries(1)[0];
MyTimelineItem = MyTimelineEntry.ref_value.GetComponent<Editor.UI.TimelineItem>();
yield return base.LoadScript(script);
MyTimelineItem.title = ScriptName;
MyTimelineItem.AddListener(() =>
{
HierarchyWindow.instance.MakeFocusOn(MyHierarchyItem.GetHierarchyItem());
if (CacheLastFocusImage != null)
CacheLastFocusImage.color = CacheLastFocusImageOriginColor;
CacheLastFocusImage = MyHierarchyItem.GetHierarchyItem().ButtonGameObject.GetComponent<UnityEngine.UI.Image>();
CacheLastFocusImage.color = FocusImageColor;
});
SetupTimelineItem(MyTimelineItem);
// 暂时的逻辑是总是展示的
{
TimelineScriptObjectWhichOnShow.Add(this);
//IsTimelineItemShow = true;
}
}
public override void OnHierarchyItemClick(HierarchyItem item)
{
//foreach (var tso in TimelineScriptObjectWhichOnShow)
//{
// tso.IsTimelineItemShow = false;
// tso.MyTimelineItem.RawButton.gameObject.SetActive(false);
//}
//TimelineScriptObjectWhichOnShow.Clear();
//ShowTimelineItemWithChilds();
}
//protected virtual void ShowTimelineItemWithChilds()
//{
// ScriptableObject parent = this;
// List<ScriptableObject> childs = this.Childs;
// if (parent is TimelineScriptObject ptso)
// {
// ptso.IsTimelineItemShow = true;
// }
// foreach (var child in childs)
// {
// if (child is TimelineScriptObject tso)
// {
// tso.IsTimelineItemShow = true;
// }
// }
//}
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
base.UpdateTicks(currentTime, deltaTime, tickType);
//if (IsTimelineItemShow)
//{
MyTimelineItem.ResizeOnTimeline();
//}
}
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);
}
public override IEnumerator UnloadScript()
{
yield return base.UnloadScript();
MyTimelineItem.RawButton.onClick.RemoveAllListeners();
MyTimelineEntry.Release();
MyTimelineEntry = null;
MyTimelineItem = null;
}
}
}