Cocos2d-x from the 2.x version to last week's Cocos2d-x 3.0 final version, the engine-driven core is still a single-threaded "dead loop", once a frame encounters a "big job", such as a large size of texture resource loading or network IO or a lot of computing, the screen will The inevitable emergence of cotton and slow response to the phenomenon. From the old Win32 GUI programming, Guru told us not to block the main thread (UI thread) and let the worker thread do the "big jobs".
Mobile games, even casual games, often involves a lot of texture resources, audio and video resources, file reading and writing, and network communications, processing a little bit will appear on the screen, the interaction is not smooth. Although the engine provides some support in some aspects, but sometimes it is more flexible to sacrifice worker threads, the following is the Cocos2d-x 3.0 final version of the game initialization as an example (for the Android platform), said how to carry out multi-threaded resource loading.
We often see a number of mobile games, starting after the first display with the company logo splash screen (flash screens), and then will enter a game welcome scene, click "Start" to officially enter the game main scene. And here the Flash screen display is often in the background will do another thing, that is, loading the game's picture resources, music and audio resources and configuration data reading, which is a "camouflage" bar, the purpose is to improve the user experience, In this way, subsequent scene rendering and scene switching directly use the data that has already been cache to memory, no need to load again.
First, add Flashscene for the game
In the game app initialization, we first create Flashscene, let the game display Flashscene screen as soon as possible:
Copy Code code as follows:
AppDelegate.cpp
BOOL Appdelegate::applicationdidfinishlaunching () {
... ...
flashscene* scene = Flashscene::create ();
Pdirector->runwithscene (Scene);
return true;
}
At Flashscene Init, we created a resource Load thread, and we used a resourceloadindicator as a medium for interacting between the rendering thread and the worker thread.
Copy Code code as follows:
FlashScene.h
struct Resourceloadindicator {
pthread_mutex_t Mutex;
BOOL Load_done;
void *context;
};
Class Flashscene:public Scene
{
Public
Flashscene (void);
~flashscene (void);
virtual BOOL init ();
Create_func (Flashscene);
BOOL Getresourceloadindicator ();
void Setresourceloadindicator (bool flag);
Private
void Updatescene (float dt);
Private
Resourceloadindicator Rli;
};
FlashScene.cpp
BOOL Flashscene::init ()
{
BOOL BRet = false;
do {
CC_BREAK_IF (! Ccscene::init ());
Size winsize = director::getinstance ()->getwinsize ();
Flashscene own resources can only be loaded synchronously
Sprite *BG = sprite::create ("Flashsceenbg.png");
Cc_break_if (!BG);
Bg->setposition (CCP (WINSIZE.WIDTH/2, WINSIZE.HEIGHT/2));
This->addchild (BG, 0);
This->schedule (Schedule_selector (Flashscene::updatescene)
, 0.01f);
Start the resource loading thread
Rli.load_done = false;
Rli.context = (void*) this;
Pthread_mutex_init (&rli.mutex, NULL);
pthread_attr_t attr;
Pthread_attr_init (&ATTR);
Pthread_attr_setdetachstate (&attr, pthread_create_detached);
pthread_t thread;
Pthread_create (&thread, &attr,
Resource_load_thread_entry, &rli);
Bret=true;
while (0);
return bRet;
}
Static void* resource_load_thread_entry (void* param)
{
Appdelegate *app = (appdelegate*) application::getinstance ();
Resourceloadindicator *rli = (resourceloadindicator*) param;
Flashscene *scene = (flashscene*) rli->context;
Load Music effect Resource
... ...
Init from config files
... ...
Load images data in worker thread
Spriteframecache::getinstance ()->addspriteframeswithfile (
"All-sprites.plist");
... ...
Set loading Done
Scene->setresourceloadindicator (TRUE);
return NULL;
}
BOOL Flashscene::getresourceloadindicator ()
{
BOOL Flag;
Pthread_mutex_lock (&rli.mutex);
flag = Rli.load_done;
Pthread_mutex_unlock (&rli.mutex);
return flag;
}
void Flashscene::setresourceloadindicator (BOOL flag)
{
Pthread_mutex_lock (&rli.mutex);
Rli.load_done = Flag;
Pthread_mutex_unlock (&rli.mutex);
Return
}
We check the indicator flag bit in the timer callback function, and when we find the load OK, switch to the next game start scene:
Copy Code code as follows:
void Flashscene::updatescene (float dt)
{
if (Getresourceloadindicator ()) {
Director::getinstance ()->replacescene (
Welcomescene::create ());
}
}
To this end, the initial design and implementation of the Flashscene is complete. Run a bit and try it.
Ii. solving the problem of collapse
On the Genymotion 4.4.2 Simulator, the game runs without the results that I expected, and the game crashed and exited after Flashscreen.
Through the monitor analysis of the game's running log, we saw some of the following exception log:
Copy Code code as follows:
Threadid=24:thread exiting, not yet detached (count=0)
Threadid=24:thread exiting, not yet detached (count=1)
Threadid=24:native Thread exited without detaching
It's very strange, when we created the thread, we set the pthread_create_detached attribute.
Copy Code code as follows:
Pthread_attr_setdetachstate (&attr, pthread_create_detached);
How can this problem occur, and there are three logs. Looking at the code Texturecache::addimageasync of the engine kernel, there are no special settings found in thread creation as well as threads master functions. Why can the kernel create threads, and I will crash when I create them myself. Debug multiple back and forth, the problem seems to focus on the tasks performed in Resource_load_thread_entry. In my code, I used simpleaudioengine to load the sound resources, use Userdefault to read some persistent data, remove these two tasks, the game will go to the next link without crashing.
What can simpleaudioengine and userdefault have in common? JNI call. Yes, there are multiple platforms at the bottom of the two interfaces, and for the Android platform, they all use the interface provided by JNI to invoke the methods in Java. And JNI is bound to multithreading. The Android Developer's website has this quote:
Copy Code code as follows:
All threads are Linux threads, scheduled by the kernel. They ' re usually started from managed code (using Thread.Start), but They can also is created elsewhere and then attached T o the JAVAVM. For example, a thread started with Pthread_create can is attached with the JNI attachcurrentthread or Attachcurrentthreada Sdaemon functions. Until a thread is attached, it has no jnienv, and cannot make JNI calls.
The new thread created by Pthread_create, by default, cannot be invoked by the JNI interface unless attach to the VM, obtains a JNIEnv object, and detach the VM before thread exit. Okay, let's try this. The Cocos2d-x engine provides some jnihelper methods to facilitate JNI-related operations.
Copy Code code as follows:
#if (Cc_target_platform = = cc_platform_android)
#include "Platform/android/jni/jnihelper.h"
#include <jni.h>
#endif
Static void* resource_load_thread_entry (void* param)
{
... ...
JAVAVM *VM;
JNIEnv *env;
VM = JNIHELPER::GETJAVAVM ();
Javavmattachargs Thread_args;
Thread_args.name = "Resource Load";
Thread_args.version = jni_version_1_4;
Thread_args.group = NULL;
Vm->attachcurrentthread (&env, &thread_args);
... ...
Your Jni Calls
... ...
Vm->detachcurrentthread ();
... ...
return NULL;
}
As to what is JAVAVM, what is jnienv,android Developer official documentation is described in this way:
The JAVAVM provides the "invocation interface" functions, which allow to create and destroy a JAVAVM. In theory your can have multiple Javavms per process, but Android only allows one.
The jnienv provides most of the JNI functions. Your native functions all receive a jnienv as the.
The jnienv is used for thread-local storage. For this reason, you cannot share a jnienv between threads.
Third, solve the problem of black screen
The above code successfully solves the problem of thread crashes, but the problem is not finished, because next we encounter a "black screen" event. The so-called "black screen", in fact, is not all black. But when entering the game Welcomscene, only Labelttf instances in scene can be displayed, and the rest sprite cannot be displayed. It certainly has something to do with our Chengga-load texture resources on the worker line:
Copy Code code as follows:
Spriteframecache::getinstance ()->addspriteframeswithfile ("All-sprites.plist");
We build spriteframe by compressing the graph into a large texture, which is the recommended optimization method of Cocos2d-x. But to find the root of the problem, you have to watch the monitor log. We did find some exception logs:
Copy Code code as follows:
Libegl:call to OpenGL ES APIs with no current context (logged once per thread)
Google learns that only renderer thread can make EGL calls, because the context of EGL is created in renderer thread, and Worker thread does not have the context of EGL, and in the EGL operation, The context cannot be found, so the operation fails and the texture is not displayed. To solve this problem you have to look at how Texturecache::addimageasync is doing.
The Texturecache::addimageasync only loads the image data on the worker thread, and the texture object Texture2d instance is created in Addimageasynccallback. That is, the texture is created in the renderer thread, so there is no "black screen" problem above us. To imitate Addimageasync, let's Modify the code:
Copy Code code as follows:
Static void* resource_load_thread_entry (void* param)
{
... ...
Allspritesimage = new Image ();
Allspritesimage->initwithimagefile ("All-sprites.png");
... ...
}
void Flashscene::updatescene (float dt)
{
if (Getresourceloadindicator ()) {
Construct texture with preloaded images
Texture2d *allspritestexture = texturecache::getinstance ()->
AddImage (Allspritesimage, "all-sprites.png");
Allspritesimage->release ();
Spriteframecache::getinstance ()->addspriteframeswithfile (
"All-sprites.plist", allspritestexture);
Director::getinstance ()->replacescene (Welcomescene::create ());
}
}
After completing this modification, the game picture becomes all normal, the multithread resource loading mechanism is formally effective.