Fix cocos2d/cocos2d-js#1658: Fix js_load_remote_image issue with multithread and improve Downloader

This commit is contained in:
pandamicro 2015-06-02 10:08:13 +08:00
parent a2fc7a85d6
commit fdec552ed7
5 changed files with 136 additions and 94 deletions

View File

@ -908,7 +908,7 @@ bool js_cocos2dx_ext_release(JSContext *cx, uint32_t argc, jsval *vp)
} }
__JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback) __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback)
: _cx(cx) : _cx(cx)
, _url(url) , _url(url)
, _buffer(nullptr) , _buffer(nullptr)
@ -917,7 +917,27 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
_obj.ref().set(obj); _obj.ref().set(obj);
_jsCallback.construct(_cx); _jsCallback.construct(_cx);
_jsCallback.ref().set(callback); _jsCallback.ref().set(callback);
}
__JSDownloaderDelegator::~__JSDownloaderDelegator()
{
_obj.destroyIfConstructed();
_jsCallback.destroyIfConstructed();
if (_buffer != nullptr)
free(_buffer);
_downloader->setErrorCallback(nullptr);
_downloader->setSuccessCallback(nullptr);
}
__JSDownloaderDelegator *__JSDownloaderDelegator::create(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback)
{
__JSDownloaderDelegator *delegate = new (std::nothrow) __JSDownloaderDelegator(cx, obj, url, callback);
delegate->autorelease();
return delegate;
}
void __JSDownloaderDelegator::startDownload()
{
if (Director::getInstance()->getTextureCache()->getTextureForKey(_url)) if (Director::getInstance()->getTextureCache()->getTextureForKey(_url))
{ {
onSuccess(nullptr, nullptr, nullptr); onSuccess(nullptr, nullptr, nullptr);
@ -929,8 +949,9 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
_downloader->setErrorCallback( std::bind(&__JSDownloaderDelegator::onError, this, std::placeholders::_1) ); _downloader->setErrorCallback( std::bind(&__JSDownloaderDelegator::onError, this, std::placeholders::_1) );
_downloader->setSuccessCallback( std::bind(&__JSDownloaderDelegator::onSuccess, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) ); _downloader->setSuccessCallback( std::bind(&__JSDownloaderDelegator::onSuccess, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) );
long contentSize = _downloader->getContentSize(_url); cocos2d::extension::Downloader::HeaderInfo info = _downloader->getHeader(_url);
if (contentSize == -1) { long contentSize = info.contentSize;
if (contentSize == -1 || info.responseCode >= 400) {
cocos2d::extension::Downloader::Error err; cocos2d::extension::Downloader::Error err;
onError(err); onError(err);
} }
@ -942,56 +963,69 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
} }
} }
__JSDownloaderDelegator::~__JSDownloaderDelegator() void __JSDownloaderDelegator::download()
{ {
if (_buffer != nullptr) retain();
free(_buffer); startDownload();
_downloader->setErrorCallback(nullptr); }
_downloader->setSuccessCallback(nullptr);
void __JSDownloaderDelegator::downloadAsync()
{
retain();
auto t = std::thread(&__JSDownloaderDelegator::startDownload, this);
t.detach();
} }
void __JSDownloaderDelegator::onError(const cocos2d::extension::Downloader::Error &error) void __JSDownloaderDelegator::onError(const cocos2d::extension::Downloader::Error &error)
{ {
if (!_jsCallback.ref().isNull()) { Director::getInstance()->getScheduler()->performFunctionInCocosThread([this]
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext(); {
JS::RootedObject global(cx, ScriptingCore::getInstance()->getGlobalObject()); JS::RootedValue callback(_cx, OBJECT_TO_JSVAL(_jsCallback.ref()));
if (!callback.isNull()) {
JSAutoCompartment ac(_cx, _obj.ref()); JS::RootedObject global(_cx, ScriptingCore::getInstance()->getGlobalObject());
JSAutoCompartment ac(_cx, global);
jsval succeed = BOOLEAN_TO_JSVAL(false); jsval succeed = BOOLEAN_TO_JSVAL(false);
JS::RootedValue retval(cx); JS::RootedValue retval(_cx);
JS_CallFunctionValue(cx, global, _jsCallback.ref(), JS::HandleValueArray::fromMarkedLocation(1, &succeed), &retval); JS_CallFunctionValue(_cx, global, callback, JS::HandleValueArray::fromMarkedLocation(1, &succeed), &retval);
} }
this->release(); release();
});
} }
void __JSDownloaderDelegator::onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId) void __JSDownloaderDelegator::onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId)
{ {
Image *image = new Image(); Image *image = new Image();
jsval valArr[2];
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext();
JS::RootedObject global(cx, ScriptingCore::getInstance()->getGlobalObject());
cocos2d::TextureCache *cache = Director::getInstance()->getTextureCache(); cocos2d::TextureCache *cache = Director::getInstance()->getTextureCache();
JSAutoCompartment ac(_cx, _obj.ref() ? _obj.ref() : global);
Texture2D *tex = cache->getTextureForKey(_url); Texture2D *tex = cache->getTextureForKey(_url);
if (!tex)
{
if (image->initWithImageData(_buffer, _size))
{
tex = Director::getInstance()->getTextureCache()->addImage(image, _url);
}
}
image->release();
Director::getInstance()->getScheduler()->performFunctionInCocosThread([this, tex]
{
JS::RootedObject global(_cx, ScriptingCore::getInstance()->getGlobalObject());
JSAutoCompartment ac(_cx, global);
jsval valArr[2];
if (tex) if (tex)
{ {
valArr[0] = BOOLEAN_TO_JSVAL(true); valArr[0] = BOOLEAN_TO_JSVAL(true);
js_proxy_t* p = jsb_get_native_proxy(tex); js_proxy_t* p = jsb_get_native_proxy(tex);
valArr[1] = OBJECT_TO_JSVAL(p->obj); if (!p)
}
else if (image->initWithImageData(_buffer, _size))
{ {
tex = Director::getInstance()->getTextureCache()->addImage(image, _url); JS::RootedObject texProto(_cx, jsb_cocos2d_Texture2D_prototype);
valArr[0] = BOOLEAN_TO_JSVAL(true); JSObject *obj = JS_NewObject(_cx, jsb_cocos2d_Texture2D_class, texProto, global);
JS::RootedObject texProto(cx, jsb_cocos2d_Texture2D_prototype);
JSObject *obj = JS_NewObject(cx, jsb_cocos2d_Texture2D_class, texProto, global);
// link the native object with the javascript object // link the native object with the javascript object
js_proxy_t* p = jsb_new_proxy(tex, obj); p = jsb_new_proxy(tex, obj);
JS::AddNamedObjectRoot(cx, &p->obj, "cocos2d::Texture2D"); JS::AddNamedObjectRoot(_cx, &p->obj, "cocos2d::Texture2D");
}
valArr[1] = OBJECT_TO_JSVAL(p->obj); valArr[1] = OBJECT_TO_JSVAL(p->obj);
} }
else else
@ -1000,21 +1034,14 @@ void __JSDownloaderDelegator::onSuccess(const std::string &srcUrl, const std::st
valArr[1] = JSVAL_NULL; valArr[1] = JSVAL_NULL;
} }
image->release(); JS::RootedValue callback(_cx, OBJECT_TO_JSVAL(_jsCallback.ref()));
if (!callback.isNull())
if (!_jsCallback.ref().isNull()) { {
JS::RootedValue retval(cx); JS::RootedValue retval(_cx);
JS_CallFunctionValue(cx, global, _jsCallback.ref(), JS::HandleValueArray::fromMarkedLocation(2, valArr), &retval); JS_CallFunctionValue(_cx, global, callback, JS::HandleValueArray::fromMarkedLocation(2, valArr), &retval);
} }
this->release(); release();
}
void __JSDownloaderDelegator::download(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback)
{
auto t = std::thread([cx, obj, url, callback]() {
new __JSDownloaderDelegator(cx, obj, url, callback);
}); });
t.detach();
} }
// jsb.loadRemoteImg(url, function(succeed, result) {}) // jsb.loadRemoteImg(url, function(succeed, result) {})
@ -1022,14 +1049,15 @@ bool js_load_remote_image(JSContext *cx, uint32_t argc, jsval *vp)
{ {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
if (argc == 2) { if (argc == 2)
{
std::string url; std::string url;
bool ok = jsval_to_std_string(cx, args.get(0), &url); bool ok = jsval_to_std_string(cx, args.get(0), &url);
JS::RootedValue callback(cx, args.get(1)); JSB_PRECONDITION2(ok, cx, false, "js_load_remote_image : Error processing arguments");
JS::RootedObject callback(cx, args.get(1).toObjectOrNull());
__JSDownloaderDelegator::download(cx, obj, url, callback); __JSDownloaderDelegator *delegate = __JSDownloaderDelegator::create(cx, obj, url, callback);
delegate->downloadAsync();
JSB_PRECONDITION2(ok, cx, false, "js_console_log : Error processing arguments");
args.rval().setUndefined(); args.rval().setUndefined();
return true; return true;

View File

@ -32,12 +32,17 @@
class __JSDownloaderDelegator : cocos2d::Ref class __JSDownloaderDelegator : cocos2d::Ref
{ {
public: public:
static void download(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback); void downloadAsync();
void download();
static __JSDownloaderDelegator *create(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback);
protected: protected:
__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback); __JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback);
~__JSDownloaderDelegator(); ~__JSDownloaderDelegator();
void startDownload();
private: private:
void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId); void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId);
void onError(const cocos2d::extension::Downloader::Error &error); void onError(const cocos2d::extension::Downloader::Error &error);
@ -46,8 +51,8 @@ private:
std::shared_ptr<cocos2d::extension::Downloader> _downloader; std::shared_ptr<cocos2d::extension::Downloader> _downloader;
std::string _url; std::string _url;
JSContext *_cx; JSContext *_cx;
mozilla::Maybe<JS::RootedValue> _jsCallback; mozilla::Maybe<JS::PersistentRootedObject> _jsCallback;
mozilla::Maybe<JS::RootedObject> _obj; mozilla::Maybe<JS::PersistentRootedObject> _obj;
}; };
void register_all_cocos2dx_extension_manual(JSContext* cx, JS::HandleObject global); void register_all_cocos2dx_extension_manual(JSContext* cx, JS::HandleObject global);

View File

@ -332,6 +332,11 @@ long Downloader::getContentSize(const std::string &srcUrl)
return info.contentSize; return info.contentSize;
} }
Downloader::HeaderInfo Downloader::getHeader(const std::string &srcUrl)
{
return prepareHeader(srcUrl);
}
void Downloader::getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback) void Downloader::getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback)
{ {
setHeaderCallback(callback); setHeaderCallback(callback);
@ -412,13 +417,11 @@ void Downloader::downloadToBuffer(const std::string &srcUrl, const std::string &
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) if (res != CURLE_OK)
{ {
_fileUtils->removeFile(data.path + data.name + TEMP_EXT); std::string msg = StringUtils::format("Unable to download file to buffer: [curl error]%s", curl_easy_strerror(res));
std::string msg = StringUtils::format("Unable to download file: [curl error]%s", curl_easy_strerror(res));
this->notifyError(msg, customId, res); this->notifyError(msg, customId, res);
} }
else
curl_easy_cleanup(curl); {
Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]{ Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]{
if (!ptr.expired()) if (!ptr.expired())
{ {
@ -431,6 +434,9 @@ void Downloader::downloadToBuffer(const std::string &srcUrl, const std::string &
} }
} }
}); });
}
curl_easy_cleanup(curl);
} }
void Downloader::downloadAsync(const std::string &srcUrl, const std::string &storagePath, const std::string &customId/* = ""*/) void Downloader::downloadAsync(const std::string &srcUrl, const std::string &storagePath, const std::string &customId/* = ""*/)

View File

@ -107,7 +107,7 @@ public:
std::string url; std::string url;
std::string contentType; std::string contentType;
double contentSize; double contentSize;
double responseCode; long responseCode;
}; };
typedef std::unordered_map<std::string, DownloadUnit> DownloadUnits; typedef std::unordered_map<std::string, DownloadUnit> DownloadUnits;
@ -139,6 +139,8 @@ public:
long getContentSize(const std::string &srcUrl); long getContentSize(const std::string &srcUrl);
HeaderInfo getHeader(const std::string &srcUrl);
void getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback); void getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback);
void downloadToBufferAsync(const std::string &srcUrl, unsigned char *buffer, const long &size, const std::string &customId = ""); void downloadToBufferAsync(const std::string &srcUrl, unsigned char *buffer, const long &size, const std::string &customId = "");

View File

@ -192,7 +192,6 @@ var RemoteTextureTest = TextureCacheTestBase.extend({
_title:"Remote Texture Test", _title:"Remote Texture Test",
_subtitle:"", _subtitle:"",
_remoteTex: "http://cn.cocos2d-x.org/image/logo.png", _remoteTex: "http://cn.cocos2d-x.org/image/logo.png",
_sprite : null,
onEnter:function () { onEnter:function () {
this._super(); this._super();
if('opengl' in cc.sys.capabilities && !cc.sys.isNative){ if('opengl' in cc.sys.capabilities && !cc.sys.isNative){
@ -205,19 +204,21 @@ var RemoteTextureTest = TextureCacheTestBase.extend({
}, },
startDownload: function() { startDownload: function() {
cc.textureCache.addImageAsync(this._remoteTex, this.texLoaded, this); var imageUrlArray = ["http://www.cocos2d-x.org/s/upload/v35.jpg", "http://www.cocos2d-x.org/s/upload/testin.jpg", "http://www.cocos2d-x.org/s/upload/geometry_dash.jpg", "http://cn.cocos2d-x.org/image/logo.png"];
for (var i = 0; i < imageUrlArray.length; i++) {
cc.textureCache.addImageAsync(imageUrlArray[i], this.texLoaded, this);
}
}, },
texLoaded: function(texture) { texLoaded: function(texture) {
if (texture instanceof cc.Texture2D) { if (texture instanceof cc.Texture2D) {
cc.log("Remote texture loaded: " + this._remoteTex); cc.log("Remote texture loaded");
if (this._sprite) {
this.removeChild(this._sprite); var sprite = new cc.Sprite(texture);
} sprite.x = cc.winSize.width/2;
this._sprite = new cc.Sprite(texture); sprite.y = cc.winSize.height/2;
this._sprite.x = cc.winSize.width/2; this.addChild(sprite);
this._sprite.y = cc.winSize.height/2;
this.addChild(this._sprite);
} }
else { else {
cc.log("Fail to load remote texture"); cc.log("Fail to load remote texture");