Cocos文件管理

Cocos 中脚本文件是如何被载入和执行

  Cocos 是可以执行 lua 和 js 的,lua 和 js 都可以认为是解释性语言,不涉及编译和链接步骤,那么文件是如何在Cocos中被载入到内存中并且被执行呢 ?

  前提,我们知道不管是IOS 系统还是 Android 系统,Cocos 的执行流程都是需要把MainLoop 执行起来。如果这个流程不太清楚,请参看我上一个帖子 Cocos 代码执行流程

  以Cocos lua 为例,前面的代码部分已经解释了,在执行MainLoop 之前,AppDelegate::applicationDidFinishLaunching 函数中会先执行lua 虚拟机的初始化 和 设定 脚本执行的入口文件,我们继续跟踪代码执行,去发现Cocos 在文件操作方面是具有如何的特点。

1
engine->executeScriptFile("src/main.lua")

  对于main.lua 我们很熟悉,cocos-lua 中lua脚本执行入口文件。我们知道硬盘可以容纳很多的文件内容,但是这些文件内容在没有被计算机读取的时候就仅仅是存放而已,真正有效是在载入到内存以后被cpu处理的时候,话外题,先pass。我们继续回到main.lua ,同样,在入口的时候一定会先将文件载入到内存,那么文件的IO操作一定少不了。因为使用lua语言编写的,所以lua解释器需要介入和解释文本内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//猜想执行流程
if(isFileExistence){
//执行文本IO 根据文本类型读取内容
FILE *fp = fopen(fullPath.c_str(), mode);
fseek(fp,0,SEEK_END);
//获取长度
*size = ftell(fp);
fseek(fp,0,SEEK_SET);
//获取文本内容 放入内存
buffer = (unsigned char*)malloc(*size);
*size = fread(buffer,sizeof(unsigned char), *size,fp);
fclose(fp);
...
//判断如果文本是经过 xxtea 加密
//拿到加密的buffer 进行解密 返回原始内容
//拿到原始内容以后进行解释执行
}

  如果这里没其他需求,最简单和直接的思路应该是这样的,所以我们转向看下Cocos 的代码部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static const std::string BYTECODE_FILE_EXT    = ".luac";
static const std::string NOT_BYTECODE_FILE_EXT = ".lua";
int LuaStack::executeScriptFile(const char* filename)
{
std::string buf(filename);
// remove .lua or .luac
...
FileUtils *utils = FileUtils::getInstance();
// 1. check .lua suffix 2. check .luac suffix
std::string tmpfilename = buf + NOT_BYTECODE_FILE_EXT;
if (utils->isFileExist(tmpfilename)){
buf = tmpfilename; // .lua file
}else{
tmpfilename = buf + BYTECODE_FILE_EXT;
if (utils->isFileExist(tmpfilename)){
buf = tmpfilename; // .luac file
}
}
//获取全路径
std::string fullPath = utils->fullPathForFilename(buf);
Data data = utils->getDataFromFile(fullPath); //获取buffer
int rn = 0;
if (!data.isNull()){
//载入lua buffer
if (luaLoadBuffer(_state, (const char*)data.getBytes(), (int)data.getSize(), fullPath.c_str()) == 0){
rn = executeFunction(0); //执行栈顶 func
}
}
return rn;
}

  跟Cocos 这边进行对比发现基本逻辑实现好像差不多(所以我们也可以按照自己意愿封装自己的引擎了)。文本最终执行到了lua虚拟机然后进行函数执行。解释到这里似乎基本上已经完事儿了,是确定这样的吗?会有好奇心想要知道文本如何高效率精准被找到的吗?

1
2
3
//获取全路径   如何做到的 ?
std::string fullPath = utils->fullPathForFilename(buf);
Data data = utils->getDataFromFile(fullPath); //获取buffer 有什么其他要注意的吗 ?

  在我们的FileUtils 中有对外暴露的 setSearchPaths 函数,直面翻译函数名字叫做设置文件搜索路径。在我们不知道内部如何实现的时候我们可以尝试使用自己的思路进行猜测别人的逻辑如何实现,如果最后发现很接近,这样是不是很开心?如果发现不太一样,我们是不是能学到一些别人优秀的设计思想 ?继续回归代码层面,我们先做设想。设置进来的是文件路径数组,那么数组有两个目的,第一临时存储,第二方便遍历。所以从方便遍历的角度来讲进行文件查找,我们推测。

1
2
3
4
5
6
7
if(isFileNameExistence){
for(int i =0;i < arry.length;i++){
//1.取出每一个字符串中的内容 + 传递进入的文件名字
//2.判断 isFileExist(tmpfilename) 存在的话就返回拼接后的字符串,
}
//在最后如果没找到就返回没找到标识
}

  我们看下源码实现部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

std::string FileUtils::fullPathForFilename(const std::string &filename) const
{
if (filename.empty()){
return ""; //空字符串监测
}
if (isAbsolutePath(filename)){
return filename; //绝对路径直接返回 优化的点
}
// Already Cached ? 加入了缓存机制 _fullPathCache
auto cacheIter = _fullPathCache.find(filename);
if(cacheIter != _fullPathCache.end()){
return cacheIter->second;
}
// Get the new file name. 缓存机制中使用了字典,所以字典两边都需要进行监测是否已经存在
const std::string newFilename( getNewFilename(filename) );
std::string fullpath;
// 遍历搜索路径
for (const auto& searchIt : _searchPathArray) {
//这里有一个分辨率搜索顺序的Vector 如果有多套分辨率 下面的资源和脚本路径结构一样可以进行设置,默认为空字符串
for (const auto& resolutionIt : _searchResolutionsOrderArray){
//拼接内容 获取字符串内容 同时判断文件是否存在 存在返回全路径
fullpath = this->getPathForFilename(newFilename, resolutionIt, searchIt);
if (!fullpath.empty()){
// Using the filename passed in as key. 全路径不为空插入缓存 map 方便下次查找使用
_fullPathCache.insert(std::make_pair(filename, fullpath));
return fullpath;
}
}
}
if(isPopupNotify()){
CCLOG("cocos2d: fullPathForFilename: No file found at %s. Possible missing file.", filename.c_str());
}
// The file wasn't found, return empty string. 找不到返回空字符串
return "";
}

  所以看下执行的逻辑和顺序,思考一下别人巧妙的思想。鉴于cocos脚本和资源部分大多数都要走fileUtils ,是不是我们可以做一些更巧妙的东西加入到cocos 的源码中 ?