1 #pragma once
2 
3 #include <jsi/jsi.h>
4 #include <memory>
5 #include <string>
6 #include <utility>
7 #include <vector>
8 
9 #include "ReanimatedRuntime.h"
10 #include "RuntimeManager.h"
11 #include "Scheduler.h"
12 
13 using namespace facebook;
14 
15 namespace reanimated {
16 
17 class JSRuntimeHelper;
18 
19 // Core functions are not allowed to capture outside variables, otherwise they'd
20 // try to access _closure variable which is something we want to avoid for
21 // simplicity reasons.
22 class CoreFunction {
23  private:
24   std::unique_ptr<jsi::Function> rnFunction_;
25   std::unique_ptr<jsi::Function> uiFunction_;
26   std::string functionBody_;
27   std::string location_;
28   JSRuntimeHelper
29       *runtimeHelper_; // runtime helper holds core function references, so we
30                        // use normal pointer here to avoid ref cycles.
31   std::unique_ptr<jsi::Function> &getFunction(jsi::Runtime &rt);
32 
33  public:
34   CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject);
35   template <typename... Args>
36   jsi::Value call(jsi::Runtime &rt, Args &&...args) {
37     return getFunction(rt)->call(rt, args...);
38   }
39 };
40 
41 class JSRuntimeHelper {
42  private:
43   jsi::Runtime *rnRuntime_; // React-Native's main JS runtime
44   jsi::Runtime *uiRuntime_; // UI runtime created by Reanimated
45   std::shared_ptr<Scheduler> scheduler_;
46 
47  public:
48   JSRuntimeHelper(
49       jsi::Runtime *rnRuntime,
50       jsi::Runtime *uiRuntime,
51       const std::shared_ptr<Scheduler> &scheduler)
52       : rnRuntime_(rnRuntime), uiRuntime_(uiRuntime), scheduler_(scheduler) {}
53 
54   volatile bool uiRuntimeDestroyed = false;
55   std::unique_ptr<CoreFunction> callGuard;
56   std::unique_ptr<CoreFunction> valueUnpacker;
57 
58   inline jsi::Runtime *uiRuntime() const {
59     return uiRuntime_;
60   }
61 
62   inline jsi::Runtime *rnRuntime() const {
63     return rnRuntime_;
64   }
65 
66   inline bool isUIRuntime(const jsi::Runtime &rt) const {
67     return &rt == uiRuntime_;
68   }
69 
70   inline bool isRNRuntime(const jsi::Runtime &rt) const {
71     return &rt == rnRuntime_;
72   }
73 
74   void scheduleOnUI(std::function<void()> job) {
75     scheduler_->scheduleOnUI(job);
76   }
77 
78   void scheduleOnJS(std::function<void()> job) {
79     scheduler_->scheduleOnJS(job);
80   }
81 
82   template <typename... Args>
83   inline void runOnUIGuarded(const jsi::Value &function, Args &&...args) {
84     // We only use callGuard in debug mode, otherwise we call the provided
85     // function directly. CallGuard provides a way of capturing exceptions in
86     // JavaScript and propagating them to the main React Native thread such that
87     // they can be presented using RN's LogBox.
88     jsi::Runtime &rt = *uiRuntime_;
89 #ifdef DEBUG
90     callGuard->call(rt, function, args...);
91 #else
92     function.asObject(rt).asFunction(rt).call(rt, args...);
93 #endif
94   }
95 };
96 
97 class Shareable {
98  protected:
99   virtual jsi::Value toJSValue(jsi::Runtime &rt) = 0;
100 
101  public:
102   virtual ~Shareable();
103 
104   enum ValueType {
105     UndefinedType,
106     NullType,
107     BooleanType,
108     NumberType,
109     // SymbolType, TODO
110     // BigIntType, TODO
111     StringType,
112     ObjectType,
113     ArrayType,
114     WorkletType,
115     RemoteFunctionType,
116     HandleType,
117     SynchronizedDataHolder,
118     HostObjectType,
119   };
120 
121   explicit Shareable(ValueType valueType) : valueType_(valueType) {}
122   virtual jsi::Value getJSValue(jsi::Runtime &rt) {
123     return toJSValue(rt);
124   }
125 
126   inline ValueType valueType() const {
127     return valueType_;
128   }
129 
130   static std::shared_ptr<Shareable> undefined();
131 
132  protected:
133   ValueType valueType_;
134 };
135 
136 template <typename BaseClass>
137 class RetainingShareable : virtual public BaseClass {
138  private:
139   std::shared_ptr<JSRuntimeHelper> runtimeHelper_;
140   std::unique_ptr<jsi::Value> remoteValue_;
141 
142  public:
143   template <typename... Args>
144   RetainingShareable(
145       const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
146       Args &&...args)
147       : BaseClass(std::forward<Args>(args)...), runtimeHelper_(runtimeHelper) {}
148   jsi::Value getJSValue(jsi::Runtime &rt) {
149     if (runtimeHelper_->isRNRuntime(rt)) {
150       // TODO: it is suboptimal to generate new object every time getJS is
151       // called on host runtime – the objects we are generating already exists
152       // and we should possibly just grab a hold of such object and use it here
153       // instead of creating a new JS representation. As far as I understand the
154       // only case where it can be realistically called this way is when a
155       // shared value is created and then accessed on the same runtime
156       return BaseClass::toJSValue(rt);
157     } else if (remoteValue_ == nullptr) {
158       auto value = BaseClass::toJSValue(rt);
159       remoteValue_ = std::make_unique<jsi::Value>(rt, value);
160       return value;
161     }
162     return jsi::Value(rt, *remoteValue_);
163   }
164   ~RetainingShareable() {
165     if (runtimeHelper_->uiRuntimeDestroyed) {
166       // The below use of unique_ptr.release prevents the smart pointer from
167       // calling the destructor of the kept object. This effectively results in
168       // leaking some memory. We do this on purpose, as sometimes we would keep
169       // references to JSI objects past the lifetime of its runtime (e.g.,
170       // shared values references from the RN VM holds reference to JSI objects
171       // on the UI runtime). When the UI runtime is terminated, the orphaned JSI
172       // objects would crash the app when their destructors are called, because
173       // they call into a memory that's managed by the terminated runtime. We
174       // accept the tradeoff of leaking memory here, as it has a limited impact.
175       // This scenario can only occur when the React instance is torn down which
176       // happens in development mode during app reloads, or in production when
177       // the app is being shut down gracefully by the system. An alternative
178       // solution would require us to keep track of all JSI values that are in
179       // use which would require additional data structure and compute spent on
180       // bookkeeping that only for the sake of destroying the values in time
181       // before the runtime is terminated. Note that the underlying memory that
182       // jsi::Value refers to is managed by the VM and gets freed along with the
183       // runtime.
184       remoteValue_.release();
185     }
186   }
187 };
188 
189 class ShareableJSRef : public jsi::HostObject {
190  private:
191   std::shared_ptr<Shareable> value_;
192 
193  public:
194   explicit ShareableJSRef(std::shared_ptr<Shareable> value) : value_(value) {}
195   std::shared_ptr<Shareable> value() const {
196     return value_;
197   }
198 
199   static jsi::Object newHostObject(
200       jsi::Runtime &rt,
201       const std::shared_ptr<Shareable> &value) {
202     return jsi::Object::createFromHostObject(
203         rt, std::make_shared<ShareableJSRef>(value));
204   }
205 };
206 
207 std::shared_ptr<Shareable> extractShareableOrThrow(
208     jsi::Runtime &rt,
209     const jsi::Value &maybeShareableValue,
210     const char *errorMessage = nullptr);
211 
212 template <typename T>
213 std::shared_ptr<T> extractShareableOrThrow(
214     jsi::Runtime &rt,
215     const jsi::Value &shareableRef,
216     const char *errorMessage = nullptr) {
217   auto res = std::dynamic_pointer_cast<T>(
218       extractShareableOrThrow(rt, shareableRef, errorMessage));
219   if (!res) {
220     throw new std::runtime_error(
221         errorMessage != nullptr
222             ? errorMessage
223             : "provided shareable object is of an incompatible type");
224   }
225   return res;
226 }
227 
228 class ShareableArray : public Shareable {
229  public:
230   ShareableArray(jsi::Runtime &rt, const jsi::Array &array);
231 
232   jsi::Value toJSValue(jsi::Runtime &rt) override {
233     auto size = data_.size();
234     auto ary = jsi::Array(rt, size);
235     for (size_t i = 0; i < size; i++) {
236       ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt));
237     }
238     return ary;
239   }
240 
241  protected:
242   std::vector<std::shared_ptr<Shareable>> data_;
243 };
244 
245 class ShareableObject : public Shareable {
246  public:
247   ShareableObject(jsi::Runtime &rt, const jsi::Object &object);
248   jsi::Value toJSValue(jsi::Runtime &rt) override {
249     auto obj = jsi::Object(rt);
250     for (size_t i = 0, size = data_.size(); i < size; i++) {
251       obj.setProperty(
252           rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt));
253     }
254     return obj;
255   }
256 
257  protected:
258   std::vector<std::pair<std::string, std::shared_ptr<Shareable>>> data_;
259 };
260 
261 class ShareableHostObject : public Shareable {
262  public:
263   ShareableHostObject(
264       const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
265       jsi::Runtime &rt,
266       const std::shared_ptr<jsi::HostObject> &hostObject)
267       : Shareable(HostObjectType), hostObject_(hostObject) {}
268   jsi::Value toJSValue(jsi::Runtime &rt) override {
269     return jsi::Object::createFromHostObject(rt, hostObject_);
270   }
271 
272  protected:
273   std::shared_ptr<jsi::HostObject> hostObject_;
274 };
275 
276 class ShareableWorklet : public ShareableObject {
277  private:
278   std::shared_ptr<JSRuntimeHelper> runtimeHelper_;
279 
280  public:
281   ShareableWorklet(
282       const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
283       jsi::Runtime &rt,
284       const jsi::Object &worklet)
285       : ShareableObject(rt, worklet), runtimeHelper_(runtimeHelper) {
286     valueType_ = WorkletType;
287   }
288   jsi::Value toJSValue(jsi::Runtime &rt) override {
289     jsi::Value obj = ShareableObject::toJSValue(rt);
290     return runtimeHelper_->valueUnpacker->call(rt, obj);
291   }
292 };
293 
294 class ShareableRemoteFunction
295     : public Shareable,
296       public std::enable_shared_from_this<ShareableRemoteFunction> {
297  private:
298   jsi::Function function_;
299   std::shared_ptr<JSRuntimeHelper> runtimeHelper_;
300 
301  public:
302   ShareableRemoteFunction(
303       const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
304       jsi::Runtime &rt,
305       jsi::Function &&function)
306       : Shareable(RemoteFunctionType),
307         function_(std::move(function)),
308         runtimeHelper_(runtimeHelper) {}
309   jsi::Value toJSValue(jsi::Runtime &rt) override {
310     if (runtimeHelper_->isUIRuntime(rt)) {
311 #ifdef DEBUG
312       return runtimeHelper_->valueUnpacker->call(
313           rt,
314           ShareableJSRef::newHostObject(rt, shared_from_this()),
315           jsi::String::createFromAscii(rt, "RemoteFunction"));
316 #else
317       return ShareableJSRef::newHostObject(rt, shared_from_this());
318 #endif
319     } else {
320       return jsi::Value(rt, function_);
321     }
322   }
323 };
324 
325 class ShareableHandle : public Shareable {
326  private:
327   std::shared_ptr<JSRuntimeHelper> runtimeHelper_;
328   std::unique_ptr<ShareableObject> initializer_;
329   std::unique_ptr<jsi::Value> remoteValue_;
330 
331  public:
332   ShareableHandle(
333       const std::shared_ptr<JSRuntimeHelper> runtimeHelper,
334       jsi::Runtime &rt,
335       const jsi::Object &initializerObject)
336       : Shareable(HandleType), runtimeHelper_(runtimeHelper) {
337     initializer_ = std::make_unique<ShareableObject>(rt, initializerObject);
338   }
339   ~ShareableHandle() {
340     if (runtimeHelper_->uiRuntimeDestroyed) {
341       // The below use of unique_ptr.release prevents the smart pointer from
342       // calling the destructor of the kept object. This effectively results in
343       // leaking some memory. We do this on purpose, as sometimes we would keep
344       // references to JSI objects past the lifetime of its runtime (e.g.,
345       // shared values references from the RN VM holds reference to JSI objects
346       // on the UI runtime). When the UI runtime is terminated, the orphaned JSI
347       // objects would crash the app when their destructors are called, because
348       // they call into a memory that's managed by the terminated runtime. We
349       // accept the tradeoff of leaking memory here, as it has a limited impact.
350       // This scenario can only occur when the React instance is torn down which
351       // happens in development mode during app reloads, or in production when
352       // the app is being shut down gracefully by the system. An alternative
353       // solution would require us to keep track of all JSI values that are in
354       // use which would require additional data structure and compute spent on
355       // bookkeeping that only for the sake of destroying the values in time
356       // before the runtime is terminated. Note that the underlying memory that
357       // jsi::Value refers to is managed by the VM and gets freed along with the
358       // runtime.
359       remoteValue_.release();
360     }
361   }
362   jsi::Value toJSValue(jsi::Runtime &rt) override {
363     if (initializer_ != nullptr) {
364       auto initObj = initializer_->getJSValue(rt);
365       remoteValue_ = std::make_unique<jsi::Value>(
366           runtimeHelper_->valueUnpacker->call(rt, initObj));
367       initializer_ = nullptr; // we can release ref to initializer as this
368                               // method should be called at most once
369     }
370     return jsi::Value(rt, *remoteValue_);
371   }
372 };
373 
374 class ShareableSynchronizedDataHolder
375     : public Shareable,
376       public std::enable_shared_from_this<ShareableSynchronizedDataHolder> {
377  private:
378   std::shared_ptr<JSRuntimeHelper> runtimeHelper_;
379   std::shared_ptr<Shareable> data_;
380   std::shared_ptr<jsi::Value> uiValue_;
381   std::shared_ptr<jsi::Value> rnValue_;
382   std::mutex dataAccessMutex_; // Protects `data_`.
383 
384  public:
385   ShareableSynchronizedDataHolder(
386       std::shared_ptr<JSRuntimeHelper> runtimeHelper,
387       jsi::Runtime &rt,
388       const jsi::Value &initialValue)
389       : Shareable(SynchronizedDataHolder),
390         runtimeHelper_(runtimeHelper),
391         data_(extractShareableOrThrow(rt, initialValue)) {}
392 
393   jsi::Value get(jsi::Runtime &rt) {
394     std::unique_lock<std::mutex> read_lock(dataAccessMutex_);
395     if (runtimeHelper_->isUIRuntime(rt)) {
396       if (uiValue_ == nullptr) {
397         auto value = data_->getJSValue(rt);
398         uiValue_ = std::make_shared<jsi::Value>(rt, value);
399         return value;
400       } else {
401         return jsi::Value(rt, *uiValue_);
402       }
403     } else {
404       if (rnValue_ == nullptr) {
405         auto value = data_->getJSValue(rt);
406         rnValue_ = std::make_shared<jsi::Value>(rt, value);
407         return value;
408       } else {
409         return jsi::Value(rt, *rnValue_);
410       }
411     }
412   }
413 
414   void set(jsi::Runtime &rt, const jsi::Value &data) {
415     std::unique_lock<std::mutex> write_lock(dataAccessMutex_);
416     data_ = extractShareableOrThrow(rt, data);
417     uiValue_.reset();
418     rnValue_.reset();
419   }
420 
421   jsi::Value toJSValue(jsi::Runtime &rt) override {
422     return ShareableJSRef::newHostObject(rt, shared_from_this());
423   };
424 };
425 
426 class ShareableString : public Shareable {
427  public:
428   explicit ShareableString(const std::string &string)
429       : Shareable(StringType), data_(string) {}
430   jsi::Value toJSValue(jsi::Runtime &rt) override {
431     return jsi::String::createFromUtf8(rt, data_);
432   }
433 
434  protected:
435   std::string data_;
436 };
437 
438 class ShareableScalar : public Shareable {
439  public:
440   explicit ShareableScalar(double number) : Shareable(NumberType) {
441     data_.number = number;
442   }
443   explicit ShareableScalar(bool boolean) : Shareable(BooleanType) {
444     data_.boolean = boolean;
445   }
446   ShareableScalar() : Shareable(UndefinedType) {}
447   explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {}
448 
449   jsi::Value toJSValue(jsi::Runtime &rt) override {
450     switch (valueType_) {
451       case Shareable::UndefinedType:
452         return jsi::Value();
453       case Shareable::NullType:
454         return jsi::Value(nullptr);
455       case Shareable::BooleanType:
456         return jsi::Value(data_.boolean);
457       case Shareable::NumberType:
458         return jsi::Value(data_.number);
459       default:
460         throw std::runtime_error(
461             "attempted to convert object that's not of a scalar type");
462     }
463   }
464 
465  protected:
466   union Data {
467     bool boolean;
468     double number;
469   };
470 
471  private:
472   Data data_;
473 };
474 
475 } // namespace reanimated
476