Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,29 @@ JsValue CallbackHandlers::CallJavaMethod(JsRuntime &rt, const JsValue &caller, c
returnValue = objectManager->GetJsObjectByJavaObject(javaObjectID);

if (js_util::is_null_or_undefined(returnValue)) {
returnValue = objectManager->CreateJSWrapper(javaObjectID, *returnType,
result);
MetadataNode *returnNode = nullptr;
JniLocalRef runtimeClazz(jEnv.GetObjectClass(result));
if (entry != nullptr && !isArrayReturn) {
if (!entry->returnClazzResolved) {
entry->returnClazzResolved = true;
// returnType is a JNI descriptor (Lpkg/Cls;); FindClass and the metadata want pkg/Cls.
if (returnType->size() > 2 && (*returnType)[0] == 'L') {
std::string declaredName = returnType->substr(1, returnType->size() - 2);
// JEnv::FindClass returns a cached global ref that lives for the process.
jclass declared = jEnv.FindClass(declaredName);
if (declared != nullptr) {
entry->returnClazz = declared;
entry->returnNode = MetadataNode::GetOrCreate(declaredName);
}
}
}
if (entry->returnClazz != nullptr && jEnv.isSameObject(runtimeClazz, entry->returnClazz)) {
returnNode = entry->returnNode;
}
}
returnValue = returnNode != nullptr
? objectManager->CreateJSWrapper(javaObjectID, returnNode, runtimeClazz, result)
: objectManager->CreateJSWrapper(javaObjectID, *returnType, result);
}
}

Expand Down
41 changes: 3 additions & 38 deletions NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,44 +248,9 @@ JsValue ArgConverter::convertToJsString(JsRuntime &rt, const jchar *data, int le
return convertToJsString(rt, std::string());
}

// Strict UTF-16 -> UTF-8, matching what napi_create_string_utf16 did inside
// the engine. Unpaired surrogates are emitted as U+FFFD rather than dropped,
// so a lone jchar (Type::Char, which is exactly one code unit) still yields a
// one-character JS string.
std::string utf8;
utf8.reserve((size_t) length);
for (int i = 0; i < length; i++) {
uint32_t cp = data[i];
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < length) {
uint32_t low = data[i + 1];
if (low >= 0xDC00 && low <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
i++;
} else {
cp = 0xFFFD;
}
} else if (cp >= 0xD800 && cp <= 0xDFFF) {
cp = 0xFFFD;
}

if (cp < 0x80) {
utf8.push_back((char) cp);
} else if (cp < 0x800) {
utf8.push_back((char) (0xC0 | (cp >> 6)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
} else if (cp < 0x10000) {
utf8.push_back((char) (0xE0 | (cp >> 12)));
utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
} else {
utf8.push_back((char) (0xF0 | (cp >> 18)));
utf8.push_back((char) (0x80 | ((cp >> 12) & 0x3F)));
utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F)));
utf8.push_back((char) (0x80 | (cp & 0x3F)));
}
}

return convertToJsString(rt, utf8);
static_assert(sizeof(jchar) == sizeof(char16_t));
return JsValue(rt, JsString::createFromUtf16(rt, reinterpret_cast<const char16_t *>(data),
static_cast<size_t>(length)));
}

u16string ArgConverter::ConvertToUtf16String(JsRuntime &rt, const JsValue &s) {
Expand Down
Binary file modified NativeScript/ffi/jni/jsi/conversion/ArgConverter.h
Binary file not shown.
37 changes: 26 additions & 11 deletions NativeScript/ffi/jni/jsi/conversion/JsArgConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,7 @@ bool JsArgConverter::ConvertJavaScriptArray(JsRuntime &rt, const JsValue &jsArr,

const auto &arraySignature = (*m_tokens)[index];

std::string elementType = arraySignature.substr(1);

const char elementTypePrefix = elementType[0];
const char elementTypePrefix = arraySignature.size() > 1 ? arraySignature[1] : ' ';

jclass elementClass;
std::string strippedClassName;
Expand Down Expand Up @@ -453,8 +451,18 @@ bool JsArgConverter::ConvertJavaScriptArray(JsRuntime &rt, const JsValue &jsArr,
case 'I': {
arr = jenv.NewIntArray(arrLength);
std::vector<jint> ints(arrLength);
for (jsize i = 0; i < arrLength; i++) {
ints[i] = (jint) js_util::get_int32(jsArray.getValueAtIndexBorrowed(rt, i));
// Bulk read through the engine (V8: Array::Iterate) into a stack buffer for small arrays.
double stackDoubles[64];
std::vector<double> heapDoubles;
double *doubles = stackDoubles;
if (arrLength > 64) { heapDoubles.resize(arrLength); doubles = heapDoubles.data(); }
size_t got = 0;
if (jsArray.copyNumbers(rt, doubles, (size_t) arrLength, &got) && got == (size_t) arrLength) {
for (jsize i = 0; i < arrLength; i++) ints[i] = (jint) (int32_t) doubles[i];
} else {
for (jsize i = 0; i < arrLength; i++) {
ints[i] = (jint) js_util::get_int32(jsArray.getValueAtIndexBorrowed(rt, i));
}
}
jenv.SetIntArrayRegion((jintArray) arr, 0, arrLength, ints.data());
break;
Expand All @@ -479,15 +487,22 @@ bool JsArgConverter::ConvertJavaScriptArray(JsRuntime &rt, const JsValue &jsArr,
}
case 'D': {
arr = jenv.NewDoubleArray(arrLength);
std::vector<jdouble> doubles(arrLength);
for (jsize i = 0; i < arrLength; i++) {
doubles[i] = (jdouble) js_util::get_number(jsArray.getValueAtIndexBorrowed(rt, i));
// Bulk read through the engine (V8: Array::Iterate) into a stack buffer for small arrays.
jdouble stackDoubles[64];
std::vector<jdouble> heapDoubles;
jdouble *doubles = stackDoubles;
if (arrLength > 64) { heapDoubles.resize(arrLength); doubles = heapDoubles.data(); }
size_t got = 0;
if (!jsArray.copyNumbers(rt, doubles, (size_t) arrLength, &got) || got != (size_t) arrLength) {
for (jsize i = 0; i < arrLength; i++) {
doubles[i] = (jdouble) js_util::get_number(jsArray.getValueAtIndexBorrowed(rt, i));
}
}
jenv.SetDoubleArrayRegion((jdoubleArray) arr, 0, arrLength, doubles.data());
jenv.SetDoubleArrayRegion((jdoubleArray) arr, 0, arrLength, doubles);
break;
}
case 'L':
strippedClassName = elementType.substr(1, elementType.length() - 2);
strippedClassName = arraySignature.substr(2, arraySignature.length() - 3);
elementClass = jenv.FindClass(strippedClassName);
arr = jenv.NewObjectArray(arrLength, elementClass, nullptr);
for (jsize i = 0; i < arrLength; i++) {
Expand Down Expand Up @@ -730,7 +745,7 @@ JniLocalRef JsArgConverter::GetByteBuffer(JsRuntime &rt, const JsValue &object,

ObjectManager::MarkObject(rt, object);

objectManager->Link(object, id, clazz);
objectManager->Link(object, id, clazz, nullptr, buffer);

return objectManager->GetJavaObjectByJsObject(object);
}
Expand Down
41 changes: 26 additions & 15 deletions NativeScript/ffi/jni/jsi/jni/JEnv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,37 @@
using namespace tns;
using namespace std;

JEnv::JEnv()
: m_env(nullptr) {
JNIEnv *env = nullptr;
jint ret = s_jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
// A JNIEnv* is valid for as long as its thread stays attached, so resolve it once per
// thread instead of asking the JavaVM on every JEnv construction (a Java call builds
// several JEnv objects on its way through the bridge).
static thread_local JNIEnv *t_cachedEnv = nullptr;

static JNIEnv *ResolveEnv() {
JNIEnv *env = nullptr;
jint ret = JEnv::GetJavaVM()->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if ((ret != JNI_OK) || (env == nullptr)) {
ret = s_jvm->AttachCurrentThread(&env, nullptr);
ret = JEnv::GetJavaVM()->AttachCurrentThread(&env, nullptr);
assert(ret == JNI_OK);
assert(env != nullptr);
}

m_env = env;
t_cachedEnv = env;
return env;
}

JEnv::JEnv(JNIEnv *jniEnv) {
jint ret = s_jvm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);

if ((ret != JNI_OK) || (jniEnv == nullptr)) {
ret = s_jvm->AttachCurrentThread(&jniEnv, nullptr);
assert(ret == JNI_OK);
assert(jniEnv != nullptr);
JEnv::JEnv()
: m_env(t_cachedEnv) {
if (m_env == nullptr) [[unlikely]] {
m_env = ResolveEnv();
}
}

m_env = jniEnv;
JEnv::JEnv(JNIEnv *jniEnv)
: m_env(jniEnv) {
if (m_env == nullptr) [[unlikely]] {
m_env = ResolveEnv();
} else {
t_cachedEnv = jniEnv;
}
}

JEnv::~JEnv() {
Expand Down Expand Up @@ -899,3 +906,7 @@ JEnv::GetInterfaceStaticMethodIDAndJClass(const std::string &interfaceName,
}



void JEnv::ClearCachedEnv() {
t_cachedEnv = nullptr;
}
6 changes: 6 additions & 0 deletions NativeScript/ffi/jni/jsi/jni/JEnv.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,12 @@ namespace tns {

static JavaVM *s_jvm;

public:
static JavaVM *GetJavaVM() { return s_jvm; }
static void ClearCachedEnv();

private:

static jclass RUNTIME_CLASS;

static jmethodID GET_CACHED_CLASS_METHOD_ID;
Expand Down
12 changes: 10 additions & 2 deletions NativeScript/ffi/jni/jsi/jni/LRUCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,19 @@ class LRUCache {
}
}

// Record a value the caller already holds (takes ownership of it). An existing entry
// for the key is evicted first: the fresh reference is the one known to be live, and the
// old one would otherwise leak outside the cache's capacity accounting.
void seed(const key_type& key, const value_type& value) {
evictKey(key);
insert(key, value);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void update(const key_type& key, const value_type& value) {
jweak ref = m_loadCallback(key, m_state);
insert(key, ref);
}

private:

// Evict a specific key (used when a cached value is no longer valid).
void evictKey(const key_type& key) {
auto it = m_key_to_value.find(key);
Expand All @@ -119,6 +125,8 @@ class LRUCache {
}
}

private:

// Record a fresh key-value pair in the cache
void insert(const key_type& k, const value_type& v) {
// Method is only called on cache misses
Expand Down
9 changes: 9 additions & 0 deletions NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "MetadataMethodInfo.h"
#include "MetadataFieldInfo.h"

class MetadataNode;

namespace tns {
enum class NodeType {
Package,
Expand Down Expand Up @@ -49,6 +51,9 @@ namespace tns {
memberId = other.memberId;
clazz = other.clazz;
parsedSig = other.parsedSig;
returnClazz = other.returnClazz;
returnClazzResolved = other.returnClazzResolved;
returnNode = other.returnNode;
mi = other.mi;
fi = other.fi;
sfi = other.sfi;
Expand Down Expand Up @@ -91,6 +96,10 @@ namespace tns {
jclass clazz;
std::vector<std::string> parsedSig;

jclass returnClazz = nullptr;
bool returnClazzResolved = false;
::MetadataNode *returnNode = nullptr;

MethodInfo mi;
FieldInfo *fi;
StaticFieldInfo *sfi;
Expand Down
21 changes: 16 additions & 5 deletions NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,22 @@ JsValue MetadataNode::CreateJSWrapper(JsRuntime &rt, ObjectManager *objectManage
return CreateArrayWrapper(rt);
}

JsValue obj = objectManager->GetEmptyObject();
JsValue ctorFunc = GetConstructorFunction(rt);
auto object = obj.asObjectBorrowed(rt);
object.setProperty(rt, "constructor", ctorFunc);
js_util::setPrototypeOf(rt, obj, js_util::get_prototype(rt, ctorFunc));
auto cache = GetMetadataNodeCache(rt);
auto itFound = cache->CtorFuncCache.find(m_treeNode);
JsValue prototype;
if (itFound != cache->CtorFuncCache.end() && itFound->second.wrapperPrototype.isObject()) {
prototype = JsValue(rt, itFound->second.wrapperPrototype);
} else {
JsValue ctorFunc = GetConstructorFunction(rt);
prototype = js_util::get_prototype(rt, ctorFunc);
itFound = cache->CtorFuncCache.find(m_treeNode);
if (itFound != cache->CtorFuncCache.end()) {
itFound->second.wrapperPrototype = JsValue(rt, prototype);
}
}
JsObject plain(rt);
JsValue obj(rt, plain);
js_util::setPrototypeOf(rt, obj, prototype);
SetInstanceMetadata(rt, obj, this);

return obj;
Expand Down
2 changes: 2 additions & 0 deletions NativeScript/ffi/jni/jsi/metadata/MetadataNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ class MetadataNode {

JsValue constructorFunction;
std::vector<MethodCallbackData *> instanceMethodCallbacks;

JsValue wrapperPrototype;
};

struct MethodCallbackData {
Expand Down
Loading