A piece of code for loading and caching Skeleton Animation in IO task [Cocos2dx.3.17.2]

发布时间 2023-10-25 14:19:01作者: GameSprite
/****************************************************************************
 Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
 
 http://www.cocos2d-x.org
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
#include "spine/spine-cocos2dx.h"
#include "base/CCAsyncTaskPool.h"
#include  <mutex>

USING_NS_CC;
using namespace spine;

Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}


// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
    printf("Error while loading: %s\n", filename);
    printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n");
}
struct ImageData {
    std::string file;
    Data* data;
};
struct SkeletonCachedData {
    std::string* atlas;
    std::string* json;
    std::vector<ImageData> imagesData;
};
struct SkeletonLoadParam {
    std::string jsonFile;
    std::string atlasFile;
    SkeletonCachedData skeletonCachedData;
};

#define CACHE_SKELETON_IMAGE 1
class SkeletonCache {
public:
    static SkeletonCache * getInstance(){
        static SkeletonCache _instance;//c++11后静态变量的创建是线程安全的
        return &_instance;
    }
    
    SkeletonCachedData addSkeletonCachedData(const std::string& jsonFile, const std::string& atlasFile){
        SkeletonCachedData cachedData;
        
        std::lock_guard<std::mutex> lg(mtx);
        
        if(_atlasCache.find(atlasFile) == _atlasCache.end()){
            _atlasCache[atlasFile] = new std::string(FileUtils::getInstance()->getStringFromFile(atlasFile));//这个api是线程安全的
        }
        cachedData.atlas = _atlasCache[atlasFile];
        
        if(_jsonCache.find(jsonFile) == _jsonCache.end()){
            _jsonCache[jsonFile] = new std::string(FileUtils::getInstance()->getStringFromFile(jsonFile));
        }
        cachedData.json = _jsonCache[jsonFile];
        
        if(_imagesCache.find(atlasFile) == _imagesCache.end()){
            _imagesCache[atlasFile] = getImagesData(atlasFile, _atlasCache[atlasFile]);
        }
        cachedData.imagesData = _imagesCache[atlasFile];
        
        return cachedData;
    }
    
    void clearSkeletonCachedData(const std::string& jsonFile, const std::string& atlasFile){
        std::lock_guard<std::mutex> lg(mtx);
        
        auto atlasIter = _atlasCache.find(atlasFile);
        if(atlasIter != _atlasCache.end()) {
            delete atlasIter->second;
            _atlasCache.erase(atlasIter);
        }
        
        auto jsonIter = _jsonCache.find(jsonFile);
        if(jsonIter != _jsonCache.end()) {
            delete jsonIter->second;
            _jsonCache.erase(jsonIter);
        }
        
        auto imagesIter = _imagesCache.find(atlasFile);
        if(imagesIter != _imagesCache.end()){
            for(auto& imageData : imagesIter->second){
                delete imageData.data;
            }
            _imagesCache.erase(imagesIter);
        }
    }
    
    ~SkeletonCache(){
        std::lock_guard<std::mutex> lg(mtx);
        CCLOGWARN("The ~SkeletonCache shouldn't be called within game running!");
        
        for(auto it = _atlasCache.begin(); it != _atlasCache.end(); it++)
            delete it->second;
        
        for(auto it = _jsonCache.begin(); it != _jsonCache.end(); it++)
            delete it->second;
        
        for(auto it = _imagesCache.begin(); it != _imagesCache.end(); it++){
            for(auto& imageData : it->second){
                delete imageData.data;
            }
        }
    }
    
    static std::string getAtlasDir(const std::string& atlasFile){
        const char* path = atlasFile.c_str();
        const char* lastForwardSlash = strrchr(path, '/');
        const char* lastBackwardSlash = strrchr(path, '\\');
        const char* lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
        if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
        
        int dirLength = (int)(lastSlash ? lastSlash - path : 0);
        char *dir = (char*)malloc(sizeof(char) * (dirLength + 1));
    
        memcpy(dir, path, dirLength);
        dir[dirLength] = '\0';
        
        std::string sDir(dir);
        free(dir);
        
        return sDir;
    }
private:
    std::map<std::string, std::string*> _atlasCache;
    std::map<std::string, std::string*> _jsonCache;
    std::map<std::string, std::vector<ImageData>> _imagesCache;
    
    std::mutex mtx;
    
    typedef struct {
        const char* begin;
        const char* end;
    } spStr;
    
    //从atlas提取纹理
    std::vector<ImageData> getImagesData(const std::string& atlasFile, const std::string* atlas){
        std::vector<ImageData> imagesData;
        
        /* Get directory from atlas path. */
        const std::string dir = getAtlasDir(atlasFile);
        int length = (int)atlas->size();
        
        const char* begin = atlas->c_str();
        const char* end = begin + length;
        
        int dirLength = (int)dir.length();
        int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\';
        
        spStr str;
        bool page = false;
        while (readLine(&begin, end, &str)) {
            if (str.end - str.begin == 0) {
                page = false;
            }
            else if (!page) {
                page = true;
                
                char* name = mallocString(&str);
                char* path = (char*)malloc(sizeof(char) * (dirLength + needsSlash + strlen(name) + 1));
                memcpy(path, dir.c_str(), dirLength);
                if (needsSlash) path[dirLength] = '/';
                strcpy(path + dirLength + needsSlash, name);
                
                Data d = FileUtils::getInstance()->getDataFromFile(path);
                if(!d.isNull()){
                    ImageData imgData;
                    imgData.file = path;
                    imgData.data = new Data(std::move(d));
                    imagesData.push_back(imgData);;
                }else{
                    CCLOGERROR("Get imageData failled: %s", path);
                }

                free(path);
                free(name);
            }
        }
        
        return std::move(imagesData);
    }
    void trim(spStr* str) {
        while (isspace((unsigned char)*str->begin) && str->begin < str->end)
            (str->begin)++;
        if (str->begin == str->end) return;
        str->end--;
        while (isspace((unsigned char)*str->end) && str->end >= str->begin)
            str->end--;
        str->end++;
    }
    /* Tokenize string without modification. Returns 0 on failure. */
    int readLine(const char** begin, const char* end, spStr* str) {
        if (*begin == end) return 0;
        str->begin = *begin;

        /* Find next delimiter. */
        while (*begin != end && **begin != '\n')
            (*begin)++;

        str->end = *begin;
        trim(str);

        if (*begin != end) (*begin)++;
        return 1;
    }
    char* mallocString(spStr* str) {
        int length = (int)(str->end - str->begin);
        char* _string = (char*)malloc(sizeof(char) * (length + 1));
        memcpy(_string, str->begin, length);
        _string[length] = '\0';
        return _string;
    }
};

void createSkeletonAnimationAsync(const std::string& jsonFile, const std::string& atlasFile, const std::function<void(SkeletonAnimation*)>& callBack)
{
    SkeletonLoadParam* loadParam = new SkeletonLoadParam();
    loadParam->jsonFile = jsonFile;
    loadParam->atlasFile = atlasFile;
    
    AsyncTaskPool::getInstance()->enqueue(AsyncTaskPool::TaskType::TASK_IO,
        [callBack](void* param) {//cocos thread
            SkeletonLoadParam* loadParam = (SkeletonLoadParam*)param;
        
            for(auto imageData : loadParam->skeletonCachedData.imagesData){
                auto img = new Image();
                img->initWithImageData(imageData.data->getBytes(), imageData.data->getSize());
                Director::getInstance()->getTextureCache()->addImage(img, imageData.file);
            }
        
            std::string dir = SkeletonCache::getAtlasDir(loadParam->atlasFile);
            int length = (int)loadParam->skeletonCachedData.atlas->size();
            
            auto atlas = spAtlas_create(loadParam->skeletonCachedData.atlas->c_str(), length, dir.c_str(), NULL);
            CCLOG("texture id: %u",((Texture2D*)atlas->pages->rendererObject)->getName());
            auto attachmentLoader = Cocos2dAttachmentLoader_create(atlas);
            auto json = spSkeletonJson_createWithLoader(&attachmentLoader->super);
            auto skeletonData = spSkeletonJson_readSkeletonDataFile(json, loadParam->jsonFile.c_str());
            SkeletonAnimation* animation = SkeletonAnimation::createWithData(skeletonData);
            callBack(animation);
        
            spSkeletonJson_dispose(json);
            delete loadParam;
        },
        (void*)loadParam,
        [loadParam, atlasFile, jsonFile]() {
            auto skeletonCache = SkeletonCache::getInstance()->addSkeletonCachedData(jsonFile, atlasFile);
            loadParam->skeletonCachedData = skeletonCache;
        }
    );
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }
    //test code
    createSkeletonAnimationAsync("welcome_back.json", "welcome_back.atlas", [this](SkeletonAnimation* am){
        auto winSize = Director::getInstance()->getWinSize();
        am->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
        addChild(am);
        am->setPosition(winSize / 2);
        am->setAnimation(0, "2", true);
        am->setDebugBonesEnabled(true);
    });
    return true;
}


void HelloWorld::menuCloseCallback(Ref* pSender)
{
    //Close the cocos2d-x game scene and quit the application
    Director::getInstance()->end();

    /*To navigate back to native iOS screen(if present) without quitting the application  ,do not use Director::getInstance()->end() as given above,instead trigger a custom event created in RootViewController.mm as below*/

    //EventCustom customEndEvent("game_scene_close_event");
    //_eventDispatcher->dispatchEvent(&customEndEvent);


}