Lua解析器管理器(封装解析器通用函数(销毁解析器,垃圾清理),通过ab包加载lua文件的加载器)

(一)做一个lua管理器,保证解析器的唯一性,封装一些解析器常用函数
单例模式代码:

/// <summary>
/// 单例模式基类
/// </summary>
public class BaseManager<T> where T:new()
{
    private static T instance;
    public static T GetInstance()
    {
        if (instance == null)
        {
            instance = new T();
        }
        return instance;
    }
}
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

/// <summary>
/// 保证解析器的唯一性,先继承单例模式,也提供些解析器的函数
/// </summary>
public class LuaMgr :BaseManager<LuaMgr>
{
    //定义解析器对象
    private LuaEnv luaEnv;
    //初始化解析器
    public void Init()
    {
        if (luaEnv != null)
            return;
        luaEnv = new LuaEnv();
        //自定义加载器
        luaEnv.AddLoader(MyCustomLoader);
    }

    
    private byte[] MyCustomLoader(ref string filepath)
    {
        
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
        Debug.Log(path);
        
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("MyCustomLoader加载器加载lua文件失败");
            return null;
        }

    }
    //执行lua文件
    public void DoString(string str)
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.DoString(str);
    }
    //清空垃圾
    public void Tick()
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.Tick();
    }
    //销毁解析器
    public void Dispose()
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.Dispose();
        luaEnv = null;
    }
}

(二)通过ab包加载lua文件
(1)准备好lua文件的ab包
过程:
1.lua文件打成lua包,注意lua文件要改后缀.txt
在这里插入图片描述

2.通过assetbundle broswer打包(至少要用2019.3以上版本的unity才有这个)
在这里插入图片描述
选择好打包平台:我的是mac,打包压缩格式为lz4
打包结果:
在这里插入图片描述
(2)编写自定义ab包加载器,并让luaenv添加这个自定义加载器

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

/// <summary>
/// 保证解析器的唯一性,先继承单例模式,也提供些解析器的函数
/// </summary>
public class LuaMgr :BaseManager<LuaMgr>
{
    //定义解析器对象
    private LuaEnv luaEnv;
    //初始化解析器
    public void Init()
    {
        if (luaEnv != null)
            return;
        luaEnv = new LuaEnv();
        //添加自定义加载器
        luaEnv.AddLoader(MyCustomLoader);
        //添加自定义的ab包加载器
        luaEnv.AddLoader(MyCustomABLoader);

    }

    /// <summary>
    /// 普通加载器
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    /// <returns></returns>
    private byte[] MyCustomLoader(ref string filepath)
    {
        
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
       
        
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("MyCustomLoader加载器加载lua文件失败");
            return null;
        }

    }

    /// <summary>
    /// 用ab包的方式加载lua文件的加载器
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    /// <returns></returns>
    private byte[] MyCustomABLoader(ref string filepath)
    {
        Debug.Log("用ab包加载lua文件");
        //找到ab包的路径
        string path = Application.streamingAssetsPath + "/lua";
        //加载含有lua文件的ab包
        AssetBundle luaab = AssetBundle.LoadFromFile(path);
        //加载ab包里的lua文件,读成txt,这里注意,因为打包的时候后缀是.lua.txt所以文件名还要加上个.lua
        TextAsset txt = luaab.LoadAsset<TextAsset>(filepath + ".lua");
        return txt.bytes;
        
    }

   

测试代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;//要添加这个命名空间
using System.IO;

public class LuaEnvTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

        LuaMgr.GetInstance().Init();
        LuaMgr.GetInstance().DoString("require('Main')");
    }

   
}

测试结果:
在这里插入图片描述
这里是因为执行require时,会依次遍历所有自定义加载器,如果都没有,就去默认路径Resources文件夹下找。
ps:只需要最后测试ab包的时候用ab包加载器,平时不需要,只需要用普通加载器
(3)luamgr的一点优化
1.不用每次都写require,增加一个方法

/// <summary>
    /// 拼接require不用写那么多次require
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    public void DoLuaFile(string filepath)
    {
        string str = string.Format("require('{0}')", filepath);
        DoString(str);
    }

2.得到lua的_G

/// <summary>
    /// 声明了一个属性,得到lua中得_G
    /// </summary>
    public LuaTable Global
    {
        get
        {
            return luaEnv.Global;
        }
    }

整个完整的luaenv管理器的代码:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;

/// <summary>
/// 保证解析器的唯一性,先继承单例模式,也提供些解析器的函数
/// </summary>
public class LuaMgr :BaseManager<LuaMgr>
{
    //定义解析器对象
    private LuaEnv luaEnv;

    /// <summary>
    /// 声明了一个属性,得到lua中得_G
    /// </summary>
    public LuaTable Global
    {
        get
        {
            return luaEnv.Global;
        }
    }

    //初始化解析器
    public void Init()
    {
        if (luaEnv != null)
            return;
        luaEnv = new LuaEnv();
        //添加自定义加载器
        luaEnv.AddLoader(MyCustomLoader);
        //添加自定义的ab包加载器
        luaEnv.AddLoader(MyCustomABLoader);

    }

    /// <summary>
    /// 普通加载器
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    /// <returns></returns>
    private byte[] MyCustomLoader(ref string filepath)
    {
        
        string path = Application.dataPath + "/Lua/" + filepath + ".lua";
       
        
        if (File.Exists(path))
        {
            return File.ReadAllBytes(path);
        }
        else
        {
            Debug.Log("MyCustomLoader加载器加载lua文件失败");
            return null;
        }

    }

    /// <summary>
    /// 用ab包的方式加载lua文件的加载器
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    /// <returns></returns>
    private byte[] MyCustomABLoader(ref string filepath)
    {
        Debug.Log("用ab包加载lua文件");
        //找到ab包的路径
        string path = Application.streamingAssetsPath + "/lua";
        //加载含有lua文件的ab包
        AssetBundle luaab = AssetBundle.LoadFromFile(path);
        //加载ab包里的lua文件,读成txt,这里注意,因为打包的时候后缀是.lua.txt所以文件名还要加上个.lua
        TextAsset txt = luaab.LoadAsset<TextAsset>(filepath + ".lua");
        return txt.bytes;
        
    }

    //执行lua文件
    public void DoString(string str)
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.DoString(str);
    }

    /// <summary>
    /// 拼接require不用写那么多次require
    /// </summary>
    /// <param name="filepath">lua文件名</param>
    public void DoLuaFile(string filepath)
    {
        string str = string.Format("require('{0}')", filepath);
        DoString(str);
    }

    //清空垃圾
    public void Tick()
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.Tick();
    }
    //销毁解析器
    public void Dispose()
    {
        if (luaEnv == null)
        {
            Debug.Log("lua解析器未初始化");
            return;
        }
        luaEnv.Dispose();
        luaEnv = null;
    }
}

介绍当下多数在java下执行lua脚本的程序都是用了luajava。然而luajava存在一些严重的问题,它会将byte数组和string等同对待,而且它的反射执行效率比较低。为了弥补这些问题,我参考luajava,重写了它的java和jni代码,并以mLua为名重新发布。特点描述和luajava类似的,mLua也有内置的全局lua函数;java对象和lua对象可以通过jni层代码进行交换。但是mLua禁止lua直接操作java对象,如果想在lua中使用java对象,必须使用内置的全局函数实现。mLua区分byte数组和string。在mLua中,java的byte数组对lua端而言,只是一个普通的userdata。在跨语言数据交换的时候,string是被复制的,因此当一个string从lua传递到java后再于lua中修改它,它在java端的对应版本并不会随着改变。将lua端的number传递给java后,会被优先解释为byte类型,否则将依照byte - short - int - long - float - double链条来尝试解释。mLua不对外暴露lua解析器实例,所有的操作都基于MLua实例完成。java端方法描述mLua的java端方法集中在MLua中:方法名称方法解释setBasedir(String)设置lua代码的最外层目录,所有lua代码都应该存放在这个目录或其子目录下pushGlobal(String, Object)设置全局lua的全局变量或函数,可以push普通的Object,或者JavaFunction。后者表示一个lua函数的java实现。只有在start方法执行前,设置的数据才会生效start(String)启动lua解析器,传递的参数表示lua代码的入口文件stop()停止lua解析器并释放资源除此之外,JavaFunction也是使用者可能需要用到的接口。它表示一个lua函数的java实现。其回调方法execute(Object[])方法会传入从lua端输入的数据,并输出一个结果传回lua端。如果方法本身不需要返回数据,则返回null即可。lua函数描述在mLua下,lua原来的require、print函数已经被改写。requirerequire必须使用设置在java端的basedir为根目录的相对路径引用其他lua脚本:require "dir1/dir2/script1" require "script2"print支持输出一个或多个对象,但是不能将string与java对象作拼接:-- 正确的做法 -- print("hello mLua") print("context: ", getContext()) print("string " .. 111) -- 错误的做法 -- print("context: " .. getContext())通过逗号分隔的对象会在java端以tab号分隔显示操作java对象mLua也采用反射来操作java对象,不过mobTools的ReflectHelper具备缓存功能,理论上会比luajava每次直接反射更快。mLua提供了如下的内置函数函数名称函数解释import(className)向ReflectHelper类缓存中导入一个类,此函数将返回一个string,用于后续代码从缓存中重新获取导入的类实例import(name, className)向ReflectHelper类缓存中导入一个类,并将此缓存的key设置为指定名称new(className, ...)构造一个java实例,参数className是import函数的返回值,后续参数为java构造方法的输入参数invokeStatic(className, methodName, ...)调用一个java的静态方法invoke(receiver, methodName, ...)调用一个Java的实例方法getStatic(className, fieldName)获取一个java的静态字段setStatic(className, fieldName, value)设置一个java的静态字段get(receiver, fieldName)获取一个java的实例字段set(receiver, fieldName, value)设置一个java的实例字段createProxy(proxyTable, ...)构造一个java接口代理。参数proxyTable是一个lua的table,其中的key必须与java接口类的方法名称相同,key对应的value是一个lua的function,function的参数列表和返回值也必须与java接口相同。proxyTable后的参数是被实现的接口列表名称,皆为string,由import函数返回。此函数将返回一个java接口代理实例,可将此实例传回java端并进行操作,当实例中的接口函数被调用时,mLua会调用proxyTable中的对应funtion代码完成操作例子java端代码public class MainActivity extends Activity {     private MLua lua;     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         // 构造一个解析器实例         lua = new MLua();         // 设置lua代码存放位置         lua.setBasedir("/sdcard/mLua/LuaTest");         // push全局对象         lua.pushGlobal("getContext", new JavaFunction() {             public Object execute(Object[] args) throws Throwable {                 return getApplication();             }         });         try {             // 启动解析器,设置main.lua为入口代码             lua.start("main");         } catch (Throwable e) {             e.printStackTrace();         }     }     protected void onDestroy() {         // 关闭解析器         lua.stop();         super.onDestroy();     } }lua端代码-- 导入ReflectHelper.ReflectRunnable类,并命名为ReflectRunnable -- import("ReflectRunnable", "com.mob.tools.utils.ReflectHelper$ReflectRunnable") local function main()   -- 演示print和invoke --   print("hello world from mLua")   local context = getContext()   print("current context: ", context)   local packageName = invoke(context, "getPackageName")   print("packageName: ", packageName)   -- 演示java接口代理 --   local luaCode = {     run = function(arg)       print("luaCode.run(), input: ", arg)       return "yoyoyo"     end   }   local proxy = createProxy(luaCode, "ReflectRunnable")   local res = invoke(proxy, "run", packageName)   print("luaCode.run(), output: ", res)   -- 演示数组复制 --   local bArray = new("[B", 16)   for i = 0, 15 do     set(bArray, "[" .. i .. "]", i   1)   end   local bArray2 = new("[B", get(bArray, "length"))   invokeStatic("System", "arraycopy", bArray, 0, bArray2, 0, 16)   for i = 0, 15 do     print("bArray2[" .. i .. "]: ", get(bArray2, "[" .. i .. "]"))   end end main()扩展mLua默认只能从文件系统中加载lua代码,但是如果对MLua的setBasedir方法进行重写,以其他的方式实现SourceLoader,则可以加载任意方式的lua代码,括assets中的,和加密的。 标签:mLua
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值