unity lua ts等脚本文件 traceback 跳转

3,427 阅读4分钟

unity 使用设置的ide打开对应的脚本文件

前言

之前在一家比较大的游戏公司上班,进入项目组的时候已经完成了项目框架搭建,基本的功能都有了,其中就包括lua traceback的跳转,换了一家公司发现没有这个功能,于是自己上手处理了一下这个部分,目前看起来网上的方案不是很多,我的方案也比较具有适用性; 相关实现:

实现

实现思路:

  • 通过字符串替换将lua traceback转换成hyperlink的形式;
  • 点击hyperlink 触发打开资源的流程,实现自定义的跳转;

具体实现

lua traceback 实例:

stdin:1: attempt to index a nil value (global 't')
stack traceback:
        stdin:1: in main chunk
        [C]: in ?

lua traceback 格式如下:\t file:line: in XXX 使用c# regex 替换字符串实现如下: 使用正则表达式识别\t\w+(.{1}\w+)+:\d+: 然后获取path和line,使用path生成当前项目相对Assets的路径,并生成<a href=\"{0}.lua\" line=\"{1}\">{2}</a>这种超链接形式的文本;

#if UNITY_EDITOR
    // 写死luapath
    private static string luaPath = "Assets/Scripts/Lua";
    static readonly string pattern = @"\t\w+(.{1}\w+)+:\d+: ";
    static readonly string template = "<a href=\"{0}.lua\" line=\"{1}\">{2}</a>";
#endif
    public static string ReplaceTraceBack(string msg)
    {
#if UNITY_EDITOR
        return Regex.Replace(msg, pattern, (Match match) =>
        {
            string[] infos = match.Value.TrimStart('\t').Split(':');
            if (infos.Length < 2)
            {
                return match.Value;
            }
            string filePath = luaPath + infos[0];
            string line = infos[1];
            return string.Format(template, filePath.Replace(".", "/"), line, match.Value);

        }
            );
#else
        return msg;
#endif
    }

由于我不是正则高手,所以可能表达式不是很优雅,测试下来整体时间开销在电脑上还行,但是线上应该不太可行,如果有更加优雅的方案,请和我联系

这样子我们就实现了lua traceback的超链接实现,点击会打开默认编辑器打开lua文件:

image.png

这里我们实现了tracebackunity Asset的改变,其他类型的脚本的堆栈报错都可以使用该方法实现转换为点击跳转对应Asset,后面我们需要使用ide打开特定的lua代码;

指定对应的luaIDE打开对应的Lua代码

这里直接借用luaProfile的实现:

static string LUA_IDE_KEY = "LuaIDE";

[UnityEditor.Callbacks.OnOpenAssetAttribute(1)]
static bool OnOpenLuaAsset(int instance, int line)
{
    string path = AssetDatabase.GetAssetPath(instance);
    if (path.EndsWith(".lua") || path.EndsWith(".lua.txt"))
    {
        return OpenFileAtLineExternal(path, line);
    }
    return false;
}
static bool OpenFileAtLineExternal(string filePath, int line)
{
    string editorPath = EditorPrefs.GetString(LUA_IDE_KEY);
    // 没有path就弹出面板设置
    if (string.IsNullOrEmpty(editorPath) || !File.Exists(editorPath))
    {
        SetExternalEditorPath();
        editorPath = EditorPrefs.GetString(LUA_IDE_KEY);
    }
    if (string.IsNullOrEmpty(editorPath) || !File.Exists(editorPath))
    {
        return false;
    }
    System.Diagnostics.Process proc = new System.Diagnostics.Process();
    proc.StartInfo.FileName = editorPath;
    string procArgument = "";

    if (editorPath.IndexOf("Code") != -1 || editorPath.IndexOf("code") != -1)
    {
        procArgument = string.Format("-g {0}:{1}", filePath, line > 0 ? line : 0);
    }
    else if (editorPath.IndexOf("idea") != -1 || editorPath.IndexOf("clion") != -1 || editorPath.IndexOf("rider") != -1)
    {
        procArgument = string.Format("--line {0} {1}", line, filePath);
        Debug.Log(procArgument);
    }
    else
    {
        procArgument = string.Format("{0}:{1}", filePath, line);
    }
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.CreateNoWindow = true;
    proc.StartInfo.Arguments = procArgument;
    proc.Start();
    return true;
}

[MenuItem("Tools/Select LuaIDE")]
public static void SetExternalEditorPath()
{
    string path = EditorPrefs.GetString(LUA_IDE_KEY);
    path = EditorUtility.OpenFilePanel("Select Lua IDE", path, "");
    if (path != "")
    {
        EditorPrefs.SetString(LUA_IDE_KEY, path);
        Debug.Log("Set Lua IDE Path: " + path);
    }
}

使用EditorPrefs存储luaide的可执行路径, 不要使用PlayerPrefs,避免和游戏的数据混淆。

更近一步

1、同时获取Lua本身的抛出的error

public void ThrowExceptionFromError(int oldTop)
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnvLock)
    {
#endif
        object err = translator.GetObject(L, -1);
        LuaAPI.lua_settop(L, oldTop);

        // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved)
        Exception ex = err as Exception;
        if (ex != null) throw ex;

        // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it
        if (err == null) err = "Unknown Lua Error";
#if UNITY_EDITOR
    err = StringHelper.ReplaceTraceBack(err.ToString());
#endif
    throw new LuaException(err.ToString());
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}

对于lua手动抛出的异常只需要手动用ReplaceTraceBack包装一层就好了,同时需要注意避免线上使用,减少字符串正则消耗;

2、实现原理

该方案最主要的原理在于unity怎么处理hyperlink的,这里我ILSpy了一下UnityEditor.dll看了一下实现:

private void EditorGUI_HyperLinkClicked(object sender, EventArgs e)  
{  
    EditorGUILayout.HyperLinkClickedEventArgs hyperLinkClickedEventArgs = (EditorGUILayout.HyperLinkClickedEventArgs)e;  
    if (hyperLinkClickedEventArgs.hyperlinkInfos.TryGetValue("href"out var value) && hyperLinkClickedEventArgs.hyperlinkInfos.TryGetValue("line"out var value2))  
    {  
        int line = int.Parse(value2);  
        string value3 = value.Replace('\\''/');  
        if (!string.IsNullOrEmpty(value3))  
        {  
            LogEntries.OpenFileOnSpecificLineAndColumn(value, line, -1);  
        }  
    }  
}

也就是说你的超链接文本带有<a href="path" line="1">这种类似文本就可以调用该方法。更进一步的,我发现unity官方的traceback方案实际上也是使用的这个方法:

internal static string StacktraceWithHyperlinks(string stacktraceText)
{
    StringBuilder stringBuilder = new StringBuilder();
    string[] array = stacktraceText.Split(new string[1] { "\n" }, StringSplitOptions.None);
    for (int i = 0; i < array.Length; i++)
    {
            string text = ") (at ";
            int num = array[i].IndexOf(text, StringComparison.Ordinal);
            if (num > 0)
            {
                    num += text.Length;
                    if (array[i][num] != '<')
                    {
                            string text2 = array[i].Substring(num);
                            int num2 = text2.LastIndexOf(":", StringComparison.Ordinal);
                            if (num2 > 0)
                            {
                                    int num3 = text2.LastIndexOf(")", StringComparison.Ordinal);
                                    if (num3 > 0)
                                    {
                                            string text3 = text2.Substring(num2 + 1, num3 - (num2 + 1));
                                            string text4 = text2.Substring(0, num2);
                                            stringBuilder.Append(array[i].Substring(0, num));
                                            stringBuilder.Append("<a href=\"" + text4 + "\" line=\"" + text3 + "\">");
                                            stringBuilder.Append(text4 + ":" + text3);
                                            stringBuilder.Append("</a>)\n");
                                            continue;
                                    }
                            }
                    }
            }
            stringBuilder.Append(array[i] + "\n");
    }
    if (stringBuilder.Length > 0)
    {
            stringBuilder.Remove(stringBuilder.Length - 1, 1);
    }
    return stringBuilder.ToString();
}

本质上也是字符串替换成为hyperLink方法,真的非常有意思;

工程中碰到的额外的问题

1、mac 编辑器选择问题

mac 选择可执行文件方式有点奇怪,打开finder找不到可执行文件,安装code只会显示app文件,但是文件系统里这个app文件会被当作文件夹,而非文件,但是选择的界面里面又会是文件,所以需要额外的处理(有点反直觉):

image.png 采取的方案是右键点击对应的app,然后选择里面可执行文件:

image.png

image.png 对此我的评价是不知道哪里出了什么问题,但是只需要配置这一次就可以用了,所以也没有额外的修改; 如果有更好的方案提供,请私信或者留言,非常感谢~

2、unityEditor点击lua文件问题

这个解决方案重写了lua的打开方法,会导致点击项目里面的lua文件或者lua.txt文件都会执行OnOpenAsset(filepath, -1),但是code -g filepath:-1会导致无法打开文件,具体报错如下:

image.png

所以改动代码加一个异常判断:

if (editorPath.IndexOf("Code") != -1 || editorPath.IndexOf("code") != -1)
{
    procArgument = string.Format("-g {0}:{1}", filePath, line > 0 ? line : 0);
}