原文地址:http://www.cocoachina.com/bbs/read.php?tid=213061
4. update包(lua)
update包是整個項目的入口包,quick會首先載入這個包,甚至在 framework 之前。4.1 爲update包所做的項目修改我修改了quick項目文件 AppDelegate.cpp 中的 applicationDidFinishLaunching 方法,使其變成這樣:
複製代碼
-
-
bool AppDelegate::applicationDidFinishLaunching()
-
{
-
// initialize director
-
CCDirector *pDirector = CCDirector::sharedDirector();
-
pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());
-
pDirector->setProjection(kCCDirectorProjection2D);
-
-
// set FPS. the default value is 1.0/60 if you don't call this
-
pDirector->setAnimationInterval(1.0 / 60);
-
-
// register lua engine
-
CCLuaEngine *pEngine = CCLuaEngine::defaultEngine();
-
CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
-
-
CCLuaStack *pStack = pEngine->getLuaStack();
-
-
string gtrackback = "\
-
function __G__TRACKBACK__(errorMessage) \
-
print(\"----------------------------------------\") \
-
print(\"LUA ERROR: \" .. tostring(errorMessage) .. \"\\n\") \
-
print(debug.traceback(\"\", 2)) \
-
print(\"----------------------------------------\") \
-
end";
-
pEngine->executeString(gtrackback.c_str());
-
-
// load update framework
-
pStack->loadChunksFromZIP("res/lib/update.zip");
-
-
string start_path = "require(\"update.UpdateApp\").new(\"update\"):run(true)";
-
CCLOG("------------------------------------------------");
-
CCLOG("EXECUTE LUA STRING: %s", start_path.c_str());
-
CCLOG("------------------------------------------------");
-
pEngine->executeString(start_path.c_str());
-
-
return true;
-
}
|
原來位於 main.lua 中的 __G_TRACKBACK__ 函數(用於輸出lua報錯信息)直接包含在C++代碼中了。那麼現在 main.lua 就不再需要了。同樣的,第一個載入的模塊變成了 res/lib/update.zip,這個zip也可以放在quick能找到的其它路徑中,使用這個路徑只是我的個人習慣。最後,LuaStack直接執行了下面這句代碼啓動了 update.UpdateApp 模塊:
複製代碼
-
require("update.UpdateApp").new("update"):run(true);
|
4.2 update包中的模塊update包有三個子模塊,每個模塊是一個lua文件,分別爲:- update.UpdateApp 檢測更新,決定啓動哪個模塊。
- update.updater 負責真正的更新工作,與C++通信,下載、解壓、複製。
- update.updateScene 負責在更新過程中顯示界面,進度條等等。
對於不同的大小寫,是因爲在我的命名規則中,類用大寫開頭,對象是小寫開頭。 update.UpdateApp 是一個類,其它兩個是對象(table)。下面的 4.3、4.4、4.5 將分別對這3個模塊進行詳細介紹。4.3 update.UpdateApp下面是入口模塊 UpdateApp 的內容:
複製代碼
-
--- The entry of Game
-
-- @author zrong(zengrong.net)
-
-- Creation 2014-07-03
-
-
local UpdateApp = {}
-
-
UpdateApp.__cname = "UpdateApp"
-
UpdateApp.__index = UpdateApp
-
UpdateApp.__ctype = 2
-
-
local sharedDirector = CCDirector:sharedDirector()
-
local sharedFileUtils = CCFileUtils:sharedFileUtils()
-
local updater = require("update.updater")
-
-
function UpdateApp.new(...)
-
local instance = setmetatable({}, UpdateApp)
-
instance.class = UpdateApp
-
instance:ctor(...)
-
return instance
-
end
-
-
function UpdateApp:ctor(appName, packageRoot)
-
self.name = appName
-
self.packageRoot = packageRoot or appName
-
-
print(string.format("UpdateApp.ctor, appName:%s, packageRoot:%s", appName, packageRoot))
-
-
-- set global app
-
_G[self.name] = self
-
end
-
-
function UpdateApp:run(checkNewUpdatePackage)
-
--print("I am new update package")
-
local newUpdatePackage = updater.hasNewUpdatePackage()
-
print(string.format("UpdateApp.run(%s), newUpdatePackage:%s",
-
checkNewUpdatePackage, newUpdatePackage))
-
if checkNewUpdatePackage and newUpdatePackage then
-
self:updateSelf(newUpdatePackage)
-
elseif updater.checkUpdate() then
-
self:runUpdateScene(function()
-
_G["finalRes"] = updater.getResCopy()
-
self:runRootScene()
-
end)
-
else
-
_G["finalRes"] = updater.getResCopy()
-
self:runRootScene()
-
end
-
end
-
-
-- Remove update package, load new update package and run it.
-
function UpdateApp:updateSelf(newUpdatePackage)
-
print("UpdateApp.updateSelf ", newUpdatePackage)
-
local updatePackage = {
-
"update.UpdateApp",
-
"update.updater",
-
"update.updateScene",
-
}
-
self:_printPackages("--before clean")
-
for __,v in ipairs(updatePackage) do
-
package.preload[v] = nil
-
package.loaded[v] = nil
-
end
-
self:_printPackages("--after clean")
-
_G["update"] = nil
-
CCLuaLoadChunksFromZIP(newUpdatePackage)
-
self:_printPackages("--after CCLuaLoadChunksForZIP")
-
require("update.UpdateApp").new("update"):run(false)
-
self:_printPackages("--after require and run")
-
end
-
-
-- Show a scene for update.
-
function UpdateApp:runUpdateScene(handler)
-
self:enterScene(require("update.updateScene").addListener(handler))
-
end
-
-
-- Load all of packages(except update package, it is not in finalRes.lib)
-
-- and run root app.
-
function UpdateApp:runRootScene()
-
for __, v in pairs(finalRes.lib) do
-
print("runRootScene:CCLuaLoadChunksFromZip",__, v)
-
CCLuaLoadChunksFromZIP(v)
-
end
-
-
require("root.RootScene").new("root"):run()
-
end
-
-
function UpdateApp:_printPackages(label)
-
label = label or ""
-
print("\npring packages "..label.."------------------")
-
for __k, __v in pairs(package.preload) do
-
print("package.preload:", __k, __v)
-
end
-
for __k, __v in pairs(package.loaded) do
-
print("package.loaded:", __k, __v)
-
end
-
print("print packages "..label.."------------------\n")
-
end
-
-
-
function UpdateApp:exit()
-
sharedDirector:endToLua()
-
os.exit()
-
end
-
-
function UpdateApp:enterScene(__scene)
-
if sharedDirector:getRunningScene() then
-
sharedDirector:replaceScene(__scene)
-
else
-
sharedDirector:runWithScene(__scene)
-
end
-
end
-
-
return UpdateApp
-
|
我來說幾個重點。4.3.1 沒有framework
由於沒有加載 framework,class當然是不能用的。所有quick framework 提供的方法都不能使用。我借用class中的一些代碼來實現 UpdateApp 的繼承。其實我覺得這個UpdateApp也可以不必寫成class的。4.3.2 入口函數 update.UpdateApp:run(checkNewUpdatePackage)
run 是入口函數,同時接受一個參數,這個參數用於判斷是否要檢測本地有新的 update.zip 模塊。是的,run 就是那個在 AppDelegate.cpp 中第一個調用的lua函數。這個函數接受一個參數 checkNewUpdatePackage ,在C++調用 run 的時候,傳遞的值是 true 。如果這個值爲真,則會檢測本地是否擁有新的更新模塊,這個檢測通過 update.updater.hasNewUpdatePackage() 方法進行,後面會說到這個方法。本地有更新的 update 模塊,則直接調用 updateSelf 來更新 update 模塊自身;若無則檢測是否有項目更新,下載更新的資源,解析它們,處理它們,然後啓動主項目。這些工作通過 update.updater.checkUpdate()
完成,後面會說到這個方法。若沒有任何內容需要更新,則直接調用 runRootScene 來顯示主場景了。這後面的內容就交給住場景去做了,update 模塊退出歷史舞臺。從上面這個流程可以看出。在更新完成之前,主要的項目代碼和資源沒有進行任何載入。這也就大致達到了我們 更新一切 的需求。因爲所有的東西都沒有載入,也就不存在更新。只需要保證我載入的內容是最新的就行了。因此,只要保證 update 模塊能更新,就達到我們最開始的目標了。這個流程還可以保證,如果沒有更新,甚至根本就不需要載入 update 模塊的場景界面,直接跳轉到遊戲的主場景即可。有句代碼在 run 函數中至關重要:
複製代碼
-
_G["finalRes"] = updater.getResCopy()
|
finalRes 這個全局變量保存了本地所有的
原始/更新 資源索引。它是一個嵌套的tabel,保存的是所有資源的名稱以及它們對應的
絕對/相對 路徑的對應關係。後面會詳述。
4.3.3 更新自身 update.UpdateApp:updateSelf(newUpdatePackage)
這是本套機制中最重要的一環。理解了它,你就知道更新一切其實沒什麼祕密。Lua本來就提供了這樣一套機制。
由於在 C++ 中已經將 update 模塊載入了內存,那麼要更新自身首先要做的是清除 Lua 的載入標記。
Lua在兩個全局變量中做了標記:
- package.preload 執行 CCLuaLoadChunksFromZIP 之後會將模塊緩存在這裏作爲 require 的加載器;
- package.loaded 執行 require 的時候會先查詢 package.loaded,若沒有則會查詢 package.preload 得到加載器,利用加載器加載模塊,再將加載的模塊緩存到 package.loaded 中。
詳細的機制可以閱讀
Lua程序設計(第2版) 15.1 require 函數。
那麼,要更新自己,只需要把 package.preload 和 package.loaded 清除,然後再用新的 模塊填充 package.preload 即可。下面就是核心代碼了:
複製代碼
-
local updatePackage = {
-
"update.UpdateApp",
-
"update.updater",
-
"update.updateScene",
-
}
-
for __,v in ipairs(updatePackage) do
-
package.preload[v] = nil
-
package.loaded[v] = nil
-
end
-
_G["update"] = nil
-
CCLuaLoadChunksFromZIP(newUpdatePackage)
-
require("update.UpdateApp").new("update"):run(false)
|
如果不相信這麼簡單,可以用上面完整的 UpdateApp 模塊中提供的 UpdateApp:_printPackages(label) 方法來檢測。4.3.4 顯示更新界面 update.UpdateApp:runUpdateScene(handler)
update.updater.checkUpdate() 的返回是異步的,下載和解壓都需要時間,在這段時間裏面,我們需要一個界面。runUpdateScene 方法的作用就是顯示這個界面。並在更新成功之後調用handler處理函數。
4.3.5 顯示主場景 update.UpdateApp:runRootScene()
到了這裏,update 包就沒有作用了。但由於我們先前沒有載入除 update 包外的任何包,這裏必須先載入它們。我上面提到過,finalRes 這個全局變量是一個索引表,它的 lib 對象就是一個包含所有待載入的包(類似於 frameworks_precompiled.zip 這種)的列表。我們通過循環將它們載入內存。對於 root.RootScene 這個模塊來說,就是標準的quick模塊了,它可以使用quick中的任何特性。
複製代碼
-
for __, v in pairs(finalRes.lib) do
-
print("runRootScene:CCLuaLoadChunksFromZip",__, v)
-
CCLuaLoadChunksFromZIP(v)
-
end
-
-
require("root.RootScene").new("root"):run()
|
4.3.5 怎麼使用這個模塊你如果要直接拿來就用,這個模塊基本上不需要修改。因爲本來它就沒什麼特別的功能。當然,你可以看完下面兩個模塊再決定。