From 146bbab36e7211d7b0e45c9106b89913f72e7196 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:03:02 +0800 Subject: [PATCH 1/9] fixed #17012: Loading assets synchronously causes rendering to freeze. --- cocos/native-binding/index.ts | 25 ++++ native/cocos/base/Scheduler.cpp | 54 +++++--- native/cocos/base/Scheduler.h | 2 + native/cocos/base/UTF8.cpp | 30 ++++ native/cocos/bindings/jswrapper/v8/Object.cpp | 16 +++ native/cocos/bindings/jswrapper/v8/Object.h | 1 + native/cocos/bindings/jswrapper/v8/Utils.h | 21 +++ .../bindings/manual/jsb_cocos_manual.cpp | 130 +++++++++++++++++- native/cocos/bindings/manual/jsb_global.cpp | 15 +- native/cocos/bindings/manual/jsb_global.h | 5 + .../bindings/manual/jsb_scene_manual.cpp | 14 +- .../platforms/xiaomi/wrapper/fs-utils.js | 1 + platforms/native/engine/jsb-fs-utils.js | 35 +++-- platforms/runtime/common/engine/fs-utils.js | 1 + 14 files changed, 299 insertions(+), 51 deletions(-) diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index 5a64c27d01a..1b1ff633089 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -815,6 +815,31 @@ export declare namespace native { * */ export function getStringFromFile(filename: string): string; + + /** + * @en Read utf-8 text file asynchronously. + * @zh 异步读取 utf-8 编码的文本文件 + * @param filepath @en The file path. @zh 文件路径 + * @param onComplete @en The complete callback. @zh 读取完成回调 + */ + export function readTextFile(filepath: string, onComplete: (err: string | null, content: string) => void): void; + + /** + * @en Read utf-8 json file asynchronously. + * @zh 异步读取 utf-8 编码的 json 文件 + * @param filepath @en The file path. @zh 文件路径 + * @param onComplete @en The complete callback. @zh 读取完成回调 + */ + export function readJsonFile(filepath: string, onComplete: (err: string | null, content: object) => void): void; + + /** + * @en Read binary file asynchronously. + * @zh 异步读取二进制文件 + * @param filepath @en The file path. @zh 文件路径 + * @param onComplete @en The complete callback. @zh 读取完成回调 + */ + export function readDataFile(filepath: string, onComplete: (err: string | null, content: ArrayBuffer) => void): void; + /** * @en * Removes a file. diff --git a/native/cocos/base/Scheduler.cpp b/native/cocos/base/Scheduler.cpp index 074e9c60fbc..402fb7608ee 100644 --- a/native/cocos/base/Scheduler.cpp +++ b/native/cocos/base/Scheduler.cpp @@ -332,6 +332,43 @@ void Scheduler::removeAllFunctionsToBePerformedInCocosThread() { _functionsToPerform.clear(); } +void Scheduler::runFunctionsToBePerformedInCocosThread() { + // + // Functions allocated from another thread + // + auto beginTime = std::chrono::steady_clock::now(); + auto nowTime = beginTime; + bool doJob = true; + + // Testing size is faster than locking / unlocking. + // And almost never there will be functions scheduled to be called. + if (!_functionsToPerform.empty()) { + _performMutex.lock(); + // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock. + auto temp = std::move(_functionsToPerform); + _performMutex.unlock(); + + for (auto &&function : temp) { + if (doJob) { + nowTime = std::chrono::steady_clock::now(); + auto passedMS = std::chrono::duration_cast(nowTime - beginTime).count(); + // If the callbacks takes more than 16ms, delay the remaining jobs to next frame. + if (passedMS > 16) { + doJob = false; + } + } + + if (doJob) { + function(); + } else { + _performMutex.lock(); + _functionsToPerform.emplace_back(std::move(function)); + _performMutex.unlock(); + } + } + } +} + // main loop void Scheduler::update(float dt) { _updateHashLocked = true; @@ -377,22 +414,7 @@ void Scheduler::update(float dt) { _updateHashLocked = false; _currentTarget = nullptr; - // - // Functions allocated from another thread - // - - // Testing size is faster than locking / unlocking. - // And almost never there will be functions scheduled to be called. - if (!_functionsToPerform.empty()) { - _performMutex.lock(); - // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock. - auto temp = _functionsToPerform; - _functionsToPerform.clear(); - _performMutex.unlock(); - for (const auto &function : temp) { - function(); - } - } + runFunctionsToBePerformedInCocosThread(); } } // namespace cc diff --git a/native/cocos/base/Scheduler.h b/native/cocos/base/Scheduler.h index 1bdaea8c84b..35421ce96a6 100644 --- a/native/cocos/base/Scheduler.h +++ b/native/cocos/base/Scheduler.h @@ -268,6 +268,8 @@ class CC_DLL Scheduler final { * @js NA */ void removeAllFunctionsToBePerformedInCocosThread(); + + void runFunctionsToBePerformedInCocosThread(); bool isCurrentTargetSalvaged() const { return _currentTargetSalvaged; }; diff --git a/native/cocos/base/UTF8.cpp b/native/cocos/base/UTF8.cpp index 5e2feac8178..ccb88256459 100644 --- a/native/cocos/base/UTF8.cpp +++ b/native/cocos/base/UTF8.cpp @@ -24,12 +24,20 @@ THE SOFTWARE. ****************************************************************************/ +#define CC_USE_SIMD_UTF 1 + #include "base/UTF8.h" #include #include #include "ConvertUTF/ConvertUTF.h" + +#if CC_USE_SIMD_UTF +#include "simdutf/simdutf.cpp" +#include "simdutf/simdutf.h" +#endif + #include "base/Log.h" namespace cc { @@ -211,7 +219,29 @@ CC_DLL void UTF8LooseFix(const ccstd::string &in, ccstd::string &out) { //NOLINT } bool UTF8ToUTF16(const ccstd::string &utf8, std::u16string &outUtf16) { //NOLINT +#if CC_USE_SIMD_UTF + bool validutf8 = simdutf::validate_utf8(utf8.c_str(), utf8.length()); + if (!validutf8) { + outUtf16.clear(); + return false; + } + + // We need a buffer of size where to write the UTF-16LE words. + size_t expected_utf16words = simdutf::utf16_length_from_utf8(utf8.c_str(), utf8.length()); + outUtf16.resize(expected_utf16words); + + // convert to UTF-16LE + size_t utf16words = simdutf::convert_utf8_to_utf16le(utf8.c_str(), utf8.length(), outUtf16.data()); + bool validutf16 = simdutf::validate_utf16le(outUtf16.c_str(), utf16words); + if (!validutf16) { + outUtf16.clear(); + return false; + } + + return true; +#else return utfConvert(utf8, outUtf16, ConvertUTF8toUTF16); +#endif } bool UTF8ToUTF32(const ccstd::string &utf8, std::u32string &outUtf32) { //NOLINT diff --git a/native/cocos/bindings/jswrapper/v8/Object.cpp b/native/cocos/bindings/jswrapper/v8/Object.cpp index 870d666e0c3..0bc93281fe1 100644 --- a/native/cocos/bindings/jswrapper/v8/Object.cpp +++ b/native/cocos/bindings/jswrapper/v8/Object.cpp @@ -400,6 +400,22 @@ Object *Object::createJSONObject(const ccstd::string &jsonStr) { return Object::_createJSObject(nullptr, jsobj); } +Object *Object::createJSONObject(std::u16string &jsonStr) { + auto v8Str = v8::String::NewExternalTwoByte(__isolate, ccnew internal::ExternalStringResource(jsonStr)); + if (v8Str.IsEmpty()) { + return nullptr; + } + + v8::Local context = __isolate->GetCurrentContext(); + v8::MaybeLocal ret = v8::JSON::Parse(context, v8Str.ToLocalChecked()); + if (ret.IsEmpty()) { + return nullptr; + } + + v8::Local jsobj = v8::Local::Cast(ret.ToLocalChecked()); + return Object::_createJSObject(nullptr, jsobj); +} + bool Object::init(Class *cls, v8::Local obj) { _cls = cls; diff --git a/native/cocos/bindings/jswrapper/v8/Object.h b/native/cocos/bindings/jswrapper/v8/Object.h index a35d70525b5..79f903edd71 100644 --- a/native/cocos/bindings/jswrapper/v8/Object.h +++ b/native/cocos/bindings/jswrapper/v8/Object.h @@ -152,6 +152,7 @@ class Object final : public RefCounter { * @note The return value (non-null) has to be released manually. */ static Object *createJSONObject(const ccstd::string &jsonStr); + static Object *createJSONObject(std::u16string &jsonStr); /** * @brief Creates a JavaScript Native Binding Object from an existing se::Class instance. diff --git a/native/cocos/bindings/jswrapper/v8/Utils.h b/native/cocos/bindings/jswrapper/v8/Utils.h index dcd75915583..bd2ad042622 100644 --- a/native/cocos/bindings/jswrapper/v8/Utils.h +++ b/native/cocos/bindings/jswrapper/v8/Utils.h @@ -50,6 +50,27 @@ void setPrivate(v8::Isolate *isolate, ObjectWrap &wrap, Object *obj); Object *getPrivate(v8::Isolate *isolate, v8::Local value); void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap); +class ExternalStringResource : public v8::String::ExternalStringResource { +public: + ExternalStringResource(std::u16string &s) + : _s(std::move(s)) { + + } + + ~ExternalStringResource() { + } + + const uint16_t* data() const override { + return reinterpret_cast(_s.data()); + } + + size_t length() const override { + return _s.length(); + } +private: + std::u16string _s; +}; + } // namespace internal } // namespace se diff --git a/native/cocos/bindings/manual/jsb_cocos_manual.cpp b/native/cocos/bindings/manual/jsb_cocos_manual.cpp index 6e827c0ac25..a62acf524ec 100644 --- a/native/cocos/bindings/manual/jsb_cocos_manual.cpp +++ b/native/cocos/bindings/manual/jsb_cocos_manual.cpp @@ -24,11 +24,14 @@ #include "jsb_cocos_manual.h" +#include "base/ThreadPool.h" +#include "base/UTF8.h" + #include "bindings/manual/jsb_global.h" -#include "cocos/bindings/auto/jsb_cocos_auto.h" -#include "cocos/bindings/jswrapper/SeApi.h" -#include "cocos/bindings/manual/jsb_conversions.h" -#include "cocos/bindings/manual/jsb_global_init.h" +#include "bindings/auto/jsb_cocos_auto.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global_init.h" #include "application/ApplicationManager.h" #include "platform/interfaces/modules/ISystemWindowManager.h" @@ -672,8 +675,127 @@ static bool js_se_setExceptionCallback(se::State &s) { // NOLINT(readability-ide } SE_BIND_FUNC(js_se_setExceptionCallback) // NOLINT(readability-identifier-naming) +template +static bool js_readFile(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string path; + ok &= sevalue_to_native(args[0], &path); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + const auto &callbackVal = args[1]; + CC_ASSERT(callbackVal.isObject()); + CC_ASSERT(callbackVal.toObject()->isFunction()); + + + if (path.empty()) { + se::ValueArray seArgs; + seArgs.reserve(2); + seArgs.emplace_back(se::Value("Path is empty")); + seArgs.emplace_back(se::Value::Null); + callbackVal.toObject()->call(seArgs, nullptr); + return true; + } + + std::shared_ptr callbackPtr = std::make_shared(callbackVal); + + // fullPathForFilename is not threadsafe, so don't invoke it in thread pool. + ccstd::string fullPath = cc::FileUtils::getInstance()->fullPathForFilename(path); + + gIOThreadPool->pushTask([fullPath, callbackPtr](int tid) { + auto *fs = cc::FileUtils::getInstance(); + if (fs == nullptr) { + return; + } + + auto app = CC_CURRENT_APPLICATION(); + if (!app) { + return; + } + + auto content = std::make_shared(); + fs->getContents(fullPath, content.get()); + + auto engine = app->getEngine(); + if (!engine) { + return; + } + + // TODO(cjh): OpenHarmony NAPI support +#if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI + std::shared_ptr u16str; + if constexpr (std::is_same_v && isJson) { + u16str = std::make_shared(); + if (!cc::StringUtils::UTF8ToUTF16(*content, *u16str)) { + CC_LOG_ERROR("UTF8ToUTF16 failed, file: %s", fullPath.c_str()); + } + } +#endif + + engine->getScheduler()->performFunctionInCocosThread([callbackPtr, content, u16str](){ + se::AutoHandleScope hs; + se::ValueArray seArgs; + seArgs.reserve(2); + + if constexpr (std::is_same_v) { + if constexpr (isJson) { +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_NAPI + se::HandleObject jsonObj(se::Object::createJSONObject(*content)); +#else + se::HandleObject jsonObj(se::Object::createJSONObject(*u16str)); +#endif + if (!jsonObj.get()) { + seArgs.emplace_back(se::Value("Parse json failed!")); + seArgs.emplace_back(se::Value::Null); + } else { + seArgs.emplace_back(se::Value::Null); + seArgs.emplace_back(se::Value(jsonObj)); + } + callbackPtr->toObject()->call(seArgs, nullptr); + } else { + seArgs.emplace_back(se::Value::Null); + seArgs.emplace_back(se::Value(*content)); + callbackPtr->toObject()->call(seArgs, nullptr); + } + } else if constexpr (std::is_same_v) { + se::HandleObject dataObj(se::Object::createArrayBufferObject(content->getBytes(), content->getSize())); + seArgs.emplace_back(se::Value::Null); + seArgs.emplace_back(se::Value(dataObj)); + callbackPtr->toObject()->call(seArgs, nullptr); + } + }); + }); + + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} + +static bool js_readTextFile(se::State &s) { // NOLINT + return js_readFile(s); +} +SE_BIND_FUNC(js_readTextFile) + +static bool js_readDataFile(se::State &s) { // NOLINT + return js_readFile(s); +} +SE_BIND_FUNC(js_readDataFile) + +static bool js_readJsonFile(se::State &s) { // NOLINT + return js_readFile(s); +} +SE_BIND_FUNC(js_readJsonFile) + + static bool register_filetuils_ext(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) __jsb_cc_FileUtils_proto->defineFunction("listFilesRecursively", _SE(js_engine_FileUtils_listFilesRecursively)); + __jsb_cc_FileUtils_proto->defineFunction("readTextFile", _SE(js_readTextFile)); + __jsb_cc_FileUtils_proto->defineFunction("readDataFile", _SE(js_readDataFile)); + __jsb_cc_FileUtils_proto->defineFunction("readJsonFile", _SE(js_readJsonFile)); + return true; } diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index b282c8df932..488e30780a0 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -60,7 +60,7 @@ extern void jsb_register_ADPF(se::Object *); // NOLINT using namespace cc; // NOLINT -static LegacyThreadPool *gThreadPool = nullptr; +LegacyThreadPool *gIOThreadPool = nullptr; static std::shared_ptr gLocalDownloader = nullptr; static ccstd::unordered_map> gLocalDownloaderHandlers; @@ -590,7 +590,7 @@ bool jsb_global_load_image(const ccstd::string &path, const se::Value &callbackV auto initImageFunc = [path, callbackPtr](const ccstd::string &fullPath, unsigned char *imageData, int imageBytes) { auto *img = ccnew Image(); - gThreadPool->pushTask([=](int /*tid*/) { + gIOThreadPool->pushTask([=](int /*tid*/) { // NOTE: FileUtils::getInstance()->fullPathForFilename isn't a threadsafe method, // Image::initWithImageFile will call fullPathForFilename internally which may // cause thread race issues. Therefore, we get the full path of file before @@ -725,7 +725,7 @@ static bool js_saveImageData(se::State &s) { // NOLINT callbackObj->incRef(); } - gThreadPool->pushTask([=](int /*tid*/) { + gIOThreadPool->pushTask([=](int /*tid*/) { // isToRGB = false, to keep alpha channel auto *img = ccnew Image(); // A conversion from size_t to uint32_t might lose integer precision @@ -1526,7 +1526,7 @@ SE_BIND_FUNC(JSB_openharmony_postSyncMessage) #endif bool jsb_register_global_variables(se::Object *global) { // NOLINT - gThreadPool = LegacyThreadPool::newFixedThreadPool(3); + gIOThreadPool = LegacyThreadPool::newFixedThreadPool(5); #if CC_EDITOR global->defineFunction("__require", _SE(require)); @@ -1546,6 +1546,7 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT __jsbObj->defineFunction("copyTextToClipboard", _SE(JSB_copyTextToClipboard)); __jsbObj->defineFunction("setPreferredFramesPerSecond", _SE(JSB_setPreferredFramesPerSecond)); __jsbObj->defineFunction("destroyImage", _SE(js_destroyImage)); + #if CC_USE_EDITBOX __jsbObj->defineFunction("showInputBox", _SE(JSB_showInputBox)); __jsbObj->defineFunction("hideInputBox", _SE(JSB_hideInputBox)); @@ -1605,8 +1606,8 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT se::ScriptEngine::getInstance()->clearException(); se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { - delete gThreadPool; - gThreadPool = nullptr; + delete gIOThreadPool; + gIOThreadPool = nullptr; DeferredReleasePool::clear(); }); @@ -1619,6 +1620,6 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT SAFE_DEC_REF(__jsbObj); SAFE_DEC_REF(__glObj); }); - + return true; } diff --git a/native/cocos/bindings/manual/jsb_global.h b/native/cocos/bindings/manual/jsb_global.h index ca16a2e7b03..23bbb0846d4 100644 --- a/native/cocos/bindings/manual/jsb_global.h +++ b/native/cocos/bindings/manual/jsb_global.h @@ -27,6 +27,11 @@ #include "bindings/jswrapper/PrivateObject.h" #include "jsb_global_init.h" +namespace cc { + class LegacyThreadPool; +} +extern cc::LegacyThreadPool *gIOThreadPool; + template T *jsb_override_new(Args &&...args) { // NOLINT(readability-identifier-naming) // create object in the default way diff --git a/native/cocos/bindings/manual/jsb_scene_manual.cpp b/native/cocos/bindings/manual/jsb_scene_manual.cpp index bd5370b0c15..064817dbac6 100644 --- a/native/cocos/bindings/manual/jsb_scene_manual.cpp +++ b/native/cocos/bindings/manual/jsb_scene_manual.cpp @@ -28,6 +28,7 @@ #include "core/Root.h" #include "core/scene-graph/Node.h" #include "scene/Model.h" +#include "application/ApplicationManager.h" #ifndef JSB_ALLOC #define JSB_ALLOC(kls, ...) ccnew kls(__VA_ARGS__) @@ -115,7 +116,7 @@ static bool js_root_registerListeners(se::State &s) // NOLINT(readability-identi auto *cobj = SE_THIS_OBJECT(s); SE_PRECONDITION2(cobj, false, "Invalid Native Object"); -#define DISPATCH_EVENT_TO_JS_ARGS_0(eventType, jsFuncName) \ +#define DISPATCH_EVENT_TO_JS_ARGS_0(eventType, jsFuncName, postCode) \ cobj->on([](cc::Root *rootObj) { \ se::AutoHandleScope hs; \ se::Value rootVal; \ @@ -124,12 +125,15 @@ static bool js_root_registerListeners(se::State &s) // NOLINT(readability-identi if (rootVal.isObject()) { \ se::ScriptEngine::getInstance()->callFunction(rootVal.toObject(), #jsFuncName, 0, nullptr); \ } \ + postCode \ }); - DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeCommit, _onDirectorBeforeCommit); - DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeRender, _onDirectorBeforeRender); - DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::AfterRender, _onDirectorAfterRender); - DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::PipelineChanged, _onDirectorPipelineChanged); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeCommit, _onDirectorBeforeCommit, {}); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeRender, _onDirectorBeforeRender, {}); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::AfterRender, _onDirectorAfterRender, { + CC_CURRENT_APPLICATION()->getEngine()->getScheduler()->runFunctionsToBePerformedInCocosThread(); + }); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::PipelineChanged, _onDirectorPipelineChanged, {}); return true; } diff --git a/platforms/minigame/platforms/xiaomi/wrapper/fs-utils.js b/platforms/minigame/platforms/xiaomi/wrapper/fs-utils.js index ad71b7e228d..6f226b84d12 100644 --- a/platforms/minigame/platforms/xiaomi/wrapper/fs-utils.js +++ b/platforms/minigame/platforms/xiaomi/wrapper/fs-utils.js @@ -209,6 +209,7 @@ const fsUtils = { rmdirSync (dirPath, recursive) { try { fs.rmdirSync(dirPath, recursive); + return null; } catch (e) { console.warn(`rm directory failed: path: ${dirPath} message: ${e.message}`); return new Error(e.message); diff --git a/platforms/native/engine/jsb-fs-utils.js b/platforms/native/engine/jsb-fs-utils.js index f0d7ae1b4b6..cfd32680925 100644 --- a/platforms/native/engine/jsb-fs-utils.js +++ b/platforms/native/engine/jsb-fs-utils.js @@ -139,21 +139,21 @@ const fsUtils = { cc.warn(`Write file failed: path: ${path}`); return new Error(`Write file failed: path: ${path}`); } + return null; }, readFile (filePath, encoding, onComplete) { - let content = null; let err = null; if (encoding === 'utf-8' || encoding === 'utf8') { - content = fs.getStringFromFile(filePath); + fs.readTextFile(filePath, (err, content) => { + if (err) err = new Error(err); + onComplete && onComplete(err, content); + }); } else { - content = fs.getDataFromFile(filePath); + fs.readDataFile(filePath, (err, content) => { + if (err) err = new Error(err); + onComplete && onComplete(err, content); + }); } - if (!content) { - err = new Error(`Read file failed: path: ${filePath}`); - cc.warn(err.message); - } - - onComplete && onComplete(err, content); }, readDir (filePath, onComplete) { @@ -176,17 +176,12 @@ const fsUtils = { }, readJson (filePath, onComplete) { - fsUtils.readFile(filePath, 'utf8', (err, text) => { - let out = null; - if (!err) { - try { - out = JSON.parse(text); - } catch (e) { - cc.warn(`Read json failed: path: ${filePath} message: ${e.message}`); - err = new Error(e.message); - } + fs.readJsonFile(filePath, (err, jsonObj) => { + if (err) { + cc.warn(`Read json failed: path: ${filePath} message: ${err}`); + err = new Error(err); } - onComplete && onComplete(err, out); + onComplete && onComplete(err, jsonObj); }); }, @@ -206,6 +201,7 @@ const fsUtils = { cc.warn(`Make directory failed: path: ${path}`); return new Error(`Make directory failed: path: ${path}`); } + return null; }, rmdirSync (dirPath, recursive) { @@ -214,6 +210,7 @@ const fsUtils = { cc.warn(`rm directory failed: path: ${dirPath}`); return new Error(`rm directory failed: path: ${dirPath}`); } + return null; }, exists (filePath, onComplete) { diff --git a/platforms/runtime/common/engine/fs-utils.js b/platforms/runtime/common/engine/fs-utils.js index 6978bafb5fb..eeb8911a1f6 100644 --- a/platforms/runtime/common/engine/fs-utils.js +++ b/platforms/runtime/common/engine/fs-utils.js @@ -211,6 +211,7 @@ const fsUtils = { rmdirSync (dirPath, recursive) { try { fs.rmdirSync(dirPath, recursive); + return null; } catch (e) { console.warn(`rm directory failed: path: ${dirPath} message: ${e.message}`); return new Error(e.message); From 66083a70cc968c2c7d93215de0b4aa0b5bbd085d Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:27:58 +0800 Subject: [PATCH 2/9] Update external-config.json --- native/cocos/bindings/jswrapper/v8/Utils.cpp | 19 +++++++++++++++++-- native/external-config.json | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/native/cocos/bindings/jswrapper/v8/Utils.cpp b/native/cocos/bindings/jswrapper/v8/Utils.cpp index 6e5334253fa..1a2453f9074 100644 --- a/native/cocos/bindings/jswrapper/v8/Utils.cpp +++ b/native/cocos/bindings/jswrapper/v8/Utils.cpp @@ -33,11 +33,24 @@ #include "ScriptEngine.h" #include "base/Log.h" #include "base/Macros.h" + #include "base/UTF8.h" namespace se { namespace internal { +namespace { + +inline v8::MaybeLocal createV8StringFromUtf8(v8::Isolate* isolate, const ccstd::string &u8Str, v8::NewStringType type = v8::NewStringType::kNormal) { + std::u16string u16Str; + if (cc::StringUtils::UTF8ToUTF16(u8Str, u16Str)) { + return v8::String::NewFromTwoByte(isolate, reinterpret_cast(u16Str.data()), type, static_cast(u16Str.length())); + } + return {}; +} + +} // namespace { + void jsToSeArgs(const v8::FunctionCallbackInfo &v8args, ValueArray &outArr) { v8::Isolate *isolate = v8args.GetIsolate(); for (int i = 0; i < v8args.Length(); i++) { @@ -62,7 +75,8 @@ void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local *out *outJsVal = v8::Number::New(isolate, v.toDouble()); break; case Value::Type::String: { - v8::MaybeLocal str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast(v.toString().length())); +// v8::MaybeLocal str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast(v.toString().length())); + auto str = createV8StringFromUtf8(isolate, v.toString(), v8::NewStringType::kNormal); if (!str.IsEmpty()) { *outJsVal = str.ToLocalChecked(); } else { @@ -181,7 +195,8 @@ void setReturnValueTemplate(const Value &data, const T &argv) { break; } case Value::Type::String: { - v8::MaybeLocal value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal); +// v8::MaybeLocal value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal); + auto value = createV8StringFromUtf8(argv.GetIsolate(), data.toString(), v8::NewStringType::kNormal); CC_ASSERT(!value.IsEmpty()); argv.GetReturnValue().Set(value.ToLocalChecked()); break; diff --git a/native/external-config.json b/native/external-config.json index 45b7100d021..6fbc71bd2dd 100644 --- a/native/external-config.json +++ b/native/external-config.json @@ -3,6 +3,6 @@ "type": "github", "owner": "cocos-creator", "name": "engine-native-external", - "checkout": "v3.8.4-2" + "checkout": "v3.8.4-3" } } \ No newline at end of file From d2dbb0907ea19cd2c3161649f02accf725f70ee3 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:44:26 +0800 Subject: [PATCH 3/9] Update runFunctionsToBePerformedInCocosThread --- native/cocos/base/Scheduler.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/native/cocos/base/Scheduler.cpp b/native/cocos/base/Scheduler.cpp index 402fb7608ee..2d6319aaf8d 100644 --- a/native/cocos/base/Scheduler.cpp +++ b/native/cocos/base/Scheduler.cpp @@ -338,7 +338,6 @@ void Scheduler::runFunctionsToBePerformedInCocosThread() { // auto beginTime = std::chrono::steady_clock::now(); auto nowTime = beginTime; - bool doJob = true; // Testing size is faster than locking / unlocking. // And almost never there will be functions scheduled to be called. @@ -347,24 +346,22 @@ void Scheduler::runFunctionsToBePerformedInCocosThread() { // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock. auto temp = std::move(_functionsToPerform); _performMutex.unlock(); - - for (auto &&function : temp) { - if (doJob) { - nowTime = std::chrono::steady_clock::now(); - auto passedMS = std::chrono::duration_cast(nowTime - beginTime).count(); - // If the callbacks takes more than 16ms, delay the remaining jobs to next frame. - if (passedMS > 16) { - doJob = false; - } + + auto iter = temp.begin(); + for (; iter != temp.end(); ++iter) { + nowTime = std::chrono::steady_clock::now(); + auto passedMS = std::chrono::duration_cast(nowTime - beginTime).count(); + // If the callbacks takes more than 16ms, delay the remaining jobs to next frame. + if (passedMS > 16) { + break; } - if (doJob) { - function(); - } else { - _performMutex.lock(); - _functionsToPerform.emplace_back(std::move(function)); - _performMutex.unlock(); - } + (*iter)(); + } + + std::lock_guard lk(_performMutex); + for (; iter != temp.end(); ++iter) { + _functionsToPerform.emplace_back(std::move(*iter)); } } } From 6b9ec3e8f037197341ab2f8576b9b589c0fcf3d6 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:49:23 +0800 Subject: [PATCH 4/9] Dispose ExternalStringResource in exe space. --- native/cocos/bindings/jswrapper/v8/Utils.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/cocos/bindings/jswrapper/v8/Utils.h b/native/cocos/bindings/jswrapper/v8/Utils.h index bd2ad042622..09ac0c2aac9 100644 --- a/native/cocos/bindings/jswrapper/v8/Utils.h +++ b/native/cocos/bindings/jswrapper/v8/Utils.h @@ -67,6 +67,10 @@ class ExternalStringResource : public v8::String::ExternalStringResource { size_t length() const override { return _s.length(); } + + void Dispose() override { + delete this; + } private: std::u16string _s; }; From 03e42707d242f763066cfbfe73317c96f7b2df14 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:49:53 +0800 Subject: [PATCH 5/9] Revert --- native/cocos/bindings/jswrapper/v8/Utils.cpp | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/native/cocos/bindings/jswrapper/v8/Utils.cpp b/native/cocos/bindings/jswrapper/v8/Utils.cpp index 1a2453f9074..6e5334253fa 100644 --- a/native/cocos/bindings/jswrapper/v8/Utils.cpp +++ b/native/cocos/bindings/jswrapper/v8/Utils.cpp @@ -33,24 +33,11 @@ #include "ScriptEngine.h" #include "base/Log.h" #include "base/Macros.h" - #include "base/UTF8.h" namespace se { namespace internal { -namespace { - -inline v8::MaybeLocal createV8StringFromUtf8(v8::Isolate* isolate, const ccstd::string &u8Str, v8::NewStringType type = v8::NewStringType::kNormal) { - std::u16string u16Str; - if (cc::StringUtils::UTF8ToUTF16(u8Str, u16Str)) { - return v8::String::NewFromTwoByte(isolate, reinterpret_cast(u16Str.data()), type, static_cast(u16Str.length())); - } - return {}; -} - -} // namespace { - void jsToSeArgs(const v8::FunctionCallbackInfo &v8args, ValueArray &outArr) { v8::Isolate *isolate = v8args.GetIsolate(); for (int i = 0; i < v8args.Length(); i++) { @@ -75,8 +62,7 @@ void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local *out *outJsVal = v8::Number::New(isolate, v.toDouble()); break; case Value::Type::String: { -// v8::MaybeLocal str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast(v.toString().length())); - auto str = createV8StringFromUtf8(isolate, v.toString(), v8::NewStringType::kNormal); + v8::MaybeLocal str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast(v.toString().length())); if (!str.IsEmpty()) { *outJsVal = str.ToLocalChecked(); } else { @@ -195,8 +181,7 @@ void setReturnValueTemplate(const Value &data, const T &argv) { break; } case Value::Type::String: { -// v8::MaybeLocal value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal); - auto value = createV8StringFromUtf8(argv.GetIsolate(), data.toString(), v8::NewStringType::kNormal); + v8::MaybeLocal value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal); CC_ASSERT(!value.IsEmpty()); argv.GetReturnValue().Set(value.ToLocalChecked()); break; From fcc8993d31ebc59835d25d60666cdd9cbb84d8fa Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 18:54:45 +0800 Subject: [PATCH 6/9] Add comment for createJSONObject --- native/cocos/bindings/jswrapper/v8/Object.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/native/cocos/bindings/jswrapper/v8/Object.h b/native/cocos/bindings/jswrapper/v8/Object.h index 79f903edd71..03e5ba64e77 100644 --- a/native/cocos/bindings/jswrapper/v8/Object.h +++ b/native/cocos/bindings/jswrapper/v8/Object.h @@ -152,6 +152,13 @@ class Object final : public RefCounter { * @note The return value (non-null) has to be released manually. */ static Object *createJSONObject(const ccstd::string &jsonStr); + + /** + * @brief Creates a JavaScript Object from a JSON formatted string. + * @param[in] jsonStr The utf-16 string containing the JSON string to be parsed. + * @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid. + * @note The return value (non-null) has to be released manually. In order to avoid memory copy, use std::u16string reference directly without const, after this method is invoked, jsonStr will be empty since it was moved. + */ static Object *createJSONObject(std::u16string &jsonStr); /** From 0451617afef7875eaac71f9260dcc4b067df93c2 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 19:01:30 +0800 Subject: [PATCH 7/9] Remove extra space --- native/cocos/bindings/manual/jsb_global.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index 488e30780a0..2f219f41d90 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -1620,6 +1620,6 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT SAFE_DEC_REF(__jsbObj); SAFE_DEC_REF(__glObj); }); - + return true; } From 433bc5a6aaeab3cb0857fbad1770b9e2127ef1ba Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 19:25:19 +0800 Subject: [PATCH 8/9] Fix tidy --- native/cocos/base/UTF8.cpp | 10 +++++----- native/cocos/bindings/jswrapper/v8/Utils.h | 5 ++--- native/cocos/bindings/manual/jsb_cocos_manual.cpp | 2 +- native/cocos/bindings/manual/jsb_global.cpp | 1 - 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/native/cocos/base/UTF8.cpp b/native/cocos/base/UTF8.cpp index ccb88256459..3e1e8dde751 100644 --- a/native/cocos/base/UTF8.cpp +++ b/native/cocos/base/UTF8.cpp @@ -34,7 +34,7 @@ #include "ConvertUTF/ConvertUTF.h" #if CC_USE_SIMD_UTF -#include "simdutf/simdutf.cpp" +#include "simdutf/simdutf.cpp" //NOLINT #include "simdutf/simdutf.h" #endif @@ -227,8 +227,8 @@ bool UTF8ToUTF16(const ccstd::string &utf8, std::u16string &outUtf16) { //NOLINT } // We need a buffer of size where to write the UTF-16LE words. - size_t expected_utf16words = simdutf::utf16_length_from_utf8(utf8.c_str(), utf8.length()); - outUtf16.resize(expected_utf16words); + size_t expectedUtf16words = simdutf::utf16_length_from_utf8(utf8.c_str(), utf8.length()); + outUtf16.resize(expectedUtf16words); // convert to UTF-16LE size_t utf16words = simdutf::convert_utf8_to_utf16le(utf8.c_str(), utf8.length(), outUtf16.data()); @@ -267,7 +267,7 @@ bool UTF32ToUTF16(const std::u32string &utf32, std::u16string &outUtf16) { //NOL #if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) ccstd::string getStringUTFCharsJNI(JNIEnv *env, jstring srcjStr, bool *ret) { ccstd::string utf8Str; - auto *unicodeChar = static_cast(env->GetStringChars(srcjStr, nullptr)); + const auto *unicodeChar = static_cast(env->GetStringChars(srcjStr, nullptr)); size_t unicodeCharLength = env->GetStringLength(srcjStr); const std::u16string unicodeStr(reinterpret_cast(unicodeChar), unicodeCharLength); bool flag = UTF16ToUTF8(unicodeStr, utf8Str); @@ -342,7 +342,7 @@ void StringUTF8::replace(const ccstd::string &newStr) { ccstd::string StringUTF8::getAsCharSequence() const { ccstd::string charSequence; - for (auto &charUtf8 : _str) { + for (const auto &charUtf8 : _str) { charSequence.append(charUtf8._char); } diff --git a/native/cocos/bindings/jswrapper/v8/Utils.h b/native/cocos/bindings/jswrapper/v8/Utils.h index 09ac0c2aac9..9b92bfb415a 100644 --- a/native/cocos/bindings/jswrapper/v8/Utils.h +++ b/native/cocos/bindings/jswrapper/v8/Utils.h @@ -52,13 +52,12 @@ void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap); class ExternalStringResource : public v8::String::ExternalStringResource { public: - ExternalStringResource(std::u16string &s) + explicit ExternalStringResource(std::u16string &s) : _s(std::move(s)) { } - ~ExternalStringResource() { - } + ~ExternalStringResource() override = default; const uint16_t* data() const override { return reinterpret_cast(_s.data()); diff --git a/native/cocos/bindings/manual/jsb_cocos_manual.cpp b/native/cocos/bindings/manual/jsb_cocos_manual.cpp index a62acf524ec..46e9894436f 100644 --- a/native/cocos/bindings/manual/jsb_cocos_manual.cpp +++ b/native/cocos/bindings/manual/jsb_cocos_manual.cpp @@ -704,7 +704,7 @@ static bool js_readFile(se::State &s) { // NOLINT // fullPathForFilename is not threadsafe, so don't invoke it in thread pool. ccstd::string fullPath = cc::FileUtils::getInstance()->fullPathForFilename(path); - gIOThreadPool->pushTask([fullPath, callbackPtr](int tid) { + gIOThreadPool->pushTask([fullPath, callbackPtr](int/* tid */) { auto *fs = cc::FileUtils::getInstance(); if (fs == nullptr) { return; diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index 0508337bf37..1895e868e94 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -42,7 +42,6 @@ #include "platform/interfaces/modules/ISystem.h" #include "platform/interfaces/modules/ISystemWindow.h" #include "ui/edit-box/EditBox.h" -#include "v8/Object.h" #include "xxtea/xxtea.h" #include From d4bfc642d48655cf3bc7b081b5801ecd20f1affa Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 31 Jul 2024 19:43:19 +0800 Subject: [PATCH 9/9] Update jsb_cocos_manual.cpp --- native/cocos/bindings/manual/jsb_cocos_manual.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/cocos/bindings/manual/jsb_cocos_manual.cpp b/native/cocos/bindings/manual/jsb_cocos_manual.cpp index 46e9894436f..eb548f74f63 100644 --- a/native/cocos/bindings/manual/jsb_cocos_manual.cpp +++ b/native/cocos/bindings/manual/jsb_cocos_manual.cpp @@ -722,10 +722,10 @@ static bool js_readFile(se::State &s) { // NOLINT if (!engine) { return; } - + + std::shared_ptr u16str; // TODO(cjh): OpenHarmony NAPI support #if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI - std::shared_ptr u16str; if constexpr (std::is_same_v && isJson) { u16str = std::make_shared(); if (!cc::StringUtils::UTF8ToUTF16(*content, *u16str)) {