进入RScript与this变量时期(未验证)

This commit is contained in:
2025-10-14 22:57:48 +08:00
parent c23cefd867
commit e693e8dac1
4 changed files with 210 additions and 350 deletions

View File

@@ -23,6 +23,7 @@ namespace Demo.Editor
// <20><><EFBFBD>ƹ<EFBFBD><C6B9>ߺ<EFBFBD>
stream.WriteLine("#define __build_in_pragma #");
stream.WriteLine("#define __build_in_to_text(x) #x");
stream.WriteLine("#define this");
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E0B6A8><EFBFBD><EFBFBD>ʶ<EFBFBD><CAB6>
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
@@ -44,6 +45,10 @@ namespace Demo.Editor
foreach (var method in typeof(Mathf).GetMethods())
{
stream.WriteLine($"#define {method.Name}({string.Join(',', from param in method.GetParameters() select param.Name)})");
}
foreach (var curveType in Enum.GetNames(typeof(MathExtension.EaseCurveType)))
{
stream.WriteLine($"#define {curveType}");
}
// <20><><EFBFBD><EFBFBD>label<65><6C>goto
stream.Write(@"

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Convention;
using Convention.RScript;
using Convention.WindowsUI.Variant;
using Demo.Game;
using Flee.PublicTypes;
@@ -252,23 +253,7 @@ namespace Demo
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
if (attr != null)
{
var text = attr.DefaultScript;
fs.Write(text);
@@ -649,6 +634,11 @@ namespace Demo
public string ScriptPath;
public string ScriptTypename;
// Hierarchy
public PropertiesWindow.ItemEntry MyHierarchyItem;
public static PropertiesWindow.ItemEntry AllScriptableObjectCounterHierarchyItem;
public void EnableScript(string sourcePath, string scriptPath, string scriptType, ScriptableObject parent)
{
#if UNITY_EDITOR||Using_ProfilerMarker
@@ -699,8 +689,204 @@ namespace Demo
/// </summary>
public partial class ScriptableObject
{
public static Dictionary<string, Type> FastScriptableObjectTypen = new();
public static int AllScriptableObjectCounter = 0;
#if UNITY_EDITOR||Using_ProfilerMarker
public ProfilerMarker s_PreparePerfMarker;
#endif
#region LoadSubScript
private static ScriptableObject LastLoadedScriptableObject;
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());
LastLoadedScriptableObject = child;
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>
/// <returns>无法找到时将返回空</returns>
[ScriptableCall(@"
<summary>
获取已加载的脚本对象
</summary>
<returns>无法找到时将返回空</returns>
")]
public ScriptableObject GetLoadedObject(string path)
{
return FindWithPath(path, false);
}
/// <summary>
/// 获取父脚本对象
/// </summary>
/// <returns></returns>
[ScriptableCall(@"
<summary>
获取父脚本对象
</summary>
<returns></returns>
")]
public ScriptableObject GetParentObject()
{
return Parent;
}
/// <summary>
/// 获取上一个加载的脚本对象
/// </summary>
/// <returns></returns>
[ScriptableCall(@"
<summary>
获取上一个加载的脚本对象
</summary>
<returns></returns>
")]
public ScriptableObject GetLastLoadedScriptableObject()
{
return LastLoadedScriptableObject;
}
/*
/// <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));
}
*/
#endregion
private IEnumerator ParseScript2Expr(string script)
{
RScriptEngine engine = new();
RScriptImportClass importClass = new()
{
typeof(Mathf),
};
RScriptVariables variables = new()
{
{ "this", new() { data = this, type = this.GetType() } },
{ "self", new() { data = this, type = this.GetType() } }
};
return engine.RunAsync(script, importClass, variables);
}
[Content] private bool IsEnableUpdate = false;
public enum TickType
{
Reset,
Pause,
Start,
Update
}
[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||Using_ProfilerMarker
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||Using_ProfilerMarker
s_PreparePerfMarker.End();
#endif
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
}
/// <summary>
@@ -710,12 +896,8 @@ namespace Demo
public partial class ScriptableObject : SerializedMonoBehaviour, IHierarchyItemClickEventListener
{
public static Dictionary<string, Type> FastScriptableObjectTypen = new();
public static bool IsAutoPlay = false;
#if UNITY_EDITOR||Using_ProfilerMarker
public ProfilerMarker s_PreparePerfMarker;
#endif
public ScriptableObject Parent;
public RootObject GetRoot()
@@ -729,16 +911,9 @@ namespace Demo
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)
@@ -810,281 +985,6 @@ namespace Demo
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)
{
@@ -1140,51 +1040,6 @@ namespace Demo
}
}
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||Using_ProfilerMarker
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||Using_ProfilerMarker
s_PreparePerfMarker.End();
#endif
}
protected virtual void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
}
public virtual void ResetEnterGameStatus()
{
ResetScriptableObjectEnterGameStatus();

View File

@@ -121,7 +121,7 @@ namespace Demo.Game
{
x = childFilePath[path2.Length..].TrimStart('\\', '/');
}
fs.Write($"\n{nameof(so.LoadSubScript)}({type}, \"{x}\");");
fs.Write($"\nthis.{nameof(so.LoadSubScript)}({type}, \"{x}\");");
// 新建时添加模板内容
using var childFileStream = File.AppendText(childFile);
{