Related Discussions
...

Hi,

I’m using Spine in my Cococs2d-x based game. It works great. Thank you.

Currently, I create SkeletonAnimation with skeletonDataFile and atlas and get a problem of performance. The game doesn’t run smoothly when spine animation playing.

My question is: How to preload a SkeletonAnimation before using it and unload when needed. I’m using Cocos2d-x version 3.1.1.

Thank you.

How you load the SkeletonAnimation mostly doesn't affect how it performs, unless you are loading it every frame or very often? You can load a SkeletonData, then create any number of SkeletonAnimations with it. This way you only load the data once.

Hi Nate,

Thank you for your quick support.

In my game, I don't loading SkeletonAnimation every frame or very often. The only problem is the game takes a short moment for SkeletonAnimation first time created and played.

I got your idea. I created a SkeletonData first then create SkeletonAnimations with it.

One more question I need your help. Please show me how to load SkeletonData asynchronously.

Thank you.

When you do the loading is up to you.

8 days later

Hi Nate,
Sorry for my late response. Thanks you for your help. But I think you don't get me because my question was not clear.

Cocos2d-x supports to load images to cache asynchronously, but I don't know If I can create SkeletonData synchronously.

Please give me advice. 🙂 Thank you.

I'm not sure, I'm not familiar with how cocos2d-x loads resources. Probably your question is best asked on the cocos2d-x forum. Sorry I can't be of more help!

It's a bit of a loaded request.

I'm browsing through the Cocos2dx sources and searching their forums and it looks to me as async loading of assets are constrained to a small subset of asset types (images, audio, armature).

Cocos2dx doesn't have great async loading support. They explicitly state that most of the API is non-thread-safe, especially the Reference Counting/ AutoRelease pools. Looking through their multithreaded code, it has some pretty common mistakes (lazy initialization) and generally a poor design. TextureCache::addImageAsync can't even be called asynchronously, which creates problems for loading.

I'm not trying to be hyper critical of Cocos2dx, but multithreading tends to expose flaws.

Here are the two major causes of performance issues that are generally solved by multithreading:

Unless you have made some particularly boneheaded mistakes, loading from disk is going to be the easiest form of asynchronization.

Example Loading Spine animation w/ multithreading
To load a spine animation, you need to load the following files:

  1. atlas

  2. images referenced in atlas

  3. JSON

While you are processing the atlas, it's going to kick out image load requests that you are going to want to perform asynchronously. Depending on how the references/pointers are setup, you may have to wait for all images in the atlas to load before completing. Remember earlier how I said "TextureCache::addImageAsync" wasn't thread-safe? All of those calls need to be issued on the main thread.

Another kicker: image file-loading and pixel-preprocessing can be done on a worker thread, but texture upload to GPU has to be done on the main thread.

Loading and processing a JSON file should be fine, but remember that the Atlas needs to be completely loaded before loading the JSON.

Loading would look something like:

  1. Read Atlas File [Worker Thread]

  2. Process Atlas File [MainThread] // to issue addImageAsync

  3. Wait for images to Load

    1. Load Image File [Worker Thread]

    2. Upload to GPU [MainThread]

  4. Read JSON from Disk [Worker Thread]

  5. Parse JSON [Worker Thread]

Design
To account for the [Worker Thread] / [Main Thread] split, your JOB should have 2 phases: one for background on worker thread and the other foreground on main thread. To process the foreground phase, you'll need a JobScheduler class running on the main thread listening for the engine update() tick.

The back and forth between main/worker thread is best handled by splitting up into sub jobs. {1,2} would be the first job, waiting on CCTextureCache to perform {3} would be the second, and {4,5} would be the third. To orchestrate that you'll need some sort of controller with a state machine and probably a callback mechanism.

You'll also have to create thread pools, concurrent queues, signals and mutexes, etc.. etc.. etc...

Example Pre-loading files w/o Spine
Does the previous approach sound complicated? It is. Every step along the way is probably more complex that what I've let on and a potential bug.

Remember the image from earlier? https://d262ilb51hltx0.cloudfront.net/m ... vZCNLL.png

The IO from disk is a major culprit in causing the loading issues. What if instead of loading a Spine animation asynchronously, you only loaded the data from disk?

Spine has methods spSkeletonJson_readSkeletonData and spAtlas_create which take character buffers instead of file names. If you use multithreaded loading to load the files into character buffers, it should decrease loading times noticeably.

You could simply copy CCTextureCache and rewrite it to load files from disk (generically) into a character buffer. e.g. "CCFileDataCache"

Issue calls to load ALL of the JSON/Atlas files async and then hit the cache when creating the SkeletonData.

The word 'load' appears 31 times in this post

Nice post James! 🙂

_spAtlasPage_createTexture could queue the loading of images for later. The Spine runtime no longer needs the width and height of the texture, it reads this from the atlas data.

Here's a high level overview of what needs to be done:

  1. Read the atlas data file from disk either from the main thread or from another thread to a char* buffer. This data is very unlikely to be huge, so moving it to another thread is a relatively small improvement.

  2. Use spAtlas to parse the atlas data either from the main thread or from another thread. Again, this data is very unlikely to be huge, so moving it to another thread is a small improvement, likely smaller than the previous step.

  3. spAtlas calls _spAtlasPage_createTexture for each texture it needs. Loading images is relatively slow, so this is where you need to load the images on another thread, however cocos2d-x allows. If you are creating the spAtlas on another thread and TextureCache::addImageAsync is what you need to use (I don't actually know) and you can't call addImageAsync from another thread as jpoag says, then in _spAtlasPage_createTexture you could store what textures need to be loaded for which spAtlasPage and then later queue them to be loaded from the main thread.

  4. Read the JSON skeleton data file from disk on another thread and parse it using spSkeletonData. This data can grow quite large, so moving this to another thread is the biggest improvement.

Hi James and Nate,

You're so awesome. I appreciate your helps.

Thanks for great forum.