概要
- Cocos2d-xのCCDirector.cppの
void Director::purgeCachedData(void)を行うとLabelで描画した文字が抜けることがある
バージョン情報
- Cocos2d-x v3.2.0
- 現在の最新バージョンv3.14.1でも未修正だったのでおそらくこの現象は起こる
原因概要
Director::purgeCachedData() 内の処理に問題あり。
void Director::purgeCachedData(void)
{
FontFNT::purgeCachedData();
FontAtlasCache::purgeCachedData();
if (s_SharedDirector->getOpenGLView())
{
SpriteFrameCache::getInstance()->removeUnusedSpriteFrames();
_textureCache->removeUnusedTextures();
// Note: some tests such as ActionsTest are leaking refcounted textures
// There should be no test textures left in the cache
CCLOG("%s\n", _textureCache->getCachedTextureInfo().c_str());
}
FileUtils::getInstance()->purgeCachedEntries();
}
原因詳細
Director::purgeCachedData(void) で FontAtlasCache::purgeCachedData() が呼ばれる。
FontAtlasCache::purgeCachedData() でFontAtlasの purgeTexturesAtlas() が呼ばれる。
void FontAtlas::purgeTexturesAtlas()
{
FontFreeType* fontTTf = dynamic_cast<FontFreeType*>(_font);
if (fontTTf && _atlasTextures.size() > 1)
{
auto eventDispatcher = Director::getInstance()->getEventDispatcher();
eventDispatcher->dispatchCustomEvent(CMD_PURGE_FONTATLAS,this);
eventDispatcher->dispatchCustomEvent(CMD_RESET_FONTATLAS,this);
}
}
ここでCMD_PURGE_FONTATLASとCMD_RESET_FONTATLASのイベントが同時に発行される。
この2つはどこでもセットで呼ばれている。
このイベントを受け取っているのはCCLabelクラス。
CMD_PURGE_FONTATLASのイベントで、SpriteBatchNodeをclearしている。
_fontAtlasも解放している。
その後、CMD_RESET_FONTATLASで_fontAtlasがnullptrになった状態で
setTTFCongig(_fontConfig)を行う処理があるが、
この時点で_fontAtlasがnullptrになる場合があり、結果その処理に行かず、リセットされない。
結果、_fontAtlasと_atlasMapに不整合が生じ、文字が再描画されない(特定の文字が解放されっぱなしになっている)。
_purgeTextureListener = EventListenerCustom::create(FontAtlas::CMD_PURGE_FONTATLAS, [this](EventCustom* event){
if (_fontAtlas && _currentLabelType == LabelType::TTF && event->getUserData() == _fontAtlas)
{
for (auto&& it : _letters)
{
it.second->setTexture(nullptr);
}
_batchNodes.clear();
// _fontAtlasが解放される
if (_fontAtlas)
{
FontAtlasCache::releaseFontAtlas(_fontAtlas);
}
}
});
_eventDispatcher->addEventListenerWithFixedPriority(_purgeTextureListener, 1);
_resetTextureListener = EventListenerCustom::create(FontAtlas::CMD_RESET_FONTATLAS, [this](EventCustom* event){
// この時点で_fontAtlasがnullptrになっている場合はifに入らない
if (_fontAtlas && _currentLabelType == LabelType::TTF && event->getUserData() == _fontAtlas)
{
_fontAtlas = nullptr;
this->setTTFConfig(_fontConfig);
for (auto&& it : _letters)
{
getLetter(it.first);
}
}
});
修正
- CMD_RESET_FONTATLASイベントで_fontAtlasを解放するようにした
_purgeTextureListener = EventListenerCustom::create(FontAtlas::CMD_PURGE_FONTATLAS, [this](EventCustom* event){
if (_fontAtlas && _currentLabelType == LabelType::TTF && event->getUserData() == _fontAtlas)
{
for (auto&& it : _letters)
{
it.second->setTexture(nullptr);
}
_batchNodes.clear();
}
});
_eventDispatcher->addEventListenerWithFixedPriority(_purgeTextureListener, 1);
_resetTextureListener = EventListenerCustom::create(FontAtlas::CMD_RESET_FONTATLAS, [this](EventCustom* event){
if (_fontAtlas && _currentLabelType == LabelType::TTF && event->getUserData() == _fontAtlas)
{
// ここで明確にreleaseしてnullptrする
CC_SAFE_RELEASE_NULL(_fontAtlas);
this->setTTFConfig(_fontConfig);
for (auto&& it : _letters)
{
getLetter(it.first);
}
}
});
_eventDispatcher->addEventListenerWithFixedPriority(_resetTextureListener, 2);