A闪的 BLOG 技术与人文
本篇记录下Cocos引擎在原生平台的启动流程,由于原生和web 版本的运行机制完全不同,底层有c++ 编写,而js变成了类似胶水层的东西,所以在调试运行时,回到看到js所产生的一些列“指令”被发送到c++层,并由原生层执行后反馈给js。这些部分在后面会一点一点的剖析。
我用的是macOS输出版本,因为这个目标平台相对来说代码比较干净。运行流程在iOS和android(本人略讨厌NDK)平台相差不大。
程序执行开始于 AppDelegate.mm
中的 applicationDidFinishLaunching
函数,内容如下:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRect rect = NSMakeRect(200, 200, 960, 640);
_window = [[NSWindow alloc] initWithContentRect:rect
styleMask:NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing:NSBackingStoreBuffered
defer:NO];
if (!_window) {
NSLog(@"Failed to allocated the window.");
return;
}
_window.title = @"Cocos creator 3D Game";
ViewController* viewController = [[ViewController alloc] initWithSize: rect];
_window.contentViewController = viewController;
_window.contentView = viewController.view;
[_window.contentView setWantsBestResolutionOpenGLSurface:YES];
[_window makeKeyAndOrderFront:nil];
_game = new Game(rect.size.width, rect.size.height);
_game->init();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillMiniaturizeNotification)name:NSWindowWillMiniaturizeNotification
object:_window];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidDeminiaturizeNotification)name:NSWindowDidDeminiaturizeNotification
object:_window];
}
默认的窗口尺寸为960,640,代码中较为简单,仅是对窗口属性进行创建和基本设置。和cocos引擎相关的只有2行。
_game = new Game(rect.size.width, rect.size.height);
_game->init();
事例化Game对象,并调用其init方法。Game类暴露的接口也相当简单,其定义在 Game.h 中。
class Game : public cc::Application
{
public:
/**
* width and height in logical pixel unit
*/
Game(int width, int height);
virtual bool init() override;
virtual void onPause() override;
virtual void onResume() override;
};
我们来拆解分析一下构造函数的具体逻辑和init函数中都做了那些事情。
由于 Game 继承自 cc::Application ,同时也并未重写构造函数,所以我们需要查阅 cc::Application 源码。由于此次发布为macOS平台,源码在 cocos/platform/mac/Application-mac.mm
中定义。函数实现如下:
Application::Application(int width, int height) {
Application::_instance = this;
_viewLogicalSize.x = width;
_viewLogicalSize.y = height;
_scheduler = std::make_shared<Scheduler>();
EventDispatcher::init();
#ifndef CC_USE_METAL
_timer = [[MyTimer alloc] initWithApp:this fps:_fps];
#endif
[[[[[NSApplication sharedApplication] delegate] getWindow] contentView] start];
}
这里主要是赋值窗口的尺寸,并且通过 make_shared 初始化 Scheduler。Scheduler调度器是非常核心关键的内容,后面会详细分析调度器。EventDispatcher 则是事件派发模块。
init 函数里面的操作相对较多,因为涉及到js层的东西,我们一步一步来。
第一步 调用 cc::Application::init();
,其函数内容如下:
bool Application::init() {
se::ScriptEngine *se = se::ScriptEngine::getInstance();
se->addRegisterCallback(setCanvasCallback);
#ifndef CC_USE_METAL
[_timer start];
#endif
return true;
}
此时主要是注册Canvas的回调函数,而 setCanvasCallback
函数的实现如下:
bool setCanvasCallback(se::Object *global) {
auto viewLogicalSize = Application::getInstance()->getViewLogicalSize();
se::ScriptEngine *se = se::ScriptEngine::getInstance();
char commandBuf[200] = {0};
NSView *view = [[[[NSApplication sharedApplication] delegate] getWindow] contentView];
sprintf(commandBuf, "window.innerWidth = %d; window.innerHeight = %d; window.windowHandler = 0x%" PRIxPTR ";",
(int)(viewLogicalSize.x),
(int)(viewLogicalSize.y),
(uintptr_t)view);
se->evalString(commandBuf);
return true;
}
核心逻辑为每次窗体发生尺寸变化后,执行 commandBuf ,这是一段简单的js代码。
第二步 jsb_set_xxtea_key("");
设置加密密钥。
第三步 jsb_init_file_operation_delegate
初始化js绑定中的文件操作委托函数。实现了 onGetDataFromFile
, onGetStringFromFile
,onGetFullPath
和 onCheckFileExist
函数。
第四步 如果是调试环境,则开启远程debugger功能。
第五步 设置js异常回调,默认情况下会将js中的异常打印到控制台中,这里是一个宏CC_LOG_ERROR
。
第六步 注册所以引擎模块 jsb_register_all_modules
,源码较长,但全部是调用 se->addRegisterCallback
方法的。
第七步 启动ScriptEngine,这里涉及到对v8的包装,后面进行详细分析。
第八步 这也算得上是最后一步了,通过本地文件,加载并执行js文件,分别是jsb-adapter/jsb-builtin.js
和main.js
两个文件。
到此为止,所有原生层面的逻辑基本准备完毕,接下来主要是执行main.js中的逻辑。