Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed #17012: Loading assets synchronously causes rendering to freeze. #17464

Merged
merged 10 commits into from
Jul 31, 2024
25 changes: 25 additions & 0 deletions cocos/native-binding/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,31 @@
*
*/
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.
Expand Down Expand Up @@ -1411,7 +1436,7 @@
*/
const adpf: {
/**
* @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling.

Check warning on line 1439 in cocos/native-binding/index.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

This line has a length of 336. Maximum allowed is 150
* @zh 提供设备在达到严重节流之前当前有多少热余量的估计值。值的范围是非负浮点数,其中0.0表示距离过热的固定距离,1.0表示设备将被严重限制,而大于1.0的值可能表示更重的限制。
* @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314
*/
Expand Down
51 changes: 35 additions & 16 deletions native/cocos/base/Scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,40 @@ void Scheduler::removeAllFunctionsToBePerformedInCocosThread() {
_functionsToPerform.clear();
}

void Scheduler::runFunctionsToBePerformedInCocosThread() {
//
// Functions allocated from another thread
//
auto beginTime = std::chrono::steady_clock::now();
auto nowTime = beginTime;

// 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();

auto iter = temp.begin();
for (; iter != temp.end(); ++iter) {
nowTime = std::chrono::steady_clock::now();
auto passedMS = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - beginTime).count();
// If the callbacks takes more than 16ms, delay the remaining jobs to next frame.
if (passedMS > 16) {
break;
}

(*iter)();
}

std::lock_guard<std::mutex> lk(_performMutex);
for (; iter != temp.end(); ++iter) {
_functionsToPerform.emplace_back(std::move(*iter));
}
}
}

// main loop
void Scheduler::update(float dt) {
_updateHashLocked = true;
Expand Down Expand Up @@ -377,22 +411,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
2 changes: 2 additions & 0 deletions native/cocos/base/Scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ class CC_DLL Scheduler final {
* @js NA
*/
void removeAllFunctionsToBePerformedInCocosThread();

void runFunctionsToBePerformedInCocosThread();

bool isCurrentTargetSalvaged() const { return _currentTargetSalvaged; };

Expand Down
30 changes: 30 additions & 0 deletions native/cocos/base/UTF8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@
THE SOFTWARE.
****************************************************************************/

#define CC_USE_SIMD_UTF 1

#include "base/UTF8.h"

#include <cstdarg>
#include <cstdlib>

#include "ConvertUTF/ConvertUTF.h"

#if CC_USE_SIMD_UTF
#include "simdutf/simdutf.cpp"

Check failure on line 37 in native/cocos/base/UTF8.cpp

View workflow job for this annotation

GitHub Actions / ClangTidy Android

suspicious #include of file with '.cpp' extension (bugprone-suspicious-include)
#include "simdutf/simdutf.h"
#endif

#include "base/Log.h"

namespace cc {
Expand Down Expand Up @@ -211,7 +219,29 @@
}

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());

Check failure on line 230 in native/cocos/base/UTF8.cpp

View workflow job for this annotation

GitHub Actions / ClangTidy Android

invalid case style for local variable 'expected_utf16words' (readability-identifier-naming)
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
Expand All @@ -237,7 +267,7 @@
#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<const uint16_t *>(env->GetStringChars(srcjStr, nullptr));

Check failure on line 270 in native/cocos/base/UTF8.cpp

View workflow job for this annotation

GitHub Actions / ClangTidy Android

'auto *unicodeChar' can be declared as 'const auto *unicodeChar' (readability-qualified-auto)
size_t unicodeCharLength = env->GetStringLength(srcjStr);
const std::u16string unicodeStr(reinterpret_cast<const char16_t *>(unicodeChar), unicodeCharLength);
bool flag = UTF16ToUTF8(unicodeStr, utf8Str);
Expand Down Expand Up @@ -312,7 +342,7 @@
ccstd::string StringUTF8::getAsCharSequence() const {
ccstd::string charSequence;

for (auto &charUtf8 : _str) {

Check failure on line 345 in native/cocos/base/UTF8.cpp

View workflow job for this annotation

GitHub Actions / ClangTidy Android

'auto &charUtf8' can be declared as 'const auto &charUtf8' (readability-qualified-auto)
charSequence.append(charUtf8._char);
}

Expand Down
16 changes: 16 additions & 0 deletions native/cocos/bindings/jswrapper/v8/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Context> context = __isolate->GetCurrentContext();
v8::MaybeLocal<v8::Value> ret = v8::JSON::Parse(context, v8Str.ToLocalChecked());
if (ret.IsEmpty()) {
return nullptr;
}

v8::Local<v8::Object> jsobj = v8::Local<v8::Object>::Cast(ret.ToLocalChecked());
return Object::_createJSObject(nullptr, jsobj);
}

bool Object::init(Class *cls, v8::Local<v8::Object> obj) {
_cls = cls;

Expand Down
1 change: 1 addition & 0 deletions native/cocos/bindings/jswrapper/v8/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
minggo marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Creates a JavaScript Native Binding Object from an existing se::Class instance.
Expand Down
19 changes: 17 additions & 2 deletions native/cocos/bindings/jswrapper/v8/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::String> 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<const uint16_t*>(u16Str.data()), type, static_cast<int>(u16Str.length()));
}
return {};
}

} // namespace {

Check failure on line 52 in native/cocos/bindings/jswrapper/v8/Utils.cpp

View workflow job for this annotation

GitHub Actions / ClangTidy Android

anonymous namespace ends with an unrecognized comment (google-readability-namespace-comments)

void jsToSeArgs(const v8::FunctionCallbackInfo<v8::Value> &v8args, ValueArray &outArr) {
v8::Isolate *isolate = v8args.GetIsolate();
for (int i = 0; i < v8args.Length(); i++) {
Expand All @@ -62,7 +75,8 @@
*outJsVal = v8::Number::New(isolate, v.toDouble());
break;
case Value::Type::String: {
v8::MaybeLocal<v8::String> str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast<int>(v.toString().length()));
// v8::MaybeLocal<v8::String> str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast<int>(v.toString().length()));
auto str = createV8StringFromUtf8(isolate, v.toString(), v8::NewStringType::kNormal);
if (!str.IsEmpty()) {
*outJsVal = str.ToLocalChecked();
} else {
Expand Down Expand Up @@ -181,7 +195,8 @@
break;
}
case Value::Type::String: {
v8::MaybeLocal<v8::String> value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal);
// v8::MaybeLocal<v8::String> 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;
Expand Down
21 changes: 21 additions & 0 deletions native/cocos/bindings/jswrapper/v8/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@
Object *getPrivate(v8::Isolate *isolate, v8::Local<v8::Value> value);
void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap);

class ExternalStringResource : public v8::String::ExternalStringResource {
public:
ExternalStringResource(std::u16string &s)

Check failure on line 55 in native/cocos/bindings/jswrapper/v8/Utils.h

View workflow job for this annotation

GitHub Actions / ClangTidy Android

single-argument constructors must be marked explicit to avoid unintentional implicit conversions (google-explicit-constructor)
: _s(std::move(s)) {

}

~ExternalStringResource() {

Check failure on line 60 in native/cocos/bindings/jswrapper/v8/Utils.h

View workflow job for this annotation

GitHub Actions / ClangTidy Android

use '= default' to define a trivial destructor (modernize-use-equals-default)

Check failure on line 60 in native/cocos/bindings/jswrapper/v8/Utils.h

View workflow job for this annotation

GitHub Actions / ClangTidy Android

annotate this function with 'override' or (rarely) 'final' (modernize-use-override)
}

const uint16_t* data() const override {
return reinterpret_cast<const uint16_t*>(_s.data());
}

size_t length() const override {
return _s.length();
}
private:
std::u16string _s;
};

} // namespace internal
} // namespace se

Expand Down
Loading
Loading