1 #include "EXTypedArrayApi.h"
2 
3 #include <unordered_map>
4 
5 namespace expo {
6 namespace gl_cpp {
7 
8 template <TypedArrayKind T>
9 using ContentType = typename typedArrayTypeMap<T>::type;
10 
11 enum class Prop {
12   Buffer, // "buffer"
13   Constructor, // "constructor"
14   Name, // "name"
15   Proto, // "__proto__"
16   Length, // "length"
17   ByteLength, // "byteLength"
18   ByteOffset, // "offset"
19   IsView, // "isView"
20   ArrayBuffer, // "ArrayBuffer"
21   Int8Array, // "Int8Array"
22   Int16Array, // "Int16Array"
23   Int32Array, // "Int32Array"
24   Uint8Array, // "Uint8Array"
25   Uint8ClampedArray, // "Uint8ClampedArray"
26   Uint16Array, // "Uint16Array"
27   Uint32Array, // "Uint32Array"
28   Float32Array, // "Float32Array"
29   Float64Array, // "Float64Array"
30 };
31 
32 class PropNameIDCache {
33  public:
get(jsi::Runtime & runtime,Prop prop)34   const jsi::PropNameID &get(jsi::Runtime &runtime, Prop prop) {
35     auto key = reinterpret_cast<uintptr_t>(&runtime);
36     if (this->props.find(key) == this->props.end()) {
37       this->props[key] = std::unordered_map<Prop, std::unique_ptr<jsi::PropNameID>>();
38     }
39     if (!this->props[key][prop]) {
40       this->props[key][prop] = std::make_unique<jsi::PropNameID>(createProp(runtime, prop));
41     }
42     return *(this->props[key][prop]);
43   }
44 
45   const jsi::PropNameID &getConstructorNameProp(jsi::Runtime &runtime, TypedArrayKind kind);
46 
invalidate(uintptr_t key)47   void invalidate(uintptr_t key) {
48     if (props.find(key) != props.end()) {
49       props[key].clear();
50     }
51   }
52 
53  private:
54   std::unordered_map<uintptr_t, std::unordered_map<Prop, std::unique_ptr<jsi::PropNameID>>> props;
55 
56   jsi::PropNameID createProp(jsi::Runtime &runtime, Prop prop);
57 };
58 
59 PropNameIDCache propNameIDCache;
60 
InvalidateCacheOnDestroy(jsi::Runtime & runtime)61 InvalidateCacheOnDestroy::InvalidateCacheOnDestroy(jsi::Runtime &runtime) {
62   key = reinterpret_cast<uintptr_t>(&runtime);
63 }
~InvalidateCacheOnDestroy()64 InvalidateCacheOnDestroy::~InvalidateCacheOnDestroy() {
65   propNameIDCache.invalidate(key);
66 }
67 
68 TypedArrayKind getTypedArrayKindForName(const std::string &name);
69 
TypedArrayBase(jsi::Runtime & runtime,size_t size,TypedArrayKind kind)70 TypedArrayBase::TypedArrayBase(jsi::Runtime &runtime, size_t size, TypedArrayKind kind)
71     : TypedArrayBase(
72           runtime,
73           runtime.global()
74               .getProperty(runtime, propNameIDCache.getConstructorNameProp(runtime, kind))
75               .asObject(runtime)
76               .asFunction(runtime)
77               .callAsConstructor(runtime, {static_cast<double>(size)})
78               .asObject(runtime)) {}
79 
TypedArrayBase(jsi::Runtime & runtime,const jsi::Object & obj)80 TypedArrayBase::TypedArrayBase(jsi::Runtime &runtime, const jsi::Object &obj)
81     : jsi::Object(jsi::Value(runtime, obj).asObject(runtime)) {}
82 
getKind(jsi::Runtime & runtime) const83 TypedArrayKind TypedArrayBase::getKind(jsi::Runtime &runtime) const {
84   auto constructorName = this->getProperty(runtime, propNameIDCache.get(runtime, Prop::Constructor))
85                              .asObject(runtime)
86                              .getProperty(runtime, propNameIDCache.get(runtime, Prop::Name))
87                              .asString(runtime)
88                              .utf8(runtime);
89   return getTypedArrayKindForName(constructorName);
90 };
91 
size(jsi::Runtime & runtime) const92 size_t TypedArrayBase::size(jsi::Runtime &runtime) const {
93   return getProperty(runtime, propNameIDCache.get(runtime, Prop::Length)).asNumber();
94 }
95 
length(jsi::Runtime & runtime) const96 size_t TypedArrayBase::length(jsi::Runtime &runtime) const {
97   return getProperty(runtime, propNameIDCache.get(runtime, Prop::Length)).asNumber();
98 }
99 
byteLength(jsi::Runtime & runtime) const100 size_t TypedArrayBase::byteLength(jsi::Runtime &runtime) const {
101   return getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteLength)).asNumber();
102 }
103 
byteOffset(jsi::Runtime & runtime) const104 size_t TypedArrayBase::byteOffset(jsi::Runtime &runtime) const {
105   return getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteOffset)).asNumber();
106 }
107 
hasBuffer(jsi::Runtime & runtime) const108 bool TypedArrayBase::hasBuffer(jsi::Runtime &runtime) const {
109   auto buffer = getProperty(runtime, propNameIDCache.get(runtime, Prop::Buffer));
110   return buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime);
111 }
112 
toVector(jsi::Runtime & runtime)113 std::vector<uint8_t> TypedArrayBase::toVector(jsi::Runtime &runtime) {
114   auto start = reinterpret_cast<uint8_t *>(getBuffer(runtime).data(runtime) + byteOffset(runtime));
115   auto end = start + byteLength(runtime);
116   return std::vector<uint8_t>(start, end);
117 }
118 
getBuffer(jsi::Runtime & runtime) const119 jsi::ArrayBuffer TypedArrayBase::getBuffer(jsi::Runtime &runtime) const {
120   auto buffer = getProperty(runtime, propNameIDCache.get(runtime, Prop::Buffer));
121   if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) {
122     return buffer.asObject(runtime).getArrayBuffer(runtime);
123   } else {
124     throw std::runtime_error("no ArrayBuffer attached");
125   }
126 }
127 
isTypedArray(jsi::Runtime & runtime,const jsi::Object & jsObj)128 bool isTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj) {
129   auto jsVal = runtime.global()
130                    .getProperty(runtime, propNameIDCache.get(runtime, Prop::ArrayBuffer))
131                    .asObject(runtime)
132                    .getProperty(runtime, propNameIDCache.get(runtime, Prop::IsView))
133                    .asObject(runtime)
134                    .asFunction(runtime)
135                    .callWithThis(runtime, runtime.global(), {jsi::Value(runtime, jsObj)});
136   if (jsVal.isBool()) {
137     return jsVal.getBool();
138   } else {
139     throw std::runtime_error("value is not a boolean");
140   }
141 }
142 
getTypedArray(jsi::Runtime & runtime,const jsi::Object & jsObj)143 TypedArrayBase getTypedArray(jsi::Runtime &runtime, const jsi::Object &jsObj) {
144   auto jsVal = runtime.global()
145                    .getProperty(runtime, propNameIDCache.get(runtime, Prop::ArrayBuffer))
146                    .asObject(runtime)
147                    .getProperty(runtime, propNameIDCache.get(runtime, Prop::IsView))
148                    .asObject(runtime)
149                    .asFunction(runtime)
150                    .callWithThis(runtime, runtime.global(), {jsi::Value(runtime, jsObj)});
151   if (jsVal.isBool()) {
152     return TypedArrayBase(runtime, jsObj);
153   } else {
154     throw std::runtime_error("value is not a boolean");
155   }
156 }
157 
arrayBufferToVector(jsi::Runtime & runtime,jsi::Object & jsObj)158 std::vector<uint8_t> arrayBufferToVector(jsi::Runtime &runtime, jsi::Object &jsObj) {
159   if (!jsObj.isArrayBuffer(runtime)) {
160     throw std::runtime_error("Object is not an ArrayBuffer");
161   }
162   auto jsArrayBuffer = jsObj.getArrayBuffer(runtime);
163 
164   uint8_t *dataBlock = jsArrayBuffer.data(runtime);
165   size_t blockSize =
166       jsArrayBuffer.getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteLength)).asNumber();
167   return std::vector<uint8_t>(dataBlock, dataBlock + blockSize);
168 }
169 
arrayBufferUpdate(jsi::Runtime & runtime,jsi::ArrayBuffer & buffer,std::vector<uint8_t> data,size_t offset)170 void arrayBufferUpdate(
171     jsi::Runtime &runtime,
172     jsi::ArrayBuffer &buffer,
173     std::vector<uint8_t> data,
174     size_t offset) {
175   uint8_t *dataBlock = buffer.data(runtime);
176   size_t blockSize = buffer.size(runtime);
177   if (data.size() > blockSize) {
178     throw jsi::JSError(runtime, "ArrayBuffer is to small to fit data");
179   }
180   std::copy(data.begin(), data.end(), dataBlock + offset);
181 }
182 
183 template <TypedArrayKind T>
TypedArray(jsi::Runtime & runtime,size_t size)184 TypedArray<T>::TypedArray(jsi::Runtime &runtime, size_t size) : TypedArrayBase(runtime, size, T){};
185 
186 template <TypedArrayKind T>
TypedArray(jsi::Runtime & runtime,std::vector<ContentType<T>> data)187 TypedArray<T>::TypedArray(jsi::Runtime &runtime, std::vector<ContentType<T>> data)
188     : TypedArrayBase(runtime, data.size(), T) {
189   update(runtime, data);
190 };
191 
192 template <TypedArrayKind T>
TypedArray(TypedArrayBase && base)193 TypedArray<T>::TypedArray(TypedArrayBase &&base) : TypedArrayBase(std::move(base)) {}
194 
195 template <TypedArrayKind T>
toVector(jsi::Runtime & runtime)196 std::vector<ContentType<T>> TypedArray<T>::toVector(jsi::Runtime &runtime) {
197   auto start =
198       reinterpret_cast<ContentType<T> *>(getBuffer(runtime).data(runtime) + byteOffset(runtime));
199   auto end = start + size(runtime);
200   return std::vector<ContentType<T>>(start, end);
201 }
202 
203 template <TypedArrayKind T>
update(jsi::Runtime & runtime,const std::vector<ContentType<T>> & data)204 void TypedArray<T>::update(jsi::Runtime &runtime, const std::vector<ContentType<T>> &data) {
205   if (data.size() != size(runtime)) {
206     throw jsi::JSError(runtime, "TypedArray can only be updated with a vector of the same size");
207   }
208   uint8_t *rawData = getBuffer(runtime).data(runtime) + byteOffset(runtime);
209   std::copy(data.begin(), data.end(), reinterpret_cast<ContentType<T> *>(rawData));
210 }
211 
getConstructorNameProp(jsi::Runtime & runtime,TypedArrayKind kind)212 const jsi::PropNameID &PropNameIDCache::getConstructorNameProp(
213     jsi::Runtime &runtime,
214     TypedArrayKind kind) {
215   switch (kind) {
216     case TypedArrayKind::Int8Array:
217       return get(runtime, Prop::Int8Array);
218     case TypedArrayKind::Int16Array:
219       return get(runtime, Prop::Int16Array);
220     case TypedArrayKind::Int32Array:
221       return get(runtime, Prop::Int32Array);
222     case TypedArrayKind::Uint8Array:
223       return get(runtime, Prop::Uint8Array);
224     case TypedArrayKind::Uint8ClampedArray:
225       return get(runtime, Prop::Uint8ClampedArray);
226     case TypedArrayKind::Uint16Array:
227       return get(runtime, Prop::Uint16Array);
228     case TypedArrayKind::Uint32Array:
229       return get(runtime, Prop::Uint32Array);
230     case TypedArrayKind::Float32Array:
231       return get(runtime, Prop::Float32Array);
232     case TypedArrayKind::Float64Array:
233       return get(runtime, Prop::Float64Array);
234   }
235 }
236 
createProp(jsi::Runtime & runtime,Prop prop)237 jsi::PropNameID PropNameIDCache::createProp(jsi::Runtime &runtime, Prop prop) {
238   auto create = [&](const std::string &propName) {
239     return jsi::PropNameID::forUtf8(runtime, propName);
240   };
241   switch (prop) {
242     case Prop::Buffer:
243       return create("buffer");
244     case Prop::Constructor:
245       return create("constructor");
246     case Prop::Name:
247       return create("name");
248     case Prop::Proto:
249       return create("__proto__");
250     case Prop::Length:
251       return create("length");
252     case Prop::ByteLength:
253       return create("byteLength");
254     case Prop::ByteOffset:
255       return create("byteOffset");
256     case Prop::IsView:
257       return create("isView");
258     case Prop::ArrayBuffer:
259       return create("ArrayBuffer");
260     case Prop::Int8Array:
261       return create("Int8Array");
262     case Prop::Int16Array:
263       return create("Int16Array");
264     case Prop::Int32Array:
265       return create("Int32Array");
266     case Prop::Uint8Array:
267       return create("Uint8Array");
268     case Prop::Uint8ClampedArray:
269       return create("Uint8ClampedArray");
270     case Prop::Uint16Array:
271       return create("Uint16Array");
272     case Prop::Uint32Array:
273       return create("Uint32Array");
274     case Prop::Float32Array:
275       return create("Float32Array");
276     case Prop::Float64Array:
277       return create("Float64Array");
278   }
279 }
280 
281 std::unordered_map<std::string, TypedArrayKind> nameToKindMap = {
282     {"Int8Array", TypedArrayKind::Int8Array},
283     {"Int16Array", TypedArrayKind::Int16Array},
284     {"Int32Array", TypedArrayKind::Int32Array},
285     {"Uint8Array", TypedArrayKind::Uint8Array},
286     {"Uint8ClampedArray", TypedArrayKind::Uint8ClampedArray},
287     {"Uint16Array", TypedArrayKind::Uint16Array},
288     {"Uint32Array", TypedArrayKind::Uint32Array},
289     {"Float32Array", TypedArrayKind::Float32Array},
290     {"Float64Array", TypedArrayKind::Float64Array},
291 };
292 
getTypedArrayKindForName(const std::string & name)293 TypedArrayKind getTypedArrayKindForName(const std::string &name) {
294   return nameToKindMap.at(name);
295 }
296 
297 template class TypedArray<TypedArrayKind::Int8Array>;
298 template class TypedArray<TypedArrayKind::Int16Array>;
299 template class TypedArray<TypedArrayKind::Int32Array>;
300 template class TypedArray<TypedArrayKind::Uint8Array>;
301 template class TypedArray<TypedArrayKind::Uint8ClampedArray>;
302 template class TypedArray<TypedArrayKind::Uint16Array>;
303 template class TypedArray<TypedArrayKind::Uint32Array>;
304 template class TypedArray<TypedArrayKind::Float32Array>;
305 template class TypedArray<TypedArrayKind::Float64Array>;
306 
307 } // namespace gl_cpp
308 } // namespace expo
309