Cocos2dx和Mac应用程序混合开发阻塞主线程
因为公司的需要,我们需要做一个Cocos2dx 和 Mac原生混合的一个应用出来,但是期间出现了一些问题,因为我们原生APP已经做完在那边了,所以需要在原生APP里面插入Cocos2dx的内容,所以导致了一些问题。
我们这个版本是基于Cocos2dx 3.15.1,如果是2.x版本的朋友应该没有这个问题,原因是因为cocos2dx内部的loop形式不一样。
ok,进入正题。
首先我们可以看下 “CCApplication-mac.h” 这个文件中的 “run”方法
int Application::run()
{
initGLContextAttrs();
if(!applicationDidFinishLaunching())
{
return 1;
}
//
// long lastTime = 0L;
// long curTime = 0L;
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
while (!glview->windowShouldClose()) {
long lastTime = getCurrentMillSecond();
director->mainLoop();
glview->pollEvents();
long curTime = getCurrentMillSecond();
if (curTime - lastTime < _animationInterval)
{
usleep(static_cast<useconds_t>((_animationInterval - curTime + lastTime)*1000));
}
}
/* Only work on Desktop
* Director::mainLoop is really one frame logic
* when we want to close the window, we should call Director::end();
* then call Director::mainLoop to do release of internal resources
*/
if (glview->isOpenGLReady())
{
director->end();
director->mainLoop();
}
glview->release();
return 0;
}
其中我们应该可以看到
while (!glview->windowShouldClose())
这一个while就是起了一个cocos内部使用的runloop,然后开始loop自己的一个消息循环。那么问题来了,他在主线程上面,他什么时候可以进入到下一个Mac的loop呢?就是当glView被close的时候。
那么问题来了,也就是游戏在跑的时候,我mac的主线程是被阻塞的。这很明显不合理。
然后有人可能会想一个办法,那就是在子线程调用run,像这样:
dispatch_async(dispatch_queue_create("cocos", 0), ^{
run();
});
这又有一个问题,那就是画面的绘制必须在主线程。所以这个方法行不通。
那么我们可以插入到主线程的loop中,like this
dispatch_async(dispatch_queue_create("cocos", 0), ^{
while (!glview->windowShouldClose())
{
long lastTime = getCurrentMillSecond();
dispatch_semaphore_t tt = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
director->mainLoop();
glview->pollEvents();
dispatch_semaphore_signal(tt);
});
dispatch_semaphore_wait(tt, DISPATCH_TIME_FOREVER);
long curTime = getCurrentMillSecond();
if (curTime - lastTime < _animationInterval)
{
usleep(static_cast<useconds_t>((_animationInterval - curTime + lastTime)*1000));
}
}
});
以为这样就ok了?问题肯定不会着么简单,然后我发现系统崩溃在:
glfwPollEvents();
系统报错:
WechatIMG7.jpeg一脸懵逼,说是autoReleasePool被干掉了。然后发现这个glfw是一个框架,cocos倒入的是一个.a文件,然后我们在去找了这个glfw的源码
void _glfwPlatformPollEvents(void)
{
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event == nil)
break;
[NSApp sendEvent:event];
}
[_glfw.ns.autoreleasePool drain];
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
}
发现这里其实是监听了mac的原生事件的,所以我们点击屏幕的时候他还是会响应的,具体这个怎么响应的大家去看看mac的runloop
那么我们下面看到一句就是
[_glfw.ns.autoreleasePool drain];
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
问题就是这两句造成的,所以很果断的把她注释掉。实际上,大家可能要问,为啥不把所有代码注释掉,因为反正不会阻塞主线程了,我还是觉得大家先去看看runloop。
然后我们重新编译 出一个 glfw.a 文件导入到工程
编译过了,主线程也ok了。喜大普泵。
没错一般情况下呢,现在这样做很多应用可以解决了,但是我们接入了基于WebRtc的音视频服务,然而这个视频呢又是通过opengl渲染的,你发现控制台一直报错
WX20170817-170630.png然后查资料发现这个错误就是 上下文指向的问题造成的,然后我们修改一下代码
dispatch_async(dispatch_queue_create("cocos", 0), ^{
while (!glview->windowShouldClose())
{
long lastTime = getCurrentMillSecond();
dispatch_semaphore_t tt = dispatch_semaphore_create(0);
@autoreleasepool {
dispatch_async(dispatch_get_main_queue(), ^{
auto window = ((cocos2d::GLViewImpl*)glview)->getWindow();
glfwMakeContextCurrent(window);
director->mainLoop();
glview->pollEvents();
dispatch_semaphore_signal(tt);
});
}
dispatch_semaphore_wait(tt, DISPATCH_TIME_FOREVER);
long curTime = getCurrentMillSecond();
if (curTime - lastTime < _animationInterval)
{
usleep(static_cast<useconds_t>((_animationInterval - curTime + lastTime)*1000));
}
}
});
然后就发现不错可以用了。
文章写的很烂,也懒得排版了,哈哈哈