1 #pragma once
2 
3 #include <jsi/jsi.h>
4 
5 #include <memory>
6 #include <unordered_map>
7 #include <utility>
8 
9 #include "RuntimeLifecycleMonitor.h"
10 
11 namespace RNJsi {
12 
13 namespace jsi = facebook::jsi;
14 
15 class BaseRuntimeAwareCache {
16 public:
setMainJsRuntime(jsi::Runtime * rt)17   static void setMainJsRuntime(jsi::Runtime *rt) { _mainRuntime = rt; }
18 
19 protected:
getMainJsRuntime()20   static jsi::Runtime *getMainJsRuntime() {
21     assert(_mainRuntime != nullptr &&
22            "Expected main Javascript runtime to be set in the "
23            "BaseRuntimeAwareCache class.");
24 
25     return _mainRuntime;
26   }
27 
28 private:
29   static jsi::Runtime *_mainRuntime;
30 };
31 
32 /**
33  * Provides a way to keep data specific to a jsi::Runtime instance that gets
34  * cleaned up when that runtime is destroyed. This is necessary because JSI does
35  * not allow for its associated objects to be retained past the runtime
36  * lifetime. If an object (e.g. jsi::Values or jsi::Function instances) is kept
37  * after the runtime is torn down, its destructor (once it is destroyed
38  * eventually) will result in a crash (JSI objects keep a pointer to memory
39  * managed by the runtime, accessing that portion of the memory after runtime is
40  * deleted is the root cause of that crash).
41  *
42  * In order to provide an efficient implementation that does not add an overhead
43  * for the cases when only a single runtiome is used, which is the primary
44  * usecase, the following assumption has been made: Only for secondary runtimes
45  * we track destruction and clean up the store associated with that runtime. For
46  * the first runtime we assume that the object holding the store is destroyed
47  * prior to the destruction of that runtime.
48  *
49  * The above assumption makes it work without any overhead when only single
50  * runtime is in use. Specifically, we don't perform any additional operations
51  * related to tracking runtime lifecycle when only a single runtime is used.
52  */
53 template <typename T>
54 class RuntimeAwareCache : public BaseRuntimeAwareCache,
55                           public RuntimeLifecycleListener {
56 
57 public:
onRuntimeDestroyed(jsi::Runtime * rt)58   void onRuntimeDestroyed(jsi::Runtime *rt) override {
59     if (getMainJsRuntime() != rt) {
60       // We are removing a secondary runtime
61       _secondaryRuntimeCaches.erase(rt);
62     }
63   }
64 
~RuntimeAwareCache()65   ~RuntimeAwareCache() {
66     for (auto &cache : _secondaryRuntimeCaches) {
67       RuntimeLifecycleMonitor::removeListener(
68           *static_cast<jsi::Runtime *>(cache.first), this);
69     }
70   }
71 
get(jsi::Runtime & rt)72   T &get(jsi::Runtime &rt) {
73     // We check if we're accessing the main runtime - this is the happy path
74     // to avoid us having to lookup by runtime for caches that only has a single
75     // runtime
76     if (getMainJsRuntime() == &rt) {
77       return _primaryCache;
78     } else {
79       if (_secondaryRuntimeCaches.count(&rt) == 0) {
80         // we only add listener when the secondary runtime is used, this assumes
81         // that the secondary runtime is terminated first. This lets us avoid
82         // additional complexity for the majority of cases when objects are not
83         // shared between runtimes. Otherwise we'd have to register all objecrts
84         // with the RuntimeMonitor as opposed to only registering ones that are
85         // used in secondary runtime. Note that we can't register listener here
86         // with the primary runtime as it may run on a separate thread.
87         RuntimeLifecycleMonitor::addListener(rt, this);
88 
89         T cache;
90         _secondaryRuntimeCaches.emplace(&rt, std::move(cache));
91       }
92     }
93     return _secondaryRuntimeCaches.at(&rt);
94   }
95 
96 private:
97   std::unordered_map<void *, T> _secondaryRuntimeCaches;
98   T _primaryCache;
99 };
100 
101 } // namespace RNJsi
102