一个LUA配置检测工具

Posted withtimer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个LUA配置检测工具相关的知识,希望对你有一定的参考价值。

SHACHIDAYS feat.YOSHIKI EZAKI (Official Video).mp3 03:45

最近摸鱼有点多了,之前几天,因为"lucky" dog策划配置出问题,导致项目崩了好几次,昨天说要写一个配置检测工具,哎,其实这个需求也是可有可无,如果lua配置表是在ide环境下编辑的有没有错误,在编辑的时候就能发现了。但是对方提了一堆假设,实在拗不过。不写也得写了。


按照以往的经历这种检测工具一般都是做字符串匹配来进行效验的,针对的只是格式上的检测。不过看了那一坨配置表,实在难以下手,比如需要 以{开头的table,结尾处必须是},而元素间又需要以,分割,另外以键值对的形式存在的配置,就更麻烦了,更何况是多层嵌套的那种。首先先不排除正则方法的可行性,确实是可行的,但是基本上代码量会特别夸张。虽然这种工具是在编辑模式下运行,基本上不考虑性能,只需要能动就行。但是依旧是无从下手。


然后这类配置都会在运行时被当成model导入到各种业务中,也就是说这些配置,准备来说应该叫事先拟定好的变量。居然如此,那么这类配置的正确性就取决于编译是否通过。那这就好办了,那么该如何做呢?


先在c#中取得lua运行环境,然后把这类配置通过io方式取得字符串再丢到luaenv中去执行,如果编译有误,便会立刻被阻断,然后,再被放到luaenv中执行前,我们先输出一下文件名,如果遇到编译不通过的,那么运行即被阻断,即得出配置出错的文件。由此检测的目的就达到了。


so

按文件读取配置,放入到luaenv中去执行:

using UnityEngine;using System.Collections;using System.Collections.Generic;using UnityEditor;using System.IO;using System.Text;using XLua;using GYEngine;
[InitializeOnLoad]public class FasterCheck{ [MenuItem("Tools/手写配置检测", false, 10)] static private void CheckConfig() { string checkPath = GetCheckPath(); FileInfo[] files = GetCheckFiles(checkPath); Debug.Log("<color=yellow>检查Start--------------------------------------------</color>"); foreach (System.IO.FileInfo f in files) { StartCheck(f); } Debug.Log("<color=yellow>检查End----------------------------------------------</color>");    } public static string GetCheckPath() {
string dirPath = Application.dataPath; string[] truelyPath = dirPath.Split('/'); string checkPath = ""; for (int i = 0; i <= truelyPath.Length - 3; i++) { checkPath += (truelyPath[i] + "/"); } checkPath += "Lua/Config/Handwork"; return checkPath; }
public static FileInfo[] GetCheckFiles(string checkPath) { DirectoryInfo dir = new DirectoryInfo(checkPath); FileInfo[] files = dir.GetFiles(); return files;    } public static void StartCheck(FileInfo f) { if (luaenv == null) { luaenv = new LuaEnv(); return; } string contentCode = File.ReadAllText(f.FullName, Encoding.Default); luaenv.DoString(contentCode); }}


这样的话,假设是单纯赋值的配置表完全就够用了,但是我发现在许多配置表中还存在。GameConf.Name.XXX这样一类的对象间相互引用的问题,或者是CsVector这一类的new出来的对象。因为这样单独运行某一个文件是缺乏上下文联系的。本来这些配置根本没有任何问题,但因为在编译运行时这些未能索引到的变量直接触发了报错,从而中断了程序,产生了误报。当时想到的第一个处理方式就是使用try catch封装起来,但是,lua是没有try catch 这类异常处理机制的,所以,这里实现了一个比较简单的try catch 。用来把运行时的错误catch出来,免得中断程序。

function try(block) local tablejoin = function (...) local result = {} for _, t in ipairs({...}) do if type(t) == 'table' then for k, v in pairs(t) do if type(k) == 'number' then table.insert(result, v) else result[k] = v end end else table.insert(result, t) end end return result end  -- get the try function local try = block[1] assert(try)  -- get catch and finally functions local funcs = tablejoin(block[2] or {}, block[3] or {})  -- try to call it local result_error = {} local results = {pcall(try)} if not results[1] then -- run the catch function if funcs and funcs.catch then result_error = {funcs.catch(results[2])} end end  -- run the finally function if funcs and funcs.finally then local result_fin = {funcs.finally(table.unpack(results))} if #result_fin > 0 then return table.unpack(result_fin) end end  -- ok? if results[1] and #results > 1 then return table.unpack(results, 2, #results) else if #result_error > 0 then return table.unpack(result_error) else return nil end end end  function catch(block) -- get the catch block function return {catch = block[1]} end  function finally(block) -- get the finally block function return {finally = block[1]} end



这时候就能把运行时的错误catch到,从而不中断了。这时候只需要把代码放入到 --RUN CODE中执行即可了。

function check()  return try { function ()             --RUN CODE           end, catch{ function(errors)   end }, finally {function(ok, errors)  end                          }        } end 

但这样也会引发一个问题,虽然代码能正确执行了,但是输出的错误信息的位置是有误的。因为按上面这种执行方式等于是把代码当成字符串拼接起来运行,所以输出的错误信息的行数是有问题的,在源表上根本对应不上。就类似于下图这样。比如配置出问题实际上是 lline:2,但是由于做了代码拼接所定位到的错误就变成了 line:41了。这个还很好解决,我们只需要把try catch结构和我们读取出来的配置文件string ,放到第一行中,最后一行调用即可。

即: try catch 结构 + 配置content + try catch 定义 + 调用。


 static string codeCheck = @" function try(block) local tablejoin = function (...) local result = {} for _, t in ipairs({...}) do if type(t) == 'table' then for k, v in pairs(t) do if type(k) == 'number' then table.insert(result, v) else result[k] = v end end else table.insert(result, t) end end return result end  -- get the try function local try = block[1] assert(try)  -- get catch and finally functions local funcs = tablejoin(block[2] or {}, block[3] or {})  -- try to call it local result_error = {} local results = {pcall(try)} if not results[1] then -- run the catch function if funcs and funcs.catch then result_error = {funcs.catch(results[2])} end end  -- run the finally function if funcs and funcs.finally then local result_fin = {funcs.finally(table.unpack(results))} if #result_fin > 0 then return table.unpack(result_fin) end end  -- ok? if results[1] and #results > 1 then return table.unpack(results, 2, #results) else if #result_error > 0 then return table.unpack(result_error) else return nil end end end  function catch(block) -- get the catch block function return {catch = block[1]} end  function finally(block) -- get the finally block function return {finally = block[1]} end
    check()";    ..........    ..........    .........        string contentCode = File.ReadAllText(f.FullName, Encoding.Default);    Debug.Log("<color=#33CC00>正在检查文件:" + f.Name + "</color>"); string code = "function check() return try {function () " + contentCode + " end,catch{ function(errors) end}, finally {function(ok, errors) end}} end ";    luaenv.DoString(code + codeCheck);

这样就能进行完美检测了,运行效果:

配置完全没问题。


配置出错。

这样就一目了然了,指出错误的文件,并指出错位置line。


另外的,如果检测这个动作是需要用户去执行的,显然感觉这个检测工具就不太人性化了,我们需要的是用户修改了某个配置文件,工具自动去检测对应的文件,因为当前你只需要对自己做修改的文件负责。

所以我们可以在类中添加[InitializeOnLoad](它可以保证在编辑器启动的时候调用此函数)标记,并在静态构造函数中去绑定文件变化的委托事件来达到这个目的。运行结果如下:



完整代码如下:


using UnityEngine;using System.Collections;using System.Collections.Generic;using UnityEditor;using System.IO;using System.Text;using XLua;using GYEngine;
[InitializeOnLoad]public class FasterCheck{ static LuaEnv luaenv; static string checkPath = ""; static FasterCheck() { checkPath = GetCheckPath(); luaenv = new LuaEnv(); FileManager.Instance.OnFileChangeEvent += OnLuaFileChange; }

static string codeCheck = @" function try(block) local tablejoin = function (...) local result = {} for _, t in ipairs({...}) do if type(t) == 'table' then for k, v in pairs(t) do if type(k) == 'number' then table.insert(result, v) else result[k] = v end end else table.insert(result, t) end end return result end -- get the try function local try = block[1] assert(try) -- get catch and finally functions local funcs = tablejoin(block[2] or {}, block[3] or {}) -- try to call it local result_error = {} local results = {pcall(try)} if not results[1] then -- run the catch function if funcs and funcs.catch then result_error = {funcs.catch(results[2])} end end -- run the finally function if funcs and funcs.finally then local result_fin = {funcs.finally(table.unpack(results))} if #result_fin > 0 then return table.unpack(result_fin) end end -- ok? if results[1] and #results > 1 then return table.unpack(results, 2, #results) else if #result_error > 0 then return table.unpack(result_error) else return nil end end end function catch(block) -- get the catch block function return {catch = block[1]} end function finally(block) -- get the finally block function return {finally = block[1]} end
check()";
[MenuItem("Tools/手写配置检测", false, 10)] static private void Find() { FileInfo[] files = GetCheckFiles(checkPath); Debug.Log("<color=yellow>检查Start--------------------------------------------</color>"); foreach (System.IO.FileInfo f in files) { StartCheck(f); } Debug.Log("<color=yellow>检查End----------------------------------------------</color>"); }
public static void OnLuaFileChange(string filePath) {
FileInfo f = CheckContain(filePath); if (f != null) { StartCheck(f); } }
public static string GetCheckPath() {
string dirPath = Application.dataPath; string[] truelyPath = dirPath.Split('/'); string checkPath = ""; for (int i = 0; i <= truelyPath.Length - 3; i++) { checkPath += (truelyPath[i] + "/"); } checkPath += "Lua/Config/Handwork"; return checkPath; }
public static FileInfo[] GetCheckFiles(string checkPath) { DirectoryInfo dir = new DirectoryInfo(checkPath); FileInfo[] files = dir.GetFiles(); return files; }
public static FileInfo CheckContain(string filePath) { FileInfo[] files = GetCheckFiles(checkPath); foreach (System.IO.FileInfo f in files) { if (filePath.Equals(f.FullName)) { return f; } } return null; }
public static void StartCheck(FileInfo f) { if (luaenv == null) { return; } string contentCode = File.ReadAllText(f.FullName, Encoding.Default); Debug.Log("<color=#33CC00>正在检查文件:" + f.Name + "</color>"); string code = "function check() return try {function () " + contentCode + " end,catch{ function(errors) end}, finally {function(ok, errors) end}} end "; luaenv.DoString(code + codeCheck); }}


一个小小的工具就完成了~

晚安~


以上是关于一个LUA配置检测工具的主要内容,如果未能解决你的问题,请参考以下文章

本地资源检测支持Lua检测!

solr分布式索引实战分片配置读取:工具类configUtil.java,读取配置代码片段,配置实例

[lua][openresty]代码覆盖率检测的解决方式

C++用来检测数据类型的声明工具源码

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

如何配置一套优雅的Lua开发环境