魔獸的UI插件結(jié)構(gòu)
1.使用lua+XML作為配置
分析:XML雖然人機(jī)交互很好,但其實(shí)沒有幾個(gè)UI是真正用純XML寫的,大多還是用編輯器比較方便。速度很慢,但尚不清楚魔獸代碼里是否進(jìn)行優(yōu)化
2. Interface\Addons為插件目錄,文件夾可以堆疊
3. 每個(gè)插件組,需要一個(gè)toc文件來做文件讀取列表描述,類似于:
# Libraries
embeds.xml
AceGUIWidget-DragLink.lua
Core.lua
# Localization
Locale-enUS.lua
Locale-zhCN.lua
Locale-zhTW.lua
AutoBarDB.lua
AutoBarOptions.lua
AutoBarSearch.lua
…
4.一個(gè)插件組里可以擁有多個(gè)lua文件,都共享一個(gè)獨(dú)立的全局空間
5.WTF\Account\賬號名\服務(wù)器名\角色名\AddOns.txt文件描述哪些插件需要讀取
根據(jù)分析:每次魔獸啟動(dòng)時(shí),都會掃描一次插件目錄,并更新這個(gè)列表,但是原有的插件讀取狀態(tài)仍然保留,類似于:
Combuctor: enabled
Combuctor_Config: enabled
Parrot: disabled
BattleInfo: disabled
BigWigs: disabled
BigWigs_Extras: disabled
BigWigs_BlackTemple: disabled
leafZone: enabled
InFlight: disabled
…
6. WTF\下的很多SavedVariables目錄都是用于保存插件狀態(tài)的,沒有對lua的擴(kuò)展庫進(jìn)行研究(ACE2/3等等),但是這是一種很好的保存插件數(shù)據(jù)的方法
OZ_Config = {
{
["bottomCol"] = {
["a"] = 1,
["r"] = 0,
["g"] = 0,
["b"] = 0.6,
},
["maxBars"] = 40,
["barHeight"] = 16,
["titleHeight"] = 20,
["sort2"] = 0,
["fadeAlpha"] = 0.3999999761581421,
["textSize"] = 10,
["colour"] = 2,
["minBars"] = 1,
["heading"] = {
3, -- [1]
0, -- [2]
0, -- [3]
…
7.暫時(shí)沒有找到魔獸UI的核心API是否用純腳本提供的證據(jù),但是可以推斷,按照暴雪的實(shí)力,應(yīng)該是全lua api寫成。
構(gòu)建安全的lua沙箱
所謂沙箱,就是每個(gè)插件擁有獨(dú)立的_G全局環(huán)境,即便用戶誤將print修改,其他的插件也不會受到影響. 同時(shí),考慮到沙箱的安全性和權(quán)限,需要對沙箱函數(shù)訪問進(jìn)行訂制.以下是本人摸索出的一種方案:
先看下我的UI環(huán)境及l(fā)ua嵌入架構(gòu):
1. C++層將必要的API注冊到lua層.但都是基于id的全局函數(shù)(考慮到效率及便捷),但是實(shí)際使用時(shí)再在lua層進(jìn)行OO封裝,這和WINDOWS API及MFC的原理類似
2. C++層只提供4種原生控件: Button,Label, EditBox,MultiLineEditBox。其他的控件都是由這些組成。
3. 可以將整個(gè)系統(tǒng)分為內(nèi)核模式和用戶模式。
內(nèi)核模式:可以使用完整的API訪問及權(quán)限。
用戶模式:被沙箱保護(hù),無法訪問一些危險(xiǎn)的API,例如io
這里,我們使用lua_setfenv進(jìn)行沙箱構(gòu)建,首先我們必須將創(chuàng)建每個(gè)沙箱對應(yīng)的table
// 放入沙箱名稱
lua_pushstring( mLua, "mysandbox" );
// 將一個(gè)table壓入棧
dotlua::table ts( mLua , false );
// 調(diào)用之前載入好的一個(gè)訂制沙箱環(huán)境的函數(shù)
gt.call<void>("SetupSandBox", ts );
// 將沙箱以mysandbox的key保存在注冊表中
lua_settable( mLua, LUA_REGISTRYINDEX );
之后使用lua_loadfile載入需要放進(jìn)沙箱的代碼
// 這里將本沙箱對應(yīng)的環(huán)境取出來
lua_pushstring( mLua, "mysandbox" );
lua_gettable( mLua, LUA_REGISTRYINDEX );
// 棧內(nèi)的情形為
// -1 沙箱table
// -2 chunk function
// 這里必須調(diào)用chunk函數(shù)
lua_setfenv( mLua, -2 );
調(diào)用pcall執(zhí)行代碼
這里的chunk函數(shù),來源于lua_loadfile或者lua_loadbuffer,這2個(gè)函數(shù)將代碼讀入,但并不會執(zhí)行,包括定義全局函數(shù)之類的操作。
沙箱訂制函數(shù)必須提前載入,這里發(fā)一個(gè)做參考
function SetupSandBox( e )
e._G = e
-- system lib
e.print = print
e.printf = printf
e.table = table
e.string = string
e.debug = debug
e.math = math
e.assert = assert
e.getmetatable = getmetatable
e.ipairs = ipairs
e.pairs = pairs
e.pcall = pcall
e.setmetatable = setmetatable
e.tostring = tostring
e.tonumber = tonumber
e.type = type
e.unpack = unpack
e.collectgarbage = collectgarbage
-- class
e.TREENODE = WIDGET_TREENODE
e.SERIALIZE = WIDGET_SERIALIZE
-- function
e.CreateWidget = CreateWidget
e._Inherit = _Inherit
end
只有被訂制的函數(shù),才能被調(diào)用
擴(kuò)展:
為了獲得完整的內(nèi)核模式開發(fā),但又擁有獨(dú)立的沙箱環(huán)境,可以使用setmetatable方式設(shè)置一個(gè)對全局table的引用,雖然速度慢了點(diǎn)。。
當(dāng)然,可以支持一套可載入dll訂制權(quán)限,并注冊更多的api給自己的腳本用