Skip to content

Commit fdec552

Browse files
committed
Fix cocos2d/cocos2d-js#1658: Fix js_load_remote_image issue with multithread and improve Downloader
1 parent a2fc7a8 commit fdec552

File tree

5 files changed

+135
-93
lines changed

5 files changed

+135
-93
lines changed

cocos/scripting/js-bindings/manual/extension/jsb_cocos2dx_extension_manual.cpp

Lines changed: 91 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ bool js_cocos2dx_ext_release(JSContext *cx, uint32_t argc, jsval *vp)
908908
}
909909

910910

911-
__JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback)
911+
__JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback)
912912
: _cx(cx)
913913
, _url(url)
914914
, _buffer(nullptr)
@@ -917,7 +917,27 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
917917
_obj.ref().set(obj);
918918
_jsCallback.construct(_cx);
919919
_jsCallback.ref().set(callback);
920-
920+
}
921+
922+
__JSDownloaderDelegator::~__JSDownloaderDelegator()
923+
{
924+
_obj.destroyIfConstructed();
925+
_jsCallback.destroyIfConstructed();
926+
if (_buffer != nullptr)
927+
free(_buffer);
928+
_downloader->setErrorCallback(nullptr);
929+
_downloader->setSuccessCallback(nullptr);
930+
}
931+
932+
__JSDownloaderDelegator *__JSDownloaderDelegator::create(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback)
933+
{
934+
__JSDownloaderDelegator *delegate = new (std::nothrow) __JSDownloaderDelegator(cx, obj, url, callback);
935+
delegate->autorelease();
936+
return delegate;
937+
}
938+
939+
void __JSDownloaderDelegator::startDownload()
940+
{
921941
if (Director::getInstance()->getTextureCache()->getTextureForKey(_url))
922942
{
923943
onSuccess(nullptr, nullptr, nullptr);
@@ -929,8 +949,9 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
929949
_downloader->setErrorCallback( std::bind(&__JSDownloaderDelegator::onError, this, std::placeholders::_1) );
930950
_downloader->setSuccessCallback( std::bind(&__JSDownloaderDelegator::onSuccess, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) );
931951

932-
long contentSize = _downloader->getContentSize(_url);
933-
if (contentSize == -1) {
952+
cocos2d::extension::Downloader::HeaderInfo info = _downloader->getHeader(_url);
953+
long contentSize = info.contentSize;
954+
if (contentSize == -1 || info.responseCode >= 400) {
934955
cocos2d::extension::Downloader::Error err;
935956
onError(err);
936957
}
@@ -942,94 +963,101 @@ __JSDownloaderDelegator::__JSDownloaderDelegator(JSContext *cx, JS::HandleObject
942963
}
943964
}
944965

945-
__JSDownloaderDelegator::~__JSDownloaderDelegator()
966+
void __JSDownloaderDelegator::download()
946967
{
947-
if (_buffer != nullptr)
948-
free(_buffer);
949-
_downloader->setErrorCallback(nullptr);
950-
_downloader->setSuccessCallback(nullptr);
968+
retain();
969+
startDownload();
970+
}
971+
972+
void __JSDownloaderDelegator::downloadAsync()
973+
{
974+
retain();
975+
auto t = std::thread(&__JSDownloaderDelegator::startDownload, this);
976+
t.detach();
951977
}
952978

953979
void __JSDownloaderDelegator::onError(const cocos2d::extension::Downloader::Error &error)
954980
{
955-
if (!_jsCallback.ref().isNull()) {
956-
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext();
957-
JS::RootedObject global(cx, ScriptingCore::getInstance()->getGlobalObject());
958-
959-
JSAutoCompartment ac(_cx, _obj.ref());
960-
961-
jsval succeed = BOOLEAN_TO_JSVAL(false);
962-
JS::RootedValue retval(cx);
963-
JS_CallFunctionValue(cx, global, _jsCallback.ref(), JS::HandleValueArray::fromMarkedLocation(1, &succeed), &retval);
964-
}
965-
this->release();
981+
Director::getInstance()->getScheduler()->performFunctionInCocosThread([this]
982+
{
983+
JS::RootedValue callback(_cx, OBJECT_TO_JSVAL(_jsCallback.ref()));
984+
if (!callback.isNull()) {
985+
JS::RootedObject global(_cx, ScriptingCore::getInstance()->getGlobalObject());
986+
JSAutoCompartment ac(_cx, global);
987+
988+
jsval succeed = BOOLEAN_TO_JSVAL(false);
989+
JS::RootedValue retval(_cx);
990+
JS_CallFunctionValue(_cx, global, callback, JS::HandleValueArray::fromMarkedLocation(1, &succeed), &retval);
991+
}
992+
release();
993+
});
966994
}
967995

968996
void __JSDownloaderDelegator::onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId)
969997
{
970998
Image *image = new Image();
971-
jsval valArr[2];
972-
JSContext *cx = ScriptingCore::getInstance()->getGlobalContext();
973-
JS::RootedObject global(cx, ScriptingCore::getInstance()->getGlobalObject());
974999
cocos2d::TextureCache *cache = Director::getInstance()->getTextureCache();
9751000

976-
JSAutoCompartment ac(_cx, _obj.ref() ? _obj.ref() : global);
977-
9781001
Texture2D *tex = cache->getTextureForKey(_url);
979-
if (tex)
1002+
if (!tex)
9801003
{
981-
valArr[0] = BOOLEAN_TO_JSVAL(true);
982-
js_proxy_t* p = jsb_get_native_proxy(tex);
983-
valArr[1] = OBJECT_TO_JSVAL(p->obj);
1004+
if (image->initWithImageData(_buffer, _size))
1005+
{
1006+
tex = Director::getInstance()->getTextureCache()->addImage(image, _url);
1007+
}
9841008
}
985-
else if (image->initWithImageData(_buffer, _size))
1009+
image->release();
1010+
1011+
Director::getInstance()->getScheduler()->performFunctionInCocosThread([this, tex]
9861012
{
987-
tex = Director::getInstance()->getTextureCache()->addImage(image, _url);
988-
valArr[0] = BOOLEAN_TO_JSVAL(true);
1013+
JS::RootedObject global(_cx, ScriptingCore::getInstance()->getGlobalObject());
1014+
JSAutoCompartment ac(_cx, global);
9891015

990-
JS::RootedObject texProto(cx, jsb_cocos2d_Texture2D_prototype);
991-
JSObject *obj = JS_NewObject(cx, jsb_cocos2d_Texture2D_class, texProto, global);
992-
// link the native object with the javascript object
993-
js_proxy_t* p = jsb_new_proxy(tex, obj);
994-
JS::AddNamedObjectRoot(cx, &p->obj, "cocos2d::Texture2D");
995-
valArr[1] = OBJECT_TO_JSVAL(p->obj);
996-
}
997-
else
998-
{
999-
valArr[0] = BOOLEAN_TO_JSVAL(false);
1000-
valArr[1] = JSVAL_NULL;
1001-
}
1002-
1003-
image->release();
1016+
jsval valArr[2];
1017+
if (tex)
1018+
{
1019+
valArr[0] = BOOLEAN_TO_JSVAL(true);
1020+
js_proxy_t* p = jsb_get_native_proxy(tex);
1021+
if (!p)
1022+
{
1023+
JS::RootedObject texProto(_cx, jsb_cocos2d_Texture2D_prototype);
1024+
JSObject *obj = JS_NewObject(_cx, jsb_cocos2d_Texture2D_class, texProto, global);
1025+
// link the native object with the javascript object
1026+
p = jsb_new_proxy(tex, obj);
1027+
JS::AddNamedObjectRoot(_cx, &p->obj, "cocos2d::Texture2D");
1028+
}
1029+
valArr[1] = OBJECT_TO_JSVAL(p->obj);
1030+
}
1031+
else
1032+
{
1033+
valArr[0] = BOOLEAN_TO_JSVAL(false);
1034+
valArr[1] = JSVAL_NULL;
1035+
}
10041036

1005-
if (!_jsCallback.ref().isNull()) {
1006-
JS::RootedValue retval(cx);
1007-
JS_CallFunctionValue(cx, global, _jsCallback.ref(), JS::HandleValueArray::fromMarkedLocation(2, valArr), &retval);
1008-
}
1009-
this->release();
1010-
}
1011-
1012-
void __JSDownloaderDelegator::download(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback)
1013-
{
1014-
auto t = std::thread([cx, obj, url, callback]() {
1015-
new __JSDownloaderDelegator(cx, obj, url, callback);
1037+
JS::RootedValue callback(_cx, OBJECT_TO_JSVAL(_jsCallback.ref()));
1038+
if (!callback.isNull())
1039+
{
1040+
JS::RootedValue retval(_cx);
1041+
JS_CallFunctionValue(_cx, global, callback, JS::HandleValueArray::fromMarkedLocation(2, valArr), &retval);
1042+
}
1043+
release();
10161044
});
1017-
t.detach();
10181045
}
10191046

10201047
// jsb.loadRemoteImg(url, function(succeed, result) {})
10211048
bool js_load_remote_image(JSContext *cx, uint32_t argc, jsval *vp)
10221049
{
10231050
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
10241051
JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
1025-
if (argc == 2) {
1052+
if (argc == 2)
1053+
{
10261054
std::string url;
10271055
bool ok = jsval_to_std_string(cx, args.get(0), &url);
1028-
JS::RootedValue callback(cx, args.get(1));
1029-
1030-
__JSDownloaderDelegator::download(cx, obj, url, callback);
1056+
JSB_PRECONDITION2(ok, cx, false, "js_load_remote_image : Error processing arguments");
1057+
JS::RootedObject callback(cx, args.get(1).toObjectOrNull());
10311058

1032-
JSB_PRECONDITION2(ok, cx, false, "js_console_log : Error processing arguments");
1059+
__JSDownloaderDelegator *delegate = __JSDownloaderDelegator::create(cx, obj, url, callback);
1060+
delegate->downloadAsync();
10331061

10341062
args.rval().setUndefined();
10351063
return true;

cocos/scripting/js-bindings/manual/extension/jsb_cocos2dx_extension_manual.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@
3232
class __JSDownloaderDelegator : cocos2d::Ref
3333
{
3434
public:
35-
static void download(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback);
35+
void downloadAsync();
36+
void download();
3637

38+
static __JSDownloaderDelegator *create(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback);
39+
3740
protected:
38-
__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleValue callback);
41+
__JSDownloaderDelegator(JSContext *cx, JS::HandleObject obj, const std::string &url, JS::HandleObject callback);
3942
~__JSDownloaderDelegator();
4043

44+
void startDownload();
45+
4146
private:
4247
void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId);
4348
void onError(const cocos2d::extension::Downloader::Error &error);
@@ -46,8 +51,8 @@ class __JSDownloaderDelegator : cocos2d::Ref
4651
std::shared_ptr<cocos2d::extension::Downloader> _downloader;
4752
std::string _url;
4853
JSContext *_cx;
49-
mozilla::Maybe<JS::RootedValue> _jsCallback;
50-
mozilla::Maybe<JS::RootedObject> _obj;
54+
mozilla::Maybe<JS::PersistentRootedObject> _jsCallback;
55+
mozilla::Maybe<JS::PersistentRootedObject> _obj;
5156
};
5257

5358
void register_all_cocos2dx_extension_manual(JSContext* cx, JS::HandleObject global);

extensions/assets-manager/Downloader.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ long Downloader::getContentSize(const std::string &srcUrl)
332332
return info.contentSize;
333333
}
334334

335+
Downloader::HeaderInfo Downloader::getHeader(const std::string &srcUrl)
336+
{
337+
return prepareHeader(srcUrl);
338+
}
339+
335340
void Downloader::getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback)
336341
{
337342
setHeaderCallback(callback);
@@ -412,25 +417,26 @@ void Downloader::downloadToBuffer(const std::string &srcUrl, const std::string &
412417
CURLcode res = curl_easy_perform(curl);
413418
if (res != CURLE_OK)
414419
{
415-
_fileUtils->removeFile(data.path + data.name + TEMP_EXT);
416-
std::string msg = StringUtils::format("Unable to download file: [curl error]%s", curl_easy_strerror(res));
420+
std::string msg = StringUtils::format("Unable to download file to buffer: [curl error]%s", curl_easy_strerror(res));
417421
this->notifyError(msg, customId, res);
418422
}
419-
420-
curl_easy_cleanup(curl);
421-
422-
Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]{
423-
if (!ptr.expired())
424-
{
425-
std::shared_ptr<Downloader> downloader = ptr.lock();
426-
427-
auto successCB = downloader->getSuccessCallback();
428-
if (successCB != nullptr)
423+
else
424+
{
425+
Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]{
426+
if (!ptr.expired())
429427
{
430-
successCB(data.url, "", data.customId);
428+
std::shared_ptr<Downloader> downloader = ptr.lock();
429+
430+
auto successCB = downloader->getSuccessCallback();
431+
if (successCB != nullptr)
432+
{
433+
successCB(data.url, "", data.customId);
434+
}
431435
}
432-
}
433-
});
436+
});
437+
}
438+
439+
curl_easy_cleanup(curl);
434440
}
435441

436442
void Downloader::downloadAsync(const std::string &srcUrl, const std::string &storagePath, const std::string &customId/* = ""*/)

extensions/assets-manager/Downloader.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class CC_EX_DLL Downloader : public std::enable_shared_from_this<Downloader>
107107
std::string url;
108108
std::string contentType;
109109
double contentSize;
110-
double responseCode;
110+
long responseCode;
111111
};
112112

113113
typedef std::unordered_map<std::string, DownloadUnit> DownloadUnits;
@@ -139,6 +139,8 @@ class CC_EX_DLL Downloader : public std::enable_shared_from_this<Downloader>
139139

140140
long getContentSize(const std::string &srcUrl);
141141

142+
HeaderInfo getHeader(const std::string &srcUrl);
143+
142144
void getHeaderAsync(const std::string &srcUrl, const HeaderCallback &callback);
143145

144146
void downloadToBufferAsync(const std::string &srcUrl, unsigned char *buffer, const long &size, const std::string &customId = "");

tests/js-tests/src/TextureCacheTest/TextureCacheTest.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ var RemoteTextureTest = TextureCacheTestBase.extend({
192192
_title:"Remote Texture Test",
193193
_subtitle:"",
194194
_remoteTex: "http://cn.cocos2d-x.org/image/logo.png",
195-
_sprite : null,
196195
onEnter:function () {
197196
this._super();
198197
if('opengl' in cc.sys.capabilities && !cc.sys.isNative){
@@ -205,19 +204,21 @@ var RemoteTextureTest = TextureCacheTestBase.extend({
205204
},
206205

207206
startDownload: function() {
208-
cc.textureCache.addImageAsync(this._remoteTex, this.texLoaded, this);
207+
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"];
208+
209+
for (var i = 0; i < imageUrlArray.length; i++) {
210+
cc.textureCache.addImageAsync(imageUrlArray[i], this.texLoaded, this);
211+
}
209212
},
210213

211214
texLoaded: function(texture) {
212215
if (texture instanceof cc.Texture2D) {
213-
cc.log("Remote texture loaded: " + this._remoteTex);
214-
if (this._sprite) {
215-
this.removeChild(this._sprite);
216-
}
217-
this._sprite = new cc.Sprite(texture);
218-
this._sprite.x = cc.winSize.width/2;
219-
this._sprite.y = cc.winSize.height/2;
220-
this.addChild(this._sprite);
216+
cc.log("Remote texture loaded");
217+
218+
var sprite = new cc.Sprite(texture);
219+
sprite.x = cc.winSize.width/2;
220+
sprite.y = cc.winSize.height/2;
221+
this.addChild(sprite);
221222
}
222223
else {
223224
cc.log("Fail to load remote texture");

0 commit comments

Comments
 (0)