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);
});
});