Skip to content

Commit 3e22b83

Browse files
committed
Merge branch 'main' of https://github.com/mrousavy/nitro
2 parents 316922e + a06c7d4 commit 3e22b83

File tree

7 files changed

+300
-53
lines changed

7 files changed

+300
-53
lines changed

docs/docs/types/array-buffers.md

Lines changed: 133 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,44 +44,149 @@ It is important to understand the ownership, and threading concerns around such
4444

4545
## Ownership
4646

47-
- An `ArrayBuffer` that was created on the native side is **owning**, which means you can safely access it's data as long as the `ArrayBuffer` reference is alive.
47+
There's two types of `ArrayBuffer`s, **owning** and **non-owning**:
48+
49+
### Owning
50+
51+
An `ArrayBuffer` that was created on the native side is **owning**, which means you can safely access it's data as long as the `ArrayBuffer` reference is alive.
4852
It can be safely held strong for longer, e.g. as a class property/member, and accessed from different Threads.
4953

50-
```swift
51-
func doSomething() -> ArrayBufferHolder {
52-
let buffer = ArrayBufferHolder.allocate(1024 * 10)
53-
let data = buffer.data // <-- ✅ safe to do because we own it!
54-
self.buffer = buffer // <-- ✅ safe to do to use it later!
55-
DispatchQueue.global().async {
56-
let data = buffer.data // <-- ✅ also safe because we own it!
57-
}
58-
return buffer
54+
```swift
55+
func doSomething() -> ArrayBufferHolder {
56+
// highlight-next-line
57+
let buffer = ArrayBufferHolder.allocate(1024 * 10)
58+
let data = buffer.data // <-- ✅ safe to do because we own it!
59+
self.buffer = buffer // <-- ✅ safe to use it later!
60+
DispatchQueue.global().async {
61+
let data = buffer.data // <-- ✅ also safe because we own it!
5962
}
60-
```
63+
return buffer
64+
}
65+
```
66+
67+
### Non-owning
6168

62-
- An `ArrayBuffer` that was received as a parameter from JS cannot be safely kept strong as the JS VM can delete it at any point, hence it is **non-owning**.
69+
An `ArrayBuffer` that was received as a parameter from JS cannot be safely kept strong as the JS VM can delete it at any point, hence it is **non-owning**.
6370
It's data can only be safely accessed before the synchronous function returned, as this will stay within the JS bounds.
6471

65-
```swift
66-
func doSomething(buffer: ArrayBufferHolder) {
67-
let data = buffer.data // <-- ✅ safe to do because we're still sync
68-
DispatchQueue.global().async {
69-
// code-error
70-
let data = buffer.data // <-- ❌ NOT safe
71-
}
72+
```swift
73+
func doSomething(buffer: ArrayBufferHolder) {
74+
let data = buffer.data // <-- ✅ safe to do because we're still sync
75+
DispatchQueue.global().async {
76+
// code-error
77+
let data = buffer.data // <-- ❌ NOT safe
7278
}
73-
```
74-
If you need a non-owning buffer's data for longer, **copy it first**:
75-
```swift
76-
func doSomething(buffer: ArrayBufferHolder) {
77-
let copy = ArrayBufferHolder.copy(of: buffer)
78-
DispatchQueue.global().async {
79-
let data = copy.data // <-- ✅ safe now because we have a owning copy
80-
}
79+
}
80+
```
81+
If you need a non-owning buffer's data for longer, **copy it first**:
82+
```swift
83+
func doSomething(buffer: ArrayBufferHolder) {
84+
// diff-add
85+
let copy = ArrayBufferHolder.copy(of: buffer)
86+
let data = copy.data // <-- ✅ safe now because we have a owning copy
87+
DispatchQueue.global().async {
88+
let data = copy.data // <-- ✅ still safe now because we have a owning copy
8189
}
82-
```
90+
}
91+
```
8392

8493
## Threading
8594

8695
An `ArrayBuffer` can be accessed from both JS and native, and even from multiple Threads at once, but they are **not thread-safe**.
8796
To prevent race conditions or garbage-data from being read, make sure to not read from- and write to- the `ArrayBuffer` at the same time.
97+
98+
## Creating Buffers
99+
100+
Buffers can either be created from native (**owning**), or from JS (**non-owning**).
101+
102+
### From native
103+
104+
On the native side, an **owning** `ArrayBuffer` can either **wrap-**, or **copy-** an existing buffer:
105+
106+
<Tabs>
107+
<TabItem value="cpp" label="C++">
108+
```cpp
109+
auto myData = new uint8_t*[4096];
110+
111+
// wrap (no copy)
112+
auto wrappingArrayBuffer = ArrayBuffer::wrap(myData, 4096, [=]() {
113+
delete[] myData;
114+
});
115+
// copy
116+
auto copiedArrayBuffer = ArrayBuffer::copy(myData, 4096);
117+
// new blank buffer
118+
auto newArrayBuffer = ArrayBuffer::allocate(4096);
119+
```
120+
</TabItem>
121+
<TabItem value="swift" label="Swift">
122+
```swift
123+
let myData = UnsafeMutablePointer<UInt8>.allocate(capacity: 4096)
124+
125+
// wrap (no copy)
126+
let wrappingArrayBuffer = ArrayBuffer.wrap(dataWithoutCopy: myData,
127+
size: 4096,
128+
onDelete: { myData.deallocate() })
129+
// copy
130+
let copiedArrayBuffer = ArrayBuffer.copy(of: wrappingArrayBuffer)
131+
// new blank buffer
132+
let newArrayBuffer = ArrayBuffer.allocate(size: 4096)
133+
```
134+
</TabItem>
135+
<TabItem value="kotlin" label="Kotlin">
136+
```kotlin
137+
val myData = ByteBuffer.allocateDirect(4096)
138+
139+
// wrap (no copy)
140+
val wrappingArrayBuffer = ArrayBuffer.wrap(myData)
141+
142+
143+
// copy
144+
let copiedArrayBuffer = ArrayBuffer.copy(myData)
145+
// new blank buffer
146+
val newArrayBuffer = ArrayBuffer.allocate(4096)
147+
```
148+
</TabItem>
149+
</Tabs>
150+
151+
#### Language-native buffer types
152+
153+
ArrayBuffers also provide helper and conversion methods for the language-native conventional buffer types:
154+
155+
<Tabs>
156+
<TabItem value="cpp" label="C++">
157+
C++ often uses [`std::vector<uint8_t>`](https://en.cppreference.com/w/cpp/container/vector) to represent Data.
158+
```cpp
159+
std::vector<uint8_t> data;
160+
auto buffer = ArrayBuffer::copy(data);
161+
/* convert back to vector would be a copy. */
162+
```
163+
</TabItem>
164+
<TabItem value="swift" label="Swift">
165+
Swift often uses [`Data`](https://developer.apple.com/documentation/foundation/data) to represent Data.
166+
```swift
167+
let data = Data(capacity: 1024)
168+
let buffer = ArrayBufferHolder.copy(data: data)
169+
let dataAgain = buffer.toData(copyIfNeeded: true)
170+
```
171+
</TabItem>
172+
<TabItem value="kotlin" label="Kotlin">
173+
Kotlin often uses [`ByteBuffer`](https://developer.android.com/reference/java/nio/ByteBuffer) to represent Data.
174+
```kotlin
175+
val data = ByteBuffer.allocateDirect(1024)
176+
val buffer = ArrayBuffer.copy(data)
177+
val dataAgain = buffer.getBuffer(copyIfNeeded = true)
178+
```
179+
</TabItem>
180+
</Tabs>
181+
182+
### From JS
183+
184+
From JS, a **non-owning** `ArrayBuffer` can be created via the [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) web APIs, and viewed or edited using the typed array APIs (e.g. [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)).
185+
186+
```ts
187+
const arrayBuffer = new ArrayBuffer(4096)
188+
const view = new Uint8Array(arrayBuffer)
189+
view[0] = 64
190+
view[1] = 128
191+
view[2] = 255
192+
```

packages/react-native-nitro-image/ios/HybridTestObjectSwift.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,8 @@ class HybridTestObjectSwift : HybridTestObjectSwiftKotlinSpec {
262262
}
263263

264264
func getBufferLastItem(buffer: ArrayBufferHolder) throws -> Double {
265-
let lastBytePointer = buffer.data.advanced(by: buffer.size - 1)
266-
let lastByte = lastBytePointer.load(as: UInt8.self)
267-
return Double(lastByte)
265+
let lastByte = buffer.data.advanced(by: buffer.size - 1)
266+
return Double(lastByte.pointee)
268267
}
269268

270269
func setAllValuesTo(buffer: ArrayBufferHolder, value: Double) throws {

packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/core/ArrayBuffer.kt

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,40 @@ class ArrayBuffer {
107107
/**
108108
* Copy the given `ArrayBuffer` into a new **owning** `ArrayBuffer`.
109109
*/
110-
fun copyOf(other: ArrayBuffer): ArrayBuffer {
111-
// 1. Create a new buffer with the same size as the other
112-
val newBuffer = ByteBuffer.allocateDirect(other.size)
113-
// 2. Prepare the source buffer
114-
val originalBuffer = other.getBuffer(false)
115-
originalBuffer.rewind()
110+
fun copy(other: ArrayBuffer): ArrayBuffer {
111+
val byteBuffer = other.getBuffer(false)
112+
return copy(byteBuffer)
113+
}
114+
115+
/**
116+
* Copy the given `ByteBuffer` into a new **owning** `ArrayBuffer`.
117+
*/
118+
fun copy(byteBuffer: ByteBuffer): ArrayBuffer {
119+
// 1. Find out size
120+
byteBuffer.rewind()
121+
val size = byteBuffer.remaining()
122+
// 2. Create a new buffer with the same size as the other
123+
val newBuffer = ByteBuffer.allocateDirect(size)
116124
// 3. Copy over the source buffer into the new buffer
117-
newBuffer.put(originalBuffer)
125+
newBuffer.put(byteBuffer)
118126
// 4. Rewind both buffers again to index 0
119127
newBuffer.rewind()
120-
originalBuffer.rewind()
128+
byteBuffer.rewind()
121129
// 5. Create a new `ArrayBuffer`
122130
return ArrayBuffer(newBuffer)
123131
}
132+
133+
/**
134+
* Wrap the given `ByteBuffer` in a new **owning** `ArrayBuffer`.
135+
*/
136+
fun wrap(byteBuffer: ByteBuffer): ArrayBuffer {
137+
byteBuffer.rewind()
138+
return ArrayBuffer(byteBuffer)
139+
}
140+
141+
@Deprecated("Use copy(...) instead", level = DeprecationLevel.WARNING)
142+
fun copyOf(other: ArrayBuffer): ArrayBuffer {
143+
return copy(other)
144+
}
124145
}
125146
}

packages/react-native-nitro-modules/cpp/core/ArrayBuffer.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,25 @@ using namespace facebook;
1717

1818
// 1. ArrayBuffer
1919

20-
std::shared_ptr<ArrayBuffer> ArrayBuffer::makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc) {
20+
std::shared_ptr<ArrayBuffer> ArrayBuffer::wrap(uint8_t* data, size_t size, DeleteFn&& deleteFunc) {
2121
return std::make_shared<NativeArrayBuffer>(data, size, std::move(deleteFunc));
2222
}
2323

24+
std::shared_ptr<ArrayBuffer> ArrayBuffer::copy(uint8_t* data, size_t size) {
25+
uint8_t* copy = new uint8_t[size];
26+
std::memcpy(copy, data, size);
27+
return ArrayBuffer::wrap(copy, size, [=]() { delete[] copy; });
28+
}
29+
30+
std::shared_ptr<ArrayBuffer> ArrayBuffer::copy(std::vector<uint8_t>& data) {
31+
return ArrayBuffer::copy(data.data(), data.size());
32+
}
33+
34+
std::shared_ptr<ArrayBuffer> ArrayBuffer::allocate(size_t size) {
35+
uint8_t* data = new uint8_t[size];
36+
return ArrayBuffer::wrap(data, size, [=]() { delete[] data; });
37+
}
38+
2439
// 2. NativeArrayBuffer
2540

2641
NativeArrayBuffer::NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc)

packages/react-native-nitro-modules/cpp/core/ArrayBuffer.hpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "OwningReference.hpp"
1111
#include <jsi/jsi.h>
1212
#include <thread>
13+
#include <vector>
1314

1415
namespace margelo::nitro {
1516

@@ -52,7 +53,25 @@ class ArrayBuffer : public jsi::MutableBuffer {
5253
* Create a new `NativeArrayBuffer` that wraps the given data (without copy) of the given size,
5354
* and calls `deleteFunc` in which `data` should be deleted.
5455
*/
55-
static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
56+
static std::shared_ptr<ArrayBuffer> wrap(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
57+
/**
58+
* Create a new `NativeArrayBuffer` that copies the given data of the given size
59+
* into a newly allocated buffer.
60+
*/
61+
static std::shared_ptr<ArrayBuffer> copy(uint8_t* data, size_t size);
62+
/**
63+
* Create a new `NativeArrayBuffer` that copies the given `std::vector`.
64+
*/
65+
static std::shared_ptr<ArrayBuffer> copy(std::vector<uint8_t>& data);
66+
/**
67+
* Create a new `NativeArrayBuffer` that allocates a new buffer of the given size.
68+
*/
69+
static std::shared_ptr<ArrayBuffer> allocate(size_t size);
70+
71+
[[deprecated("Use wrapBuffer(...) instead.")]]
72+
static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc) {
73+
return ArrayBuffer::wrap(data, size, std::move(deleteFunc));
74+
}
5675
};
5776

5877
/**

packages/react-native-nitro-modules/ios/core/ArrayBufferHolder.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ class ArrayBufferHolder {
3535
* Once the `ArrayBuffer` is no longer in use, the given `deleteFunc` will be called with the given `deleteFuncContext`
3636
* as an argument. The caller is responsible for deleting `data` once this is called.
3737
*/
38-
static ArrayBufferHolder makeBuffer(uint8_t* _Nonnull data, size_t size, SwiftClosure destroy) {
38+
static ArrayBufferHolder wrap(uint8_t* _Nonnull data, size_t size, SwiftClosure destroy) {
3939
std::function<void()> deleteFunc = destroy.getFunction();
40-
auto arrayBuffer = ArrayBuffer::makeBuffer(data, size, std::move(deleteFunc));
40+
auto arrayBuffer = ArrayBuffer::wrap(data, size, std::move(deleteFunc));
4141
return ArrayBufferHolder(arrayBuffer);
4242
}
4343

4444
public:
4545
/**
4646
* Gets the raw bytes the underlying `ArrayBuffer` points to.
4747
*/
48-
void* _Nonnull getData() const SWIFT_COMPUTED_PROPERTY {
48+
uint8_t* _Nonnull getData() const SWIFT_COMPUTED_PROPERTY {
4949
return _arrayBuffer->data();
5050
}
5151
/**

0 commit comments

Comments
 (0)