imgタグのように使えるスプライト

アイテムやキャラクター、イベント用バッチ等、
コンテンツ内で扱う画像リソースが段々と着実に増えていき
初回ダウンロードの増加が止まらない。。。

そんな悪夢な未来を、手間なく楽に回避する方法について考えてみました。

* * *

そして思い付いたのは、随時追加される画像リソースを
ネットワークから直接取得する。

つまり、htmlのimgタグのような使い方で
勿論urlを指定して直接表示できるスプライトです。

理想としては、こんな感じに使いたい。

```cpp
auto sprite = Sprite::createWithUrl("http://resource-server/a.png");
addChild( sprite );
```

後はロードが終わったら勝手に表示されてくれて、
初期化中にUIスレッドに負荷をかけない事が理想。

これを実現するには、

ネットワークから画像ファイルをダウンロードするのに
・network::HttpClient::getInstance()->sendImmediate

端末にダウンロード済みの画像ファイルを非同期読み込みするのに
・Director::getInstance()->getTextureCache()->addImageAsync

を使うのが良さそうです。


コードにすると、こんな感じでしょうか。
* * *

※とりあえず、Spriteを継承したLazySpriteとして定義してみます。

初期化時に先ず、ファイルをダウンロードします

```cpp
bool LazySprite::initWithURL(const std::string& url){
    if( Sprite::init() ){
        _saveFileDir = FileUtils::getInstance()->getWritablePath();
        _saveFilePath = _saveFileDir + url.substr( url.rfind('/')+1, std::string::npos );

        network::HttpRequest* req = new network::HttpRequest();
        req->setRequestType( network::HttpRequest::Type::GET );
        req->setUrl( url.c_str() );
        req->setResponseCallback( CC_CALLBACK_2(LazySprite::httpRequestCallback, this) );

        // ダウンロード中に自インスタンスが削除されないように、参照カウントを上げておく
        retain();
        // HttpRequest発行
        //   network::HttpClient::getInstance()->sendではなく
        //   network::HttpClient::getInstance()->sendImmediateなのは、
        //   network::HttpClient::getInstance()->sendでAPIコールをしている場合に
        //   阻害される事がないようにする為
        network::HttpClient::getInstance()->sendImmediate( req );
        return true;
    }
    return false;
}
```

次にダウンロードしたファイルをローカルへ保存し、そのテクスチャを非同期で読み込みます。
本当はダウンロードしたバイナリから直接Imageを作成できると良いのですが、
その場合は自分でスレッドを立てなければいけなさそうなので、
ひとまずaddImageAsyncを使います。

```cpp:
void LazySprite::httpRequestCallback(network::HttpClient* client, network::HttpResponse* response){
    
    if( response->getResponseCode() == 200 ){
        // ダウンロードしたファイルをローカルへ保存する
        if( FileUtils::getInstance()->createDirectory( _saveFileDir ) ){
            FILE* file = fopen( _saveFilePath, "wb" );
            fwrite( &response->getResponseData()->at(0), response->getResponseData()->size(), 1, file );
            fclose( file );
        }
        // 自分以外からの参照があれば処理
        if( getReferenceCount() > 1 ){
            // テクスチャ読込中に自インスタンスが削除されないように、参照カウントを上げておく
            retain();
            // テクスチャ読み込み開始
            Director::getInstance()->getTextureCache()->addImageAsync( _saveFilePath, CC_CALLBACK_1(LazySprite::textureLoadCallback, this) );
        }
    }else{
        CCLOG("%s", _saveFilePath.c_str());
        CC_ASSERT(0);
    }
    
    // initWithURLで上げた参照カウントを戻す
    release();
}
```

テクスチャの読み込みが完了したら、スプライトへ適用します。
このタイミングでスプライトが表示されます。

```cpp:
void LazySprite::textureLoadCallback(Texture2D* texture){
    
    // 自分以外からの参照があれば処理
    if( getReferenceCount() > 1 ){
        setTexture( texture );
        setTextureRect( Rect(0, 0, texture->getContentSize().width, texture->getContentSize().height ) );
    }
    
    // httpRequestCallbackで上げた参照カウントを戻す
    release();
}
```

注意点としては、各非同期処理の完了待ち中に
インスタンスが削除されてしまう(removeChild等で)と良くないことになる為、
非同期処理コールの直前に自身をretainし、終わったらreleaseという感じで保護します。

最後に、cocos2d-x恒例の create**** メソッドを定義します。

```cpp
Sprite* LazySprite::createWithURL(const std::string& url){
    LazySprite *sprite = new (std::nothrow) LazySprite();
    if (sprite && sprite->initWithURL(url))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}
```

これで cocos2d-x版、imgタグみたいなクラスができました。

更にダウンロードした画像ファイルをキャッシュする機能を付けると
コチラのようになります。
[CCLazySprite.h](https://github.com/yassy0413/cocos2dx-3.x-util/blob/develop/CCLazySprite.h)
[CCLazySprite.cpp](https://github.com/yassy0413/cocos2dx-3.x-util/blob/develop/CCLazySprite.cpp)