一个LUA配置检测工具
Posted withtimer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个LUA配置检测工具相关的知识,希望对你有一定的参考价值。
最近摸鱼有点多了,之前几天,因为"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;
[ ]
public class FasterCheck
{
[ ]
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;
[ ]
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()";
[ ]
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配置检测工具的主要内容,如果未能解决你的问题,请参考以下文章
solr分布式索引实战分片配置读取:工具类configUtil.java,读取配置代码片段,配置实例