@rn-org/react-native-thread 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ #pragma once
2
+
3
+ #include <jni.h>
4
+ #include <jsi/jsi.h>
5
+ #include <hermes/hermes.h>
6
+ #include <chrono>
7
+ #include <memory>
8
+ #include <mutex>
9
+ #include <queue>
10
+ #include <string>
11
+ #include <unordered_set>
12
+
13
+ namespace rnthread {
14
+
15
+ /**
16
+ * Holds a standalone Hermes runtime for a single background thread.
17
+ * All evaluateJavaScript / global-injection calls must happen on the
18
+ * same thread that created the runtime (enforced by the caller in Kotlin).
19
+ */
20
+ class HermesThreadRuntime {
21
+ public:
22
+ HermesThreadRuntime(JNIEnv *env, jobject module, long threadId);
23
+ ~HermesThreadRuntime();
24
+
25
+ void evaluate(const std::string &code, const std::string &sourceURL);
26
+
27
+ private:
28
+ void installConsole(long threadId);
29
+ void installResolveThreadMessage(JNIEnv *env, jobject module, long threadId);
30
+ void installTimers();
31
+ void drainTimerLoop();
32
+
33
+ std::unique_ptr<facebook::hermes::HermesRuntime> runtime_;
34
+ jobject moduleRef_;
35
+ JavaVM *jvm_;
36
+
37
+ // ── Timer state ──
38
+ struct TimerEntry {
39
+ int id;
40
+ std::chrono::steady_clock::time_point fireAt;
41
+ bool repeating;
42
+ int intervalMs;
43
+
44
+ bool operator>(const TimerEntry &o) const { return fireAt > o.fireAt; }
45
+ };
46
+
47
+ // Min-heap ordered by fire time.
48
+ std::priority_queue<TimerEntry, std::vector<TimerEntry>,
49
+ std::greater<TimerEntry>>
50
+ timerQueue_;
51
+ std::unordered_set<int> cancelledTimers_;
52
+ int nextTimerId_ = 1;
53
+ };
54
+
55
+ } // namespace rnthread
@@ -0,0 +1,35 @@
1
+ package com.rnorg.reactnativethread
2
+
3
+ /**
4
+ * Thin JNI wrapper around the C++ HermesThreadRuntime.
5
+ * Each instance owns a standalone Hermes runtime.
6
+ */
7
+ class HermesThreadEngine {
8
+ companion object {
9
+ init {
10
+ System.loadLibrary("react_native_thread")
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Create a new Hermes runtime. Must be called on the thread that
16
+ * will subsequently call [evaluate].
17
+ *
18
+ * @param module The ReactNativeThreadModule instance (so native
19
+ * code can call back onThreadMessage).
20
+ * @param threadId The numeric thread ID.
21
+ * @return A native pointer (opaque handle) to the C++ runtime.
22
+ */
23
+ external fun nativeCreate(module: Any, threadId: Long): Long
24
+
25
+ /**
26
+ * Evaluate a JS code string on the runtime identified by [ptr].
27
+ * Must be called on the same thread that created the runtime.
28
+ */
29
+ external fun nativeEvaluate(ptr: Long, code: String, sourceURL: String)
30
+
31
+ /**
32
+ * Destroy the native runtime and free its memory.
33
+ */
34
+ external fun nativeDestroy(ptr: Long)
35
+ }
@@ -4,12 +4,6 @@ import android.util.Log
4
4
  import com.facebook.react.bridge.Arguments
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.modules.core.DeviceEventManagerModule
7
- import org.mozilla.javascript.BaseFunction
8
- import org.mozilla.javascript.Context as RhinoContext
9
- import org.mozilla.javascript.NativeObject
10
- import org.mozilla.javascript.ScriptRuntime
11
- import org.mozilla.javascript.Scriptable
12
- import org.mozilla.javascript.ScriptableObject
13
7
  import java.util.concurrent.CompletableFuture
14
8
  import java.util.concurrent.ConcurrentHashMap
15
9
  import java.util.concurrent.ExecutorService
@@ -21,9 +15,10 @@ class ReactNativeThreadModule(reactContext: ReactApplicationContext) :
21
15
 
22
16
  private data class ThreadEntry(
23
17
  val executor: ExecutorService,
24
- val scope: Scriptable
18
+ val runtimePtr: Long
25
19
  )
26
20
 
21
+ private val engine = HermesThreadEngine()
27
22
  private val threads = ConcurrentHashMap<Long, ThreadEntry>()
28
23
  private val nextId = AtomicLong(1L)
29
24
 
@@ -36,24 +31,19 @@ class ReactNativeThreadModule(reactContext: ReactApplicationContext) :
36
31
  Thread(r, "RNThread-$id")
37
32
  }
38
33
 
39
- val scopeFuture = CompletableFuture<Scriptable>()
34
+ // Create the Hermes runtime on the thread that will own it.
35
+ val ptrFuture = CompletableFuture<Long>()
40
36
  executor.execute {
41
- val cx = RhinoContext.enter()
42
37
  try {
43
- cx.optimizationLevel = -1
44
- val scope = cx.initStandardObjects()
45
- injectConsole(scope, id)
46
- injectResolveThreadMessage(scope, id)
47
- scopeFuture.complete(scope)
38
+ val ptr = engine.nativeCreate(this, id)
39
+ ptrFuture.complete(ptr)
48
40
  } catch (e: Exception) {
49
- scopeFuture.completeExceptionally(e)
50
- } finally {
51
- RhinoContext.exit()
41
+ ptrFuture.completeExceptionally(e)
52
42
  }
53
43
  }
54
44
 
55
- val scope = scopeFuture.get()
56
- threads[id] = ThreadEntry(executor, scope)
45
+ val ptr = ptrFuture.get()
46
+ threads[id] = ThreadEntry(executor, ptr)
57
47
  return id.toDouble()
58
48
  }
59
49
 
@@ -64,91 +54,40 @@ class ReactNativeThreadModule(reactContext: ReactApplicationContext) :
64
54
  return
65
55
  }
66
56
  entry.executor.execute {
67
- val cx = RhinoContext.enter()
68
57
  try {
69
- cx.optimizationLevel = -1
70
- cx.evaluateString(entry.scope, code, "RNThread-${threadId.toLong()}", 1, null)
58
+ engine.nativeEvaluate(entry.runtimePtr, code, "RNThread-${threadId.toLong()}")
71
59
  } catch (e: Exception) {
72
60
  Log.e(NAME, "JS exception on thread $threadId: ${e.message}")
73
- } finally {
74
- RhinoContext.exit()
75
61
  }
76
62
  }
77
63
  }
78
64
 
79
65
  override fun destroyThread(threadId: Double) {
80
66
  val entry = threads.remove(threadId.toLong()) ?: return
81
- entry.executor.shutdown()
82
- }
83
-
84
- private fun injectResolveThreadMessage(scope: Scriptable, threadId: Long) {
85
- val ctx = reactApplicationContext
86
- ScriptableObject.putProperty(
87
- scope as ScriptableObject,
88
- "resolveThreadMessage",
89
- object : BaseFunction() {
90
- override fun call(
91
- cx: RhinoContext,
92
- callScope: Scriptable,
93
- thisObj: Scriptable?,
94
- args: Array<out Any?>
95
- ): Any {
96
- val raw = args.getOrNull(0)
97
- val jsonString: String = try {
98
- val jsonObj = ScriptableObject.getProperty(callScope, "JSON") as? Scriptable
99
- val stringify = jsonObj?.let { ScriptableObject.getProperty(it, "stringify") }
100
- if (stringify is org.mozilla.javascript.Function) {
101
- ScriptRuntime.toString(stringify.call(cx, callScope, jsonObj, arrayOf(raw)))
102
- } else {
103
- raw?.toString() ?: "null"
104
- }
105
- } catch (e: Exception) {
106
- raw?.toString() ?: "null"
107
- }
108
-
109
- val params = Arguments.createMap().apply {
110
- putDouble("threadId", threadId.toDouble())
111
- putString("data", jsonString)
112
- }
113
- ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
114
- .emit("RNThreadMessage", params)
115
-
116
- return RhinoContext.getUndefinedValue()
117
- }
67
+ // Destroy the native runtime on its owning thread, then shut down.
68
+ entry.executor.execute {
69
+ try {
70
+ engine.nativeDestroy(entry.runtimePtr)
71
+ } catch (e: Exception) {
72
+ Log.w(NAME, "Error destroying runtime for thread $threadId: ${e.message}")
118
73
  }
119
- )
74
+ }
75
+ entry.executor.shutdown()
120
76
  }
121
77
 
122
- private fun injectConsole(scope: Scriptable, threadId: Long) {
123
- val tag = "RNThread-$threadId"
124
-
125
- fun makeLogFn(level: Int) = object : BaseFunction() {
126
- override fun call(
127
- cx: RhinoContext,
128
- scope: Scriptable,
129
- thisObj: Scriptable?,
130
- args: Array<out Any?>
131
- ): Any {
132
- val msg = args.joinToString(" ") { it?.toString() ?: "null" }
133
- when (level) {
134
- Log.DEBUG -> Log.d(tag, msg)
135
- Log.INFO -> Log.i(tag, msg)
136
- Log.WARN -> Log.w(tag, msg)
137
- Log.ERROR -> Log.e(tag, msg)
138
- else -> Log.v(tag, msg)
139
- }
140
- return RhinoContext.getUndefinedValue()
141
- }
78
+ /**
79
+ * Called from C++ via JNI when resolveThreadMessage() is invoked
80
+ * inside a background thread.
81
+ */
82
+ @Suppress("unused")
83
+ fun onThreadMessage(threadId: Double, data: String) {
84
+ val params = Arguments.createMap().apply {
85
+ putDouble("threadId", threadId)
86
+ putString("data", data)
142
87
  }
143
-
144
- val console = NativeObject()
145
- console.parentScope = scope
146
- ScriptableObject.putProperty(console, "log", makeLogFn(Log.INFO))
147
- ScriptableObject.putProperty(console, "info", makeLogFn(Log.INFO))
148
- ScriptableObject.putProperty(console, "warn", makeLogFn(Log.WARN))
149
- ScriptableObject.putProperty(console, "error", makeLogFn(Log.ERROR))
150
- ScriptableObject.putProperty(console, "debug", makeLogFn(Log.DEBUG))
151
- ScriptableObject.putProperty(scope as ScriptableObject, "console", console)
88
+ reactApplicationContext
89
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
90
+ .emit("RNThreadMessage", params)
152
91
  }
153
92
 
154
93
  companion object {
@@ -0,0 +1,263 @@
1
+ #include "HermesThreadEngine.h"
2
+
3
+ #include <string>
4
+ #include <thread>
5
+
6
+ using namespace facebook;
7
+
8
+ namespace rnthread {
9
+
10
+ // ──────────────────────────────────────────────────────────────────
11
+ // HermesThreadRuntime
12
+ // ──────────────────────────────────────────────────────────────────
13
+
14
+ HermesThreadRuntime::HermesThreadRuntime(long threadId, MessageCallback onMessage)
15
+ : runtime_(facebook::hermes::makeHermesRuntime(
16
+ ::hermes::vm::RuntimeConfig::Builder()
17
+ .withES6Promise(true)
18
+ .withMicrotaskQueue(true)
19
+ .build())),
20
+ onMessage_(std::move(onMessage)) {
21
+ installConsole(threadId);
22
+ installTimers();
23
+ installResolveThreadMessage(threadId);
24
+ }
25
+
26
+ HermesThreadRuntime::~HermesThreadRuntime() {
27
+ runtime_.reset();
28
+ }
29
+
30
+ void HermesThreadRuntime::evaluate(
31
+ const std::string &code,
32
+ const std::string &sourceURL) {
33
+ auto buf = std::make_shared<jsi::StringBuffer>(code);
34
+ runtime_->evaluateJavaScript(buf, sourceURL);
35
+ drainTimerLoop();
36
+ }
37
+
38
+ // ──────────────────────────────────────────────────────────────────
39
+ // Timer event loop
40
+ // ──────────────────────────────────────────────────────────────────
41
+
42
+ void HermesThreadRuntime::drainTimerLoop() {
43
+ auto &rt = *runtime_;
44
+ auto callbacks =
45
+ rt.global()
46
+ .getPropertyAsObject(rt, "__rnTimerCallbacks__");
47
+
48
+ while (true) {
49
+ rt.drainMicrotasks();
50
+
51
+ if (timerQueue_.empty()) break;
52
+
53
+ auto now = std::chrono::steady_clock::now();
54
+ auto &next = timerQueue_.top();
55
+
56
+ if (next.fireAt > now) {
57
+ std::this_thread::sleep_until(next.fireAt);
58
+ }
59
+
60
+ bool fired = false;
61
+ while (!timerQueue_.empty()) {
62
+ now = std::chrono::steady_clock::now();
63
+ if (timerQueue_.top().fireAt > now) break;
64
+
65
+ auto entry = timerQueue_.top();
66
+ timerQueue_.pop();
67
+
68
+ if (cancelledTimers_.count(entry.id)) {
69
+ cancelledTimers_.erase(entry.id);
70
+ continue;
71
+ }
72
+
73
+ auto idStr = std::to_string(entry.id);
74
+ auto cb = callbacks.getProperty(rt, idStr.c_str());
75
+ if (cb.isObject() && cb.getObject(rt).isFunction(rt)) {
76
+ cb.getObject(rt).asFunction(rt).call(rt);
77
+ fired = true;
78
+ }
79
+
80
+ if (entry.repeating) {
81
+ entry.fireAt = std::chrono::steady_clock::now() +
82
+ std::chrono::milliseconds(entry.intervalMs);
83
+ timerQueue_.push(entry);
84
+ } else {
85
+ callbacks.setProperty(rt, idStr.c_str(), jsi::Value::undefined());
86
+ }
87
+ }
88
+
89
+ if (fired) {
90
+ rt.drainMicrotasks();
91
+ }
92
+
93
+ if (timerQueue_.empty()) break;
94
+ }
95
+ }
96
+
97
+ // ──────────────────────────────────────────────────────────────────
98
+ // setTimeout / setInterval / clearTimeout / clearInterval
99
+ // ──────────────────────────────────────────────────────────────────
100
+
101
+ void HermesThreadRuntime::installTimers() {
102
+ auto &rt = *runtime_;
103
+
104
+ rt.global().setProperty(
105
+ rt, "__rnTimerCallbacks__", jsi::Object(rt));
106
+
107
+ auto *self = this;
108
+
109
+ // ── setTimeout(fn, ms) → id ──
110
+ auto setTimeoutFn = jsi::Function::createFromHostFunction(
111
+ rt, jsi::PropNameID::forAscii(rt, "setTimeout"), 2,
112
+ [self](jsi::Runtime &rt, const jsi::Value &,
113
+ const jsi::Value *args, size_t count) -> jsi::Value {
114
+ if (count < 1 || !args[0].isObject() ||
115
+ !args[0].getObject(rt).isFunction(rt))
116
+ return jsi::Value::undefined();
117
+
118
+ int ms = count >= 2 ? static_cast<int>(args[1].asNumber()) : 0;
119
+ int id = self->nextTimerId_++;
120
+
121
+ auto cbs = rt.global().getPropertyAsObject(rt, "__rnTimerCallbacks__");
122
+ cbs.setProperty(rt, std::to_string(id).c_str(),
123
+ args[0].getObject(rt));
124
+
125
+ TimerEntry entry;
126
+ entry.id = id;
127
+ entry.fireAt = std::chrono::steady_clock::now() +
128
+ std::chrono::milliseconds(ms);
129
+ entry.repeating = false;
130
+ entry.intervalMs = 0;
131
+ self->timerQueue_.push(entry);
132
+
133
+ return jsi::Value(id);
134
+ });
135
+ rt.global().setProperty(rt, "setTimeout", std::move(setTimeoutFn));
136
+
137
+ // ── setInterval(fn, ms) → id ──
138
+ auto setIntervalFn = jsi::Function::createFromHostFunction(
139
+ rt, jsi::PropNameID::forAscii(rt, "setInterval"), 2,
140
+ [self](jsi::Runtime &rt, const jsi::Value &,
141
+ const jsi::Value *args, size_t count) -> jsi::Value {
142
+ if (count < 1 || !args[0].isObject() ||
143
+ !args[0].getObject(rt).isFunction(rt))
144
+ return jsi::Value::undefined();
145
+
146
+ int ms = count >= 2 ? static_cast<int>(args[1].asNumber()) : 0;
147
+ if (ms <= 0) ms = 1;
148
+ int id = self->nextTimerId_++;
149
+
150
+ auto cbs = rt.global().getPropertyAsObject(rt, "__rnTimerCallbacks__");
151
+ cbs.setProperty(rt, std::to_string(id).c_str(),
152
+ args[0].getObject(rt));
153
+
154
+ TimerEntry entry;
155
+ entry.id = id;
156
+ entry.fireAt = std::chrono::steady_clock::now() +
157
+ std::chrono::milliseconds(ms);
158
+ entry.repeating = true;
159
+ entry.intervalMs = ms;
160
+ self->timerQueue_.push(entry);
161
+
162
+ return jsi::Value(id);
163
+ });
164
+ rt.global().setProperty(rt, "setInterval", std::move(setIntervalFn));
165
+
166
+ // ── clearTimeout(id) / clearInterval(id) ──
167
+ auto clearTimerFn = jsi::Function::createFromHostFunction(
168
+ rt, jsi::PropNameID::forAscii(rt, "clearTimeout"), 1,
169
+ [self](jsi::Runtime &rt, const jsi::Value &,
170
+ const jsi::Value *args, size_t count) -> jsi::Value {
171
+ if (count >= 1 && args[0].isNumber()) {
172
+ int id = static_cast<int>(args[0].asNumber());
173
+ self->cancelledTimers_.insert(id);
174
+ auto cbs =
175
+ rt.global().getPropertyAsObject(rt, "__rnTimerCallbacks__");
176
+ cbs.setProperty(rt, std::to_string(id).c_str(),
177
+ jsi::Value::undefined());
178
+ }
179
+ return jsi::Value::undefined();
180
+ });
181
+ rt.global().setProperty(rt, "clearTimeout", clearTimerFn);
182
+ rt.global().setProperty(rt, "clearInterval", std::move(clearTimerFn));
183
+ }
184
+
185
+ // ──────────────────────────────────────────────────────────────────
186
+ // console.{log,info,warn,error,debug}
187
+ // ──────────────────────────────────────────────────────────────────
188
+
189
+ void HermesThreadRuntime::installConsole(long threadId) {
190
+ auto &rt = *runtime_;
191
+
192
+ auto makeLogFn = [&rt, threadId](const char *level) {
193
+ return jsi::Function::createFromHostFunction(
194
+ rt,
195
+ jsi::PropNameID::forAscii(rt, "log"),
196
+ 1,
197
+ [threadId, level](
198
+ jsi::Runtime &rt,
199
+ const jsi::Value &,
200
+ const jsi::Value *args,
201
+ size_t count) -> jsi::Value {
202
+ std::string msg;
203
+ for (size_t i = 0; i < count; ++i) {
204
+ if (i > 0) msg += ' ';
205
+ msg += args[i].toString(rt).utf8(rt);
206
+ }
207
+ // NSLog is not available in pure C++; use fprintf which
208
+ // shows up in Xcode console.
209
+ fprintf(stderr, "[RNThread-%ld] %s: %s\n", threadId, level, msg.c_str());
210
+ return jsi::Value::undefined();
211
+ });
212
+ };
213
+
214
+ auto console = jsi::Object(rt);
215
+ console.setProperty(rt, "log", makeLogFn("LOG"));
216
+ console.setProperty(rt, "info", makeLogFn("INFO"));
217
+ console.setProperty(rt, "warn", makeLogFn("WARN"));
218
+ console.setProperty(rt, "error", makeLogFn("ERROR"));
219
+ console.setProperty(rt, "debug", makeLogFn("DEBUG"));
220
+ rt.global().setProperty(rt, "console", std::move(console));
221
+ }
222
+
223
+ // ──────────────────────────────────────────────────────────────────
224
+ // resolveThreadMessage(data)
225
+ // ──────────────────────────────────────────────────────────────────
226
+
227
+ void HermesThreadRuntime::installResolveThreadMessage(long threadId) {
228
+ auto &rt = *runtime_;
229
+
230
+ auto fn = jsi::Function::createFromHostFunction(
231
+ rt,
232
+ jsi::PropNameID::forAscii(rt, "resolveThreadMessage"),
233
+ 1,
234
+ [this](
235
+ jsi::Runtime &rt,
236
+ const jsi::Value &,
237
+ const jsi::Value *args,
238
+ size_t count) -> jsi::Value {
239
+ auto json = rt.global().getPropertyAsObject(rt, "JSON");
240
+ auto stringify = json.getPropertyAsFunction(rt, "stringify");
241
+ std::string serialised;
242
+ if (count > 0) {
243
+ auto result = stringify.call(rt, args[0]);
244
+ if (result.isString()) {
245
+ serialised = result.getString(rt).utf8(rt);
246
+ } else {
247
+ serialised = "null";
248
+ }
249
+ } else {
250
+ serialised = "null";
251
+ }
252
+
253
+ if (onMessage_) {
254
+ onMessage_(serialised);
255
+ }
256
+
257
+ return jsi::Value::undefined();
258
+ });
259
+
260
+ rt.global().setProperty(rt, "resolveThreadMessage", std::move(fn));
261
+ }
262
+
263
+ } // namespace rnthread
@@ -0,0 +1,49 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+ #include <hermes/hermes.h>
5
+ #include <chrono>
6
+ #include <functional>
7
+ #include <memory>
8
+ #include <queue>
9
+ #include <string>
10
+ #include <unordered_set>
11
+
12
+ namespace rnthread {
13
+
14
+ using MessageCallback = std::function<void(const std::string &serialised)>;
15
+
16
+ class HermesThreadRuntime {
17
+ public:
18
+ HermesThreadRuntime(long threadId, MessageCallback onMessage);
19
+ ~HermesThreadRuntime();
20
+
21
+ void evaluate(const std::string &code, const std::string &sourceURL);
22
+
23
+ private:
24
+ void installConsole(long threadId);
25
+ void installResolveThreadMessage(long threadId);
26
+ void installTimers();
27
+ void drainTimerLoop();
28
+
29
+ std::unique_ptr<facebook::hermes::HermesRuntime> runtime_;
30
+ MessageCallback onMessage_;
31
+
32
+ // ── Timer state ──
33
+ struct TimerEntry {
34
+ int id;
35
+ std::chrono::steady_clock::time_point fireAt;
36
+ bool repeating;
37
+ int intervalMs;
38
+
39
+ bool operator>(const TimerEntry &o) const { return fireAt > o.fireAt; }
40
+ };
41
+
42
+ std::priority_queue<TimerEntry, std::vector<TimerEntry>,
43
+ std::greater<TimerEntry>>
44
+ timerQueue_;
45
+ std::unordered_set<int> cancelledTimers_;
46
+ int nextTimerId_ = 1;
47
+ };
48
+
49
+ } // namespace rnthread
@@ -1,9 +1,12 @@
1
1
  #import "ReactNativeThread.h"
2
- #import <JavaScriptCore/JavaScriptCore.h>
2
+ #import "HermesThreadEngine.h"
3
+
4
+ #include <dispatch/dispatch.h>
5
+ #include <memory>
6
+ #include <string>
3
7
 
4
8
  @interface RNThread : NSObject
5
- @property (nonatomic, strong) JSVirtualMachine *vm;
6
- @property (nonatomic, strong) JSContext *context;
9
+ @property (nonatomic, assign) std::shared_ptr<rnthread::HermesThreadRuntime> engine;
7
10
  @property (nonatomic, strong) dispatch_queue_t queue;
8
11
  @property (nonatomic) dispatch_semaphore_t ready;
9
12
  @end
@@ -44,59 +47,25 @@
44
47
  t.queue = dispatch_queue_create(label.UTF8String, attr);
45
48
  t.ready = dispatch_semaphore_create(0);
46
49
 
47
- NSUInteger tidForLog = (NSUInteger)_nextId;
50
+ long tidForLog = (long)_nextId;
51
+ __weak ReactNativeThread *weakSelf = self;
48
52
 
49
53
  dispatch_async(t.queue, ^{
50
- t.vm = [[JSVirtualMachine alloc] init];
51
- t.context = [[JSContext alloc] initWithVirtualMachine:t.vm];
52
- t.context.exceptionHandler = ^(JSContext *ctx, JSValue *exception) {
53
- NSLog(@"[RNThread-%lu] JS exception: %@", (unsigned long)tidForLog, exception);
54
- };
55
-
56
- NSString *tag = [NSString stringWithFormat:@"RNThread-%lu", (unsigned long)tidForLog];
57
- NSMutableDictionary *console = [NSMutableDictionary new];
58
- console[@"log"] = ^{
59
- NSArray *args = [JSContext currentArguments];
60
- NSMutableArray *parts = [NSMutableArray new];
61
- for (JSValue *v in args) [parts addObject:[v toString]];
62
- NSLog(@"[%@] %@", tag, [parts componentsJoinedByString:@" "]);
63
- };
64
- console[@"info"] = console[@"log"];
65
- console[@"debug"] = console[@"log"];
66
- console[@"warn"] = ^{
67
- NSArray *args = [JSContext currentArguments];
68
- NSMutableArray *parts = [NSMutableArray new];
69
- for (JSValue *v in args) [parts addObject:[v toString]];
70
- NSLog(@"[%@] WARN: %@", tag, [parts componentsJoinedByString:@" "]);
71
- };
72
- console[@"error"] = ^{
73
- NSArray *args = [JSContext currentArguments];
74
- NSMutableArray *parts = [NSMutableArray new];
75
- for (JSValue *v in args) [parts addObject:[v toString]];
76
- NSLog(@"[%@] ERROR: %@", tag, [parts componentsJoinedByString:@" "]);
77
- };
78
- t.context[@"console"] = console;
79
-
80
- NSUInteger capturedTid = tidForLog;
81
- __weak ReactNativeThread *weakSelf = self;
82
- t.context[@"resolveThreadMessage"] = ^(JSValue *data) {
83
- JSValue *jsonStr = [data.context[@"JSON"] invokeMethod:@"stringify"
84
- withArguments:@[data]];
85
- NSString *serialised = [jsonStr toString];
86
- if (!serialised || [serialised isEqualToString:@"undefined"]) {
87
- serialised = @"null";
88
- }
89
-
54
+ auto onMessage = [weakSelf, tidForLog](const std::string &serialised) {
90
55
  ReactNativeThread *strongSelf = weakSelf;
91
56
  if (!strongSelf) return;
92
57
 
58
+ NSString *nsData = [NSString stringWithUTF8String:serialised.c_str()];
59
+ if (!nsData) nsData = @"null";
60
+
93
61
  [strongSelf sendEventWithName:@"RNThreadMessage"
94
62
  body:@{
95
- @"threadId": @(capturedTid),
96
- @"data": serialised
63
+ @"threadId": @(tidForLog),
64
+ @"data": nsData
97
65
  }];
98
66
  };
99
67
 
68
+ t.engine = std::make_shared<rnthread::HermesThreadRuntime>(tidForLog, onMessage);
100
69
  dispatch_semaphore_signal(t.ready);
101
70
  });
102
71
 
@@ -119,12 +88,18 @@
119
88
  return;
120
89
  }
121
90
 
122
- NSString *codeCopy = [code copy];
91
+ std::string codeCpp = std::string([code UTF8String]);
92
+ std::string sourceURL = "RNThread-" + std::to_string((long)threadId);
123
93
  dispatch_semaphore_t ready = t.ready;
94
+
124
95
  dispatch_async(t.queue, ^{
125
96
  dispatch_semaphore_wait(ready, DISPATCH_TIME_FOREVER);
126
97
  dispatch_semaphore_signal(ready);
127
- [t.context evaluateScript:codeCopy];
98
+ try {
99
+ t.engine->evaluate(codeCpp, sourceURL);
100
+ } catch (const std::exception &e) {
101
+ NSLog(@"[RNThread-%.0f] JS exception: %s", threadId, e.what());
102
+ }
128
103
  });
129
104
  }
130
105
 
@@ -137,7 +112,7 @@
137
112
  if (!t) return;
138
113
 
139
114
  dispatch_async(t.queue, ^{
140
- (void)t;
115
+ t.engine.reset();
141
116
  });
142
117
  }
143
118