Unity编辑器扩展——事件监听监测工具

在项目开发中,我们通过会在脚本中有一些成对存在的方法,例如添加监听和移除监听,如果它们不能成对,可能会导致某些地方产生bug,所以提供了一个事件监听的检测工具。

 主要使用了正则表达式去匹配。

使用AddListener与RemoveListener或RemoveAllListeners去匹配。

核心方法如下:

        /// <summary>
        /// 根据路径匹配成对监听
        /// </summary>
        /// <param name="path"></param>
        private static void FindVmListener(string path)
        {
            var codes = File.ReadAllLines(path, Encoding.UTF8).ToList();

            try
            {
                for (int i = 0,countCountI = codes.Count; i < countCountI; i++)
                {
                    string addListenerRegStr = "(\\S+).AddListener\\((\\S+)\\)";
                    Regex addListenerReg = new Regex(addListenerRegStr);
                    Match addListenerMatch = addListenerReg.Match(codes[i]);
                    var flag = false;
                
                    if (addListenerMatch.Success)
                    {
                        //在这个文件中没有找到对应的移除监听项
                        string removeListenerRegStr = 
                            $"(.+){addListenerMatch.Groups[1].Value}.RemoveListener\\({addListenerMatch.Groups[2].Value}\\)(.+)";
                        Regex removeListenerReg = new Regex(removeListenerRegStr);
                        
                        
                        
                        string removeAllListenerRegStr = 
                            $"(.+){addListenerMatch.Groups[1].Value}.RemoveAllListeners(.+)";
                        Regex removeAllListenerReg = new Regex(removeAllListenerRegStr);
                        
                        //用这个去重新配置如果没有匹配到说明有错误
                        for (int j = 0, countCountJ = codes.Count; j < countCountJ; j++)
                        {
                            Match removeListenerMatch = removeListenerReg.Match(codes[j]);
                            Match removeAllListenerMatch = removeAllListenerReg.Match(codes[j]);
                            
                            if (removeListenerMatch.Success || removeAllListenerMatch.Success)
                            {
                                flag = true;
                                break;
                            }
                        }
                        if (!flag)
                        {
                            string str = $"{path}  第 ${i + 1} 行 AddListener:{addListenerMatch.Value}  RemoveListener:{removeListenerRegStr}";
                            Debug.LogError(str);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogError($"Path:{path}  ExceptionMessage:{e.Message}");
            }
        }

主要思路是通过传入文件路径读出文件内容后,根据以下串进行正则匹配:

string addListenerRegStr = "(\\S+).AddListener\\((\\S+)\\)";

如果匹配到了指定的内容,再根据匹配到内容Groups的第一个Value去组合成需要成组的另外一个待匹配串:

string removeListenerRegStr = 
$"(.+){addListenerMatch.Groups[1].Value}.RemoveListener\\({addListenerMatch.Groups[2].Value}\\)(.+)";
                        
string removeAllListenerRegStr = 
$"(.+){addListenerMatch.Groups[1].Value}.RemoveAllListeners(.+)";

上述方法的缺点:

  • 循环遍历次数过多,另外有一种Leetcode上的两数之和问题,可以用空间换取时间,通过声明字典缓存行内容再去进行匹配,可以消除部分循环,节省时间复杂度。
  • 无法完全匹配出所有不合法的文件,会有疏漏或匹配出错的地方,比如换行的话是没办法正确匹配的。不过一般这种情况相对较少,受限于IDE的局限性,暂无更好的思路。

在查找出不合法的代码文件后,双击控制台能直接定位到指定的代码行,于是查阅了一些网上的资料,发现如下方法,主要是通过反射获取UnityEditor提供的方法:

        private static string GetCallStackInfos()
        {
            var asbUnityEditor = Assembly.GetAssembly(typeof(EditorWindow)); if (asbUnityEditor == null) return null;
            var typConsole = asbUnityEditor.GetType("UnityEditor.ConsoleWindow"); if (typConsole == null) return null;
            var fieldInfo = typConsole.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic); if (fieldInfo == null) return null;
            var insConsole = fieldInfo.GetValue(null); if (insConsole == null) return null;

            if (focusedWindow == (EditorWindow) insConsole)
            {
                var typListViewState = asbUnityEditor.GetType("UnityEditor.ListViewState"); if (typListViewState == null) return null;
                var fieldListView = typConsole.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic); if (fieldListView == null) return null;
                var oListView = fieldListView.GetValue(insConsole); if (oListView == null) return null;
                var txtContent = typConsole.GetField("m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic); if (txtContent == null) return null;
                return txtContent.GetValue(insConsole).ToString();
            }

            return null;
        }

        private static bool _findFlag;

        [UnityEditor.Callbacks.OnOpenAssetAttribute(0)]
        static bool OnOpenAsset(int instance, int line)
        {
            if (_findFlag)
            {
                _findFlag = false;
                return false;
            }

            string stackTrace = GetCallStackInfos();
            if (string.IsNullOrEmpty(stackTrace)) { return false; }

            var path = Regex.Match(stackTrace, "Assets.*?cs").ToString();
            Match m = Regex.Match(stackTrace, "第.*?行");
            string strLine = Regex.Match(m.ToString(), @"([1-9]\d*\.?\d*)|(0\.\d*[1-9])").ToString();
            int.TryParse(strLine, out var lineInCs);
            if (!string.IsNullOrEmpty(path))
            {
                _findFlag = true;

                return AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path), lineInCs);
            }
            return false;
        }

总结:

此工具基本能用于匹配在脚本中需要成对存在的代码段,用于做客户端开发人员脚本检验使用,但存在一些小缺点,总体还是适用性很强,可以根据自己的业务需要很方便的定制化脚本代码校验工具。

如果觉得还不错的话,欢迎收藏、点赞、评论和关注我,我会持续带来更多实用性的通用工具的制作分享博客。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值