xLua——LuaManager

作者:追风剑情 发布于:2021-8-25 14:44 分类:Lua

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using UnityEngine;
using XLua;
/// <summary>
/// Lua管理器
/// </summary>
public class LuaManager : MonoBehaviour
{
    public static LuaManager Instance { get; private set; }
    private const float GC_INTERVAL = 1;//1 second 
    private float _lastGCTime = 0;
    private LuaEnv _luaEnv = new LuaEnv();
    private List<WeakReference> luaBehList = new List<WeakReference>();
    private Canvas _canvas;
    //加载列表
    private List<UILoadingItem> _uiLoadingList = new List<UILoadingItem>();
    private Dictionary<string, string> _luaChunkDic = new Dictionary<string, string>();

    /// <summary>
    /// 执行Lua代码块
    /// </summary>
    /// <param name="chunk">Lua代码块</param>
    /// <param name="chunkName">发生error时的debug显示信息中使用,通常等于脚本文件名(否则IDE调试不了)</param>
    /// <param name="env">这个代码块的环境变量</param>
    /// <returns>Lua代码返回的一个或多个值</returns>
    public object[] DoString(string chunk, string chunkName = "chunk", LuaTable env = null)
    {
        return _luaEnv.DoString(chunk, chunkName, env);
    }

    //加载一个代码块,但不执行,只返回类型可以指定为一个delegate或者一个LuaFunction
    public LuaFunction LoadString(string chunk, string chunkName = "chunk", LuaTable env = null)
    {
        return LoadString<LuaFunction>(chunk, chunkName, env);
    }

    //加载一个代码块,但不执行,只返回类型可以指定为一个delegate或者一个LuaFunction
    public T LoadString<T>(string chunk, string chunkName = "chunk", LuaTable env = null)
    {
        return _luaEnv.LoadString<T>(chunk, chunkName, env);
    }

    public LuaTable NewLuaTable()
    {
        if (_luaEnv == null)
            return null;
        return _luaEnv.NewTable();
    }

    //为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
    public LuaTable NewEvnLuaTable()
    {
        if (_luaEnv == null)
            return null;
        LuaTable scriptEnv = _luaEnv.NewTable();
        LuaTable meta = _luaEnv.NewTable();
        meta.Set("__index", _luaEnv.Global);
        scriptEnv.SetMetaTable(meta);
        meta.Dispose();
        return scriptEnv;
    }

    //通过 lua chunk 创建 LuaTable
    public LuaTable CreateLuaTable(string chunk, string chunkName = "chunk")
    {
        LuaTable scriptEnv = NewEvnLuaTable();
        object[] returns = _luaEnv.DoString(chunk, chunkName, scriptEnv);
        //判断Lua文件是否返回了一个作为类的table对象
        if (returns != null || returns.Length > 0)
        {
            LuaTable classTable = returns[0] as LuaTable;
            string className;
            classTable.Get<string, string>("__name", out className);
            Debug.LogFormat("CS: return lua class {0}", className);
            scriptEnv = classTable;
        }
        else
        {
            //Lua文件没按照OOP方式编程,这里认为采用了函数式编程
            Debug.Log("Lua UI No value was returned");
        }
        return scriptEnv;
    }

    //通过Lua文件创建LuaBehaviour对象
    public LuaBehaviour CreateLuaBehaviour(string chunk, string chunkName = "chunk")
    {
        LuaTable scriptEnv = CreateLuaTable(chunk, chunkName);
        GameObject go = new GameObject(chunkName);
        var luaBehaviour = go.AddComponent<LuaBehaviour>();
        luaBehaviour.AttachLuaScript(scriptEnv);
        luaBehList.Add(new WeakReference(luaBehaviour));

        return luaBehaviour;
    }

    //添加LuaBehaviour组件
    public void AddLuaBehaviour(GameObject go, string chunk=null, LuaTable parameter = null)
    {
        var luaBehaviour = go.GetComponent<LuaBehaviour>();
        if (luaBehaviour == null)
            luaBehaviour = go.AddComponent<LuaBehaviour>();
        luaBehList.Add(new WeakReference(luaBehaviour));

        if (string.IsNullOrEmpty(chunk))
            return;
        LuaTable scriptEnv = CreateLuaTable(chunk);
        luaBehaviour.AttachLuaScript(scriptEnv, parameter);
    }

    public void AddLuaBehaviour(GameObject go, LuaTable scriptEnv, LuaTable parameter = null)
    {
        var luaBehaviour = go.GetComponent<LuaBehaviour>();
        if (luaBehaviour == null)
            luaBehaviour = go.AddComponent<LuaBehaviour>();
        luaBehList.Add(new WeakReference(luaBehaviour));
        luaBehaviour.AttachLuaScript(scriptEnv, parameter);
    }

    //UI的默认Canvas
    public Canvas DefaultCanvas
    {
        get
        {
            if (_canvas == null)
            {
                GameObject go = GameObject.Find("Canvas");
                _canvas = go.GetComponent<Canvas>();
            }
            return _canvas;
        }
    }

    //加载 lua bundle (app.lua)
    public void LoadLuaBundle(string url, Action callback=null, Action<string> errorback=null)
    {
        StartCoroutine(CoroutineLoadLuaBundle(url, callback, errorback));
    }

    private IEnumerator CoroutineLoadLuaBundle(string url, Action callback = null, Action<string> errorback = null)
    {
        yield return null;
        WWW www = new WWW(url);
        while (!www.isDone)
            yield return www;
        if (!string.IsNullOrEmpty(www.error))
        {
            Debug.LogErrorFormat("{0}\n{1}", www.error, url);
            errorback?.Invoke(url);
            yield break;
        }
        AssetBundle ab = www.assetBundle;
        if (ab == null)
        {
            Debug.LogErrorFormat("Error: assetBundle=null \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }
        //将lua chunk缓存到字典
        string[] names = ab.GetAllAssetNames();
        foreach (var name in names)
        {
            if (_luaChunkDic.ContainsKey(name))
                continue;
            TextAsset textAsset = ab.LoadAsset<TextAsset>(name);
            _luaChunkDic.Add(name, textAsset.text);
        }
        callback?.Invoke();
    }

    //执行Lua Chunk
    public object[] DoLuaChunk(string chunkName)
    {
        chunkName = chunkName.ToLower();
        if (!_luaChunkDic.ContainsKey(chunkName))
        {
            Debug.LogErrorFormat("No Lua Chunk: {0}", chunkName);
            return null;
        }
        string chunk = _luaChunkDic[chunkName];
        return DoString(chunk, chunkName, NewEvnLuaTable());
    }

    //加载Resources下的UI
    public GameObject LoadUIFromResources(string uiName, string chunk)
    {
        //加载
        GameObject ui = Resources.Load<GameObject>(uiName);
        if (ui == null)
        {
            Debug.LogErrorFormat("Resources unable to load {0}.prefab", uiName);
            return null;
        }

        //克隆UI
        GameObject ui_clone = GameObject.Instantiate<GameObject>(ui);
        ui_clone.transform.SetParent(DefaultCanvas.transform);
        ui_clone.transform.localPosition = Vector3.zero;
        ui_clone.transform.localRotation = Quaternion.identity;
        ui_clone.transform.localScale = Vector3.one;
        ui_clone.transform.SetAsLastSibling();

        //检查是否有LuaBehaviour组件
        LuaBehaviour luaBehaviour = ui_clone.GetComponent<LuaBehaviour>();
        if (luaBehaviour == null)
            luaBehaviour = ui_clone.AddComponent<LuaBehaviour>();

        //关联Lua脚本
        LuaTable scriptEnv = CreateLuaTable(chunk, uiName);
        luaBehaviour.AttachLuaScript(scriptEnv);
        luaBehList.Add(new WeakReference(luaBehaviour));

        return ui_clone;
    }

    //从URL加载Lua文件
    public void LoadLuaFromURL(string url, Action<LuaTable> callback, Action<string> errorback = null)
    {
        StartCoroutine(CoroutineLoadLuaFromURL(url, callback, errorback));
    }

    private IEnumerator CoroutineLoadLuaFromURL(string url, Action<LuaTable> callback, Action<string> errorback = null)
    {
        yield return null;
        WWW www = new WWW(url);
        while (!www.isDone)
            yield return www;
        if (!string.IsNullOrEmpty(www.error))
        {
            Debug.LogErrorFormat("{0}\n{1}", www.error, url);
            errorback?.Invoke(url);
            yield break;
        }

        AssetBundle ab = www.assetBundle;
        if (ab == null)
        {
            Debug.LogErrorFormat("Error: assetBundle=null \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }

        object[] objs = ab.LoadAllAssets();
        if (objs == null || objs.Length == 0)
        {
            Debug.LogErrorFormat("Error: lua assetBundle is empty. \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }

        TextAsset chunk = objs[0] as TextAsset;
        object[] tables = DoString(chunk.text);
        if (tables == null || tables.Length <= 0)
        {
            Debug.LogErrorFormat("Error: the lua chunk is not returned. \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }
        LuaTable table = tables[0] as LuaTable;
        callback?.Invoke(table);
    }

    //从URL加载UI
    public void LoadUIFromURL(string url, Action<GameObject> callback, Action<string> errorback=null)
    {
        StartCoroutine(CoroutineLoadUIFromURL(url, callback, errorback));
    }

    private IEnumerator CoroutineLoadUIFromURL(string url, Action<GameObject> callback, Action<string> errorback=null)
    {
        yield return null;
        WWW www = new WWW(url);
        while (!www.isDone)
            yield return www;
        if (!string.IsNullOrEmpty(www.error))
        {
            Debug.LogErrorFormat("{0}\n{1}", www.error, url);
            errorback?.Invoke(url);
            yield break;
        }

        AssetBundle ab = www.assetBundle;
        if (ab == null)
        {
            Debug.LogErrorFormat("Error: assetBundle=null \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }

        object[] objs = ab.LoadAllAssets();
        if (objs == null || objs.Length == 0)
        {
            Debug.LogErrorFormat("Error: UI assetBundle is empty. \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }

        GameObject ui = objs[0] as GameObject;
        if (ui == null)
        {
            Debug.LogErrorFormat("type is not GameObject. \n{0}", url);
            errorback?.Invoke(url);
            yield break;
        }
        GameObject clone = GameObject.Instantiate<GameObject>(ui);
        callback?.Invoke(clone);
    }

    //异步 加载UI & 关联Lua脚本
    public void LoadUIAsync(string uiUrl, string chunkUrl, LuaTable parameter = null, Action < GameObject> callback=null, Action<string> errorback=null)
    {
        UILoadingItem item = new UILoadingItem();
        item.parameter = parameter;
        item.Parent = DefaultCanvas.transform;
        item.OnCreateLuaBehaviour = (luaBehaviour) => {
            luaBehList.Add(new WeakReference(luaBehaviour));
        };
        item.OnCompleted = callback;
        item.OnError = errorback;
        _uiLoadingList.Add(item);

        //加载lua
        string chunkName = Path.GetFileNameWithoutExtension(chunkUrl);
        bool hasChunk = _luaChunkDic.ContainsKey(chunkName);
        if (hasChunk)
            item.scriptEnv = CreateLuaTable(_luaChunkDic[chunkName]);
        else
            LoadLuaFromURL(chunkUrl, item.OnLuaCompleted, item.OnErrorCallback);
        //加载UI
        LoadUIFromURL(uiUrl, item.OnUICompleted, item.OnErrorCallback);
    }

    //Lua 自定义加载器
    private byte[] OnCustomLoader(ref string filepath)
    {
        string chunkName = filepath.ToLower();
        Debug.LogFormat("CustomLoader({0})", chunkName);
        string chunk = string.Empty;
        if (_luaChunkDic.ContainsKey(chunkName))
            chunk = _luaChunkDic[chunkName];
        byte[] bytes = Encoding.UTF8.GetBytes(chunk);
        return bytes;
    }

    private void Awake()
    {
        Instance = this;
        DontDestroyOnLoad(gameObject);
        _luaEnv.AddLoader(OnCustomLoader);
    }

    private void Start()
    {
        StartCoroutine(CheckLuaBehList());
    }

    private void Update()
    {
        if (_luaEnv != null)
        {
            //定时GC
            if (Time.time - _lastGCTime > GC_INTERVAL)
            {
                _luaEnv.Tick();
                _lastGCTime = Time.time;
            }
        }

        //移除在加载过程中出错或已完成的UI
        for (int i=0; i<_uiLoadingList.Count; i++)
        {
            var item = _uiLoadingList[i];
            if (item.IsDone || item.IsError)
            {
                item.Dispose();
                _uiLoadingList.RemoveAt(i);
                break;
            }
        }
    }

    private IEnumerator CheckLuaBehList()
    {
        while(true)
        {
            //移除无效的LuaBehaviour对象
            for (int i = 0; i < luaBehList.Count; i++)
            {
                if (!luaBehList[i].IsAlive)
                {
                    luaBehList.RemoveAt(i);
                    yield return null;
                }
                //避免遍历耗时太久
                if (i % 100 == 0)
                    yield return null;
            }
            yield return new WaitForSeconds(1f);
        }
    }

    private void OnDestroy()
    {
        StopAllCoroutines();
        DisposeAllLuaReference();

        //在调用Dispose()之前必须释放所有scriptEnv.Get()产生的引用(即,引用设为null)
        //否则,会抛出下面错误
        //InvalidOperationException: try to dispose a LuaEnv with C# callback!
        _luaEnv.Dispose();
        _luaEnv = null;
        Instance = null;
    }

    //切换场景时需要调用
    //释放所有Lua引用以及触发Lua层的OnDestroy()回调
    public void DisposeAllLuaReference()
    {
        for (int i = 0; i < luaBehList.Count; i++)
        {
            if (luaBehList[i].IsAlive)
            {
                (luaBehList[i].Target as LuaBehaviour).DisposeLuaReference();
            }
        }
        luaBehList.Clear();
    }
}

//UI Loading List Item
public sealed class UILoadingItem
{
    public GameObject UI { get; private set; }
    public LuaTable scriptEnv { get; set; }
    public LuaBehaviour luaBehaviour { get; private set; }
    public Transform Parent { get; set; }
    public bool IsError { get; private set; }
    public Action<LuaBehaviour> OnCreateLuaBehaviour;
    public Action<GameObject> OnCompleted;
    public Action<string> OnError;

    public LuaTable parameter = null;

    //是否加载完成
    public bool IsDone
    {
        get
        {
            return UI != null && scriptEnv != null;
        }
    }

    private void AttachParent()
    {
        if (Parent == null)
            return;
        Transform trans = UI.transform;
        trans.SetParent(Parent);
        trans.localPosition = Vector3.zero;
        trans.localRotation = Quaternion.identity;
        trans.localScale = Vector3.one;
    }

    public void OnUICompleted(GameObject ui)
    {
        UI = ui;
        //检查是否有LuaBehaviour组件
        luaBehaviour = UI.GetComponent<LuaBehaviour>();
        if (luaBehaviour == null)
            luaBehaviour = UI.AddComponent<LuaBehaviour>();
        if (scriptEnv != null)
            luaBehaviour.AttachLuaScript(scriptEnv, parameter);
        AttachParent();
        OnCreateLuaBehaviour?.Invoke(luaBehaviour);

        if (scriptEnv != null)
            OnCompleted?.Invoke(ui);
    }

    public void OnLuaCompleted(LuaTable scriptEnv)
    {
        this.scriptEnv = scriptEnv;
        if (luaBehaviour != null)
            luaBehaviour.AttachLuaScript(scriptEnv, parameter);

        if (luaBehaviour != null)
            OnCompleted?.Invoke(UI);
    }

    public void OnErrorCallback(string url)
    {
        Debug.LogError(url);
        IsError = true;
        OnError?.Invoke(url);
    }

    public void Dispose()
    {
        UI = null;
        scriptEnv = null;
        luaBehaviour = null;
        Parent = null;
        OnCreateLuaBehaviour = null;
        OnCompleted = null;
        OnError = null;
        parameter = null;
    }
}

调用方式

//将所有lua文件打包到app.lua中
string lua_url = string.Format("{0}/lua/app.lua", Application.streamingAssetsPath);
LuaManager.Instance.LoadLuaBundle(lua_url, () =>
{
	Debug.Log("app.lua load completed.");
	//执行Lua侧入口程序
	LuaManager.Instance.DoLuaChunk("Main");
	//加载一个测试UI
	LuaManager.Instance.LoadUIAsync(ui_url, "luatestscript", parameter, (ui)=> {
		Debug.LogFormat("Load UI Completed! {0}", ui.name);
	},
	(error)=> {
		Debug.LogError(error);
	});
});

标签: xLua

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号