reactor-effect-native 0.2.0 → 0.3.0-rc.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.
- package/Dockerfile +4 -2
- package/README.md +90 -15
- package/dist/_internal/bridge.d.ts +70 -14
- package/dist/_internal/bridge.d.ts.map +1 -1
- package/dist/_internal/bridge.js +290 -145
- package/dist/_internal/bridge.js.map +1 -1
- package/dist/_internal/isolated/child.d.ts +2 -0
- package/dist/_internal/isolated/child.d.ts.map +1 -0
- package/dist/_internal/isolated/child.js +176 -0
- package/dist/_internal/isolated/child.js.map +1 -0
- package/dist/_internal/isolated/host.d.ts +147 -0
- package/dist/_internal/isolated/host.d.ts.map +1 -0
- package/dist/_internal/isolated/host.js +645 -0
- package/dist/_internal/isolated/host.js.map +1 -0
- package/dist/_internal/isolated/protocol.d.ts +399 -0
- package/dist/_internal/isolated/protocol.d.ts.map +1 -0
- package/dist/_internal/isolated/protocol.js +250 -0
- package/dist/_internal/isolated/protocol.js.map +1 -0
- package/dist/_internal/peer.d.ts +68 -13
- package/dist/_internal/peer.d.ts.map +1 -1
- package/dist/_internal/peer.js +233 -157
- package/dist/_internal/peer.js.map +1 -1
- package/dist/index.d.ts +19 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -33
- package/dist/index.js.map +1 -1
- package/dist/isolated.d.ts +25 -0
- package/dist/isolated.d.ts.map +1 -0
- package/dist/isolated.js +39 -0
- package/dist/isolated.js.map +1 -0
- package/lib/darwin-arm64/libreactor_effect_native.dylib +0 -0
- package/lib/darwin-arm64/native-identity.json +4 -3
- package/lib/linux-x64/libreactor_effect_native.so +0 -0
- package/lib/linux-x64/native-identity.json +4 -3
- package/package.json +7 -3
- package/rust/Cargo.toml +108 -2
- package/rust/build.rs +253 -114
- package/rust/clippy.toml +7 -0
- package/rust/include/reactor_effect_native.h +101 -42
- package/rust/src/abi.rs +258 -0
- package/rust/src/error.rs +149 -0
- package/rust/src/ffi/memory.rs +256 -0
- package/rust/src/ffi/tests.rs +719 -0
- package/rust/src/ffi.rs +474 -0
- package/rust/src/lib.rs +36 -2341
- package/rust/src/peer/callbacks.rs +145 -0
- package/rust/src/peer/media.rs +221 -0
- package/rust/src/peer/owner/tests.rs +188 -0
- package/rust/src/peer/owner.rs +353 -0
- package/rust/src/peer/shared.rs +323 -0
- package/rust/src/peer/tests.rs +513 -0
- package/rust/src/peer.rs +189 -0
- package/rust/src/protocol/event.rs +238 -0
- package/rust/src/protocol/request.rs +347 -0
- package/rust/src/protocol/stats.rs +356 -0
- package/rust/src/protocol.rs +71 -0
- package/rust/src/sync/gate.rs +159 -0
- package/rust/src/sync/notifier.rs +136 -0
- package/rust/src/sync/queue.rs +384 -0
- package/rust/src/sync.rs +20 -0
- package/rust/src/test_support.rs +99 -0
- package/rust-toolchain.toml +7 -0
- package/scripts/stage.mjs +41 -1
package/rust/src/ffi.rs
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
//! The exported C ABI. `include/reactor_effect_native.h` is its contract.
|
|
2
|
+
//!
|
|
3
|
+
//! Every entry point catches panics, since unwinding into C is undefined: a
|
|
4
|
+
//! caught panic reports a native failure.
|
|
5
|
+
//!
|
|
6
|
+
//! A misaligned pointer argument is invalid input, and a length over its bound
|
|
7
|
+
//! is an overflow. Both are refused before any caller memory is touched, so
|
|
8
|
+
//! the `# Safety` sections below constrain only aligned pointers and lengths
|
|
9
|
+
//! within their bounds.
|
|
10
|
+
|
|
11
|
+
mod memory;
|
|
12
|
+
#[cfg(test)]
|
|
13
|
+
mod tests;
|
|
14
|
+
|
|
15
|
+
pub use crate::peer::ReactorEffectPeer;
|
|
16
|
+
|
|
17
|
+
use crate::abi::{
|
|
18
|
+
ABI_VERSION, CALL_BUFFER_MIN, FAILURE_MESSAGE_BYTES, MAX_MESSAGE_BYTES, MAX_REQUEST_BYTES,
|
|
19
|
+
Status,
|
|
20
|
+
};
|
|
21
|
+
use crate::error::{BridgeError, FailureClass};
|
|
22
|
+
use crate::sync::Taken;
|
|
23
|
+
use memory::{Out, OutSlice, input, peer_ref};
|
|
24
|
+
use std::ffi::{CStr, c_char};
|
|
25
|
+
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
26
|
+
use std::ptr;
|
|
27
|
+
|
|
28
|
+
/// Called on a peer's notifier thread with the readiness bits of every queue
|
|
29
|
+
/// that received an item since the previous call (`ReactorEffectNotify`).
|
|
30
|
+
pub type ReactorEffectNotify = extern "C" fn(ready: u32);
|
|
31
|
+
|
|
32
|
+
/// The header of one decoded BGRA frame, written by
|
|
33
|
+
/// [`reactor_effect_peer_take_video`].
|
|
34
|
+
#[repr(C)]
|
|
35
|
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
36
|
+
pub struct ReactorEffectVideoHeader {
|
|
37
|
+
/// Frame width in pixels.
|
|
38
|
+
pub width: u32,
|
|
39
|
+
/// Frame height in pixels.
|
|
40
|
+
pub height: u32,
|
|
41
|
+
/// BGRA bytes: `width * height * 4`.
|
|
42
|
+
pub data_len: u32,
|
|
43
|
+
/// Bytes of the sender's frame-metadata user data.
|
|
44
|
+
pub metadata_len: u32,
|
|
45
|
+
/// The sender's frame ID; 0 when it supplied none.
|
|
46
|
+
pub frame_id: u64,
|
|
47
|
+
/// The sender's capture time in microseconds; 0 when absent.
|
|
48
|
+
pub timestamp_us: u64,
|
|
49
|
+
/// The track's index in the prepare request.
|
|
50
|
+
pub track: u32,
|
|
51
|
+
/// Always 0.
|
|
52
|
+
pub reserved: u32,
|
|
53
|
+
/// The frame's admission sequence on its track, from 0, stamped before
|
|
54
|
+
/// the queue can drop it: a gap is a frame the queue dropped.
|
|
55
|
+
pub sequence: u64,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// The header of one interleaved PCM block, written by
|
|
59
|
+
/// [`reactor_effect_peer_take_audio`].
|
|
60
|
+
#[repr(C)]
|
|
61
|
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
62
|
+
pub struct ReactorEffectAudioHeader {
|
|
63
|
+
/// Samples per second, per channel.
|
|
64
|
+
pub sample_rate: u32,
|
|
65
|
+
/// Interleaved channels.
|
|
66
|
+
pub channels: u32,
|
|
67
|
+
/// Interleaved `int16_t` samples: frames times channels.
|
|
68
|
+
pub samples: u32,
|
|
69
|
+
/// The track's index in the prepare request.
|
|
70
|
+
pub track: u32,
|
|
71
|
+
/// The block's admission sequence on its track, from 0, stamped before
|
|
72
|
+
/// the queue can drop it: a gap is a block the queue dropped.
|
|
73
|
+
pub sequence: u64,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Diagnostic text beside a failure status. Never match on it: the status is
|
|
77
|
+
/// the failure class.
|
|
78
|
+
#[repr(C)]
|
|
79
|
+
#[derive(Debug, Clone)]
|
|
80
|
+
pub struct ReactorEffectFailure {
|
|
81
|
+
/// Bytes of `message` in use.
|
|
82
|
+
pub message_len: u32,
|
|
83
|
+
/// UTF-8, truncated on a character boundary.
|
|
84
|
+
pub message: [u8; FAILURE_MESSAGE_BYTES],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl ReactorEffectFailure {
|
|
88
|
+
/// The diagnostic for `message`, truncated to fit.
|
|
89
|
+
fn new(message: &str) -> Self {
|
|
90
|
+
let text = truncate(message, FAILURE_MESSAGE_BYTES).as_bytes();
|
|
91
|
+
let mut failure = Self {
|
|
92
|
+
message_len: 0,
|
|
93
|
+
message: [0; FAILURE_MESSAGE_BYTES],
|
|
94
|
+
};
|
|
95
|
+
// `truncate` keeps the text within the array, so this always copies.
|
|
96
|
+
if let (Some(prefix), Ok(len)) = (
|
|
97
|
+
failure.message.get_mut(..text.len()),
|
|
98
|
+
u32::try_from(text.len()),
|
|
99
|
+
) {
|
|
100
|
+
prefix.copy_from_slice(text);
|
|
101
|
+
failure.message_len = len;
|
|
102
|
+
}
|
|
103
|
+
failure
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// The longest prefix of `text` within `max` bytes that ends on a character
|
|
108
|
+
/// boundary.
|
|
109
|
+
fn truncate(text: &str, max: usize) -> &str {
|
|
110
|
+
(0..=max.min(text.len()))
|
|
111
|
+
.rev()
|
|
112
|
+
.find_map(|end| text.get(..end))
|
|
113
|
+
.unwrap_or_default()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// This image's source and build identity: a NUL-terminated C string inside a
|
|
117
|
+
/// marker that packaging finds by scanning the file, without loading a
|
|
118
|
+
/// foreign-platform library.
|
|
119
|
+
static BUILD_IDENTITY: &CStr = {
|
|
120
|
+
let marked = concat!(
|
|
121
|
+
"reactor-effect-native:build-identity:",
|
|
122
|
+
env!("REACTOR_EFFECT_BUILD_IDENTITY"),
|
|
123
|
+
":end\0"
|
|
124
|
+
);
|
|
125
|
+
match CStr::from_bytes_with_nul(marked.as_bytes()) {
|
|
126
|
+
Ok(identity) => identity,
|
|
127
|
+
Err(_) => panic!("the build identity must be one NUL-terminated C string"),
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/// The ABI version this library implements. The host refuses any other.
|
|
132
|
+
#[unsafe(no_mangle)]
|
|
133
|
+
pub extern "C" fn reactor_effect_abi_version() -> u32 {
|
|
134
|
+
ABI_VERSION
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// The static source and build identity of this loaded image. Never free it.
|
|
138
|
+
#[unsafe(no_mangle)]
|
|
139
|
+
pub extern "C" fn reactor_effect_build_identity() -> *const c_char {
|
|
140
|
+
BUILD_IDENTITY.as_ptr()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Allocate a peer, or return null when its threads cannot start.
|
|
144
|
+
///
|
|
145
|
+
/// When `notify` is non-null, a notifier thread calls it with the readiness
|
|
146
|
+
/// bits of the queues that received items since its previous call, until
|
|
147
|
+
/// shutdown joins that thread.
|
|
148
|
+
#[unsafe(no_mangle)]
|
|
149
|
+
pub extern "C" fn reactor_effect_peer_create(
|
|
150
|
+
notify: Option<ReactorEffectNotify>,
|
|
151
|
+
) -> *mut ReactorEffectPeer {
|
|
152
|
+
catch_unwind(|| ReactorEffectPeer::create(notify))
|
|
153
|
+
.ok()
|
|
154
|
+
.flatten()
|
|
155
|
+
.map_or(ptr::null_mut(), |peer| Box::into_raw(Box::new(peer)))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Run one serialized peer operation. `request` is UTF-8; on success the
|
|
159
|
+
/// response is UTF-8 JSON and `response_cap` must be at least 4 MiB, which
|
|
160
|
+
/// `BUFFER_TOO_SMALL` reports through `response_len`.
|
|
161
|
+
///
|
|
162
|
+
/// # Safety
|
|
163
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
164
|
+
/// A nonzero `request_len` of at most 1 MiB needs `request` valid for reads of
|
|
165
|
+
/// that many bytes. A non-null `response` must be valid for writes of
|
|
166
|
+
/// `response_cap` bytes, a non-null `response_len` for one `usize`, and a
|
|
167
|
+
/// non-null `failure` for one [`ReactorEffectFailure`].
|
|
168
|
+
#[unsafe(no_mangle)]
|
|
169
|
+
pub unsafe extern "C" fn reactor_effect_peer_call(
|
|
170
|
+
peer: *mut ReactorEffectPeer,
|
|
171
|
+
operation: u32,
|
|
172
|
+
request: *const u8,
|
|
173
|
+
request_len: usize,
|
|
174
|
+
response: *mut u8,
|
|
175
|
+
response_cap: usize,
|
|
176
|
+
response_len: *mut usize,
|
|
177
|
+
failure: *mut ReactorEffectFailure,
|
|
178
|
+
) -> i32 {
|
|
179
|
+
// SAFETY: a non-null `failure` is writable for the call, per the contract.
|
|
180
|
+
let failure = unsafe { Out::new(failure) };
|
|
181
|
+
// SAFETY: a non-null `response_len` is writable for the call.
|
|
182
|
+
let response_len = unsafe { Out::new(response_len) };
|
|
183
|
+
// SAFETY: a non-null `response` is writable for `response_cap` bytes.
|
|
184
|
+
let response = unsafe { OutSlice::new(response, response_cap) };
|
|
185
|
+
status_of(failure, || {
|
|
186
|
+
// SAFETY: `peer` is null or live for the call.
|
|
187
|
+
let peer = unsafe { peer_ref(peer) }?;
|
|
188
|
+
let mut response = response?;
|
|
189
|
+
let Some(mut response_len) = response_len?.filter(|_| !response.is_null()) else {
|
|
190
|
+
return Err(BridgeError::invalid("call requires a response buffer"));
|
|
191
|
+
};
|
|
192
|
+
response_len.write(0);
|
|
193
|
+
if response_cap < CALL_BUFFER_MIN {
|
|
194
|
+
response_len.write(CALL_BUFFER_MIN);
|
|
195
|
+
return Ok(Status::BufferTooSmall);
|
|
196
|
+
}
|
|
197
|
+
// SAFETY: a nonzero `request_len` within its bound makes `request`
|
|
198
|
+
// readable for it.
|
|
199
|
+
let request = unsafe { input(request, request_len, MAX_REQUEST_BYTES) }?;
|
|
200
|
+
let bytes = peer.call(operation, request)?;
|
|
201
|
+
if !response.holds(bytes.len()) {
|
|
202
|
+
return Err(BridgeError::overflow(
|
|
203
|
+
"native call response exceeds its buffer",
|
|
204
|
+
));
|
|
205
|
+
}
|
|
206
|
+
response.copy_from(&bytes)?;
|
|
207
|
+
response_len.write(bytes.len());
|
|
208
|
+
Ok(Status::Ok)
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Send one binary SCTP message on a bridge-owned data channel.
|
|
213
|
+
///
|
|
214
|
+
/// # Safety
|
|
215
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
216
|
+
/// A nonzero `data_len` of at most 256 KiB needs `data` valid for reads of
|
|
217
|
+
/// that many bytes, and a non-null `failure` must be writable for one
|
|
218
|
+
/// [`ReactorEffectFailure`].
|
|
219
|
+
#[unsafe(no_mangle)]
|
|
220
|
+
pub unsafe extern "C" fn reactor_effect_peer_send(
|
|
221
|
+
peer: *mut ReactorEffectPeer,
|
|
222
|
+
channel: u32,
|
|
223
|
+
data: *const u8,
|
|
224
|
+
data_len: usize,
|
|
225
|
+
failure: *mut ReactorEffectFailure,
|
|
226
|
+
) -> i32 {
|
|
227
|
+
// SAFETY: a non-null `failure` is writable for the call.
|
|
228
|
+
let failure = unsafe { Out::new(failure) };
|
|
229
|
+
status_of(failure, || {
|
|
230
|
+
// SAFETY: `peer` is null or live for the call.
|
|
231
|
+
let peer = unsafe { peer_ref(peer) }?;
|
|
232
|
+
// SAFETY: a nonzero `data_len` within its bound makes `data` readable
|
|
233
|
+
// for it.
|
|
234
|
+
let bytes = unsafe { input(data, data_len, MAX_MESSAGE_BYTES) }?;
|
|
235
|
+
peer.send(channel, bytes)?;
|
|
236
|
+
Ok(Status::Ok)
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Nonblocking: copy the oldest transport event into caller memory and
|
|
241
|
+
/// remove it. `BUFFER_TOO_SMALL` writes the required size to `out_len` and
|
|
242
|
+
/// keeps the event queued; `AGAIN` means the queue is empty.
|
|
243
|
+
///
|
|
244
|
+
/// # Safety
|
|
245
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
246
|
+
/// A non-null `out_len` must be writable for one `usize`, and a nonzero
|
|
247
|
+
/// `out_cap` needs `out` valid for writes of that many bytes.
|
|
248
|
+
#[unsafe(no_mangle)]
|
|
249
|
+
pub unsafe extern "C" fn reactor_effect_peer_take_event(
|
|
250
|
+
peer: *mut ReactorEffectPeer,
|
|
251
|
+
out: *mut u8,
|
|
252
|
+
out_cap: usize,
|
|
253
|
+
out_len: *mut usize,
|
|
254
|
+
) -> i32 {
|
|
255
|
+
// SAFETY: a non-null `out_len` is writable for the call.
|
|
256
|
+
let out_len = unsafe { Out::new(out_len) };
|
|
257
|
+
// SAFETY: a nonzero `out_cap` makes `out` writable for it; `holds` never
|
|
258
|
+
// accepts bytes for a null `out`.
|
|
259
|
+
let out = unsafe { OutSlice::new(out, out_cap) };
|
|
260
|
+
status_of(Ok(None), || {
|
|
261
|
+
// SAFETY: `peer` is null or live for the call.
|
|
262
|
+
let peer = unsafe { peer_ref(peer) }?;
|
|
263
|
+
let mut out = out?;
|
|
264
|
+
let Some(mut out_len) = out_len? else {
|
|
265
|
+
return Err(BridgeError::invalid("take_event requires out_len"));
|
|
266
|
+
};
|
|
267
|
+
if out_cap != 0 && out.is_null() {
|
|
268
|
+
return Err(BridgeError::invalid("null event buffer with a capacity"));
|
|
269
|
+
}
|
|
270
|
+
let taken = peer.shared().events.take(|packet| {
|
|
271
|
+
out_len.write(packet.len());
|
|
272
|
+
out.holds(packet.len())
|
|
273
|
+
});
|
|
274
|
+
Ok(match taken {
|
|
275
|
+
Taken::Item(packet) => {
|
|
276
|
+
out.copy_from(&packet)?;
|
|
277
|
+
Status::Ok
|
|
278
|
+
}
|
|
279
|
+
Taken::TooSmall => Status::BufferTooSmall,
|
|
280
|
+
Taken::Empty => {
|
|
281
|
+
out_len.write(0);
|
|
282
|
+
Status::Again
|
|
283
|
+
}
|
|
284
|
+
Taken::Closed => {
|
|
285
|
+
out_len.write(0);
|
|
286
|
+
Status::Closed
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Nonblocking: copy the oldest decoded frame into caller memory and remove it.
|
|
293
|
+
///
|
|
294
|
+
/// The header is written for `OK` and for `BUFFER_TOO_SMALL`, which keeps the
|
|
295
|
+
/// frame queued; a null `bgra` asks for the header alone.
|
|
296
|
+
///
|
|
297
|
+
/// # Safety
|
|
298
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
299
|
+
/// A non-null `header` must be writable for one [`ReactorEffectVideoHeader`],
|
|
300
|
+
/// a non-null `bgra` for `bgra_cap` bytes and a non-null `metadata` for
|
|
301
|
+
/// `metadata_cap` bytes.
|
|
302
|
+
#[unsafe(no_mangle)]
|
|
303
|
+
pub unsafe extern "C" fn reactor_effect_peer_take_video(
|
|
304
|
+
peer: *mut ReactorEffectPeer,
|
|
305
|
+
header: *mut ReactorEffectVideoHeader,
|
|
306
|
+
bgra: *mut u8,
|
|
307
|
+
bgra_cap: usize,
|
|
308
|
+
metadata: *mut u8,
|
|
309
|
+
metadata_cap: usize,
|
|
310
|
+
) -> i32 {
|
|
311
|
+
// SAFETY: a non-null `header` is writable for the call.
|
|
312
|
+
let header = unsafe { Out::new(header) };
|
|
313
|
+
// SAFETY: a non-null `bgra` is writable for `bgra_cap` bytes.
|
|
314
|
+
let bgra = unsafe { OutSlice::new(bgra, bgra_cap) };
|
|
315
|
+
// SAFETY: a non-null `metadata` is writable for `metadata_cap` bytes.
|
|
316
|
+
let metadata = unsafe { OutSlice::new(metadata, metadata_cap) };
|
|
317
|
+
status_of(Ok(None), || {
|
|
318
|
+
// SAFETY: `peer` is null or live for the call.
|
|
319
|
+
let peer = unsafe { peer_ref(peer) }?;
|
|
320
|
+
let (mut bgra, mut metadata) = (bgra?, metadata?);
|
|
321
|
+
let Some(mut header) = header? else {
|
|
322
|
+
return Err(BridgeError::invalid("take_video requires a header"));
|
|
323
|
+
};
|
|
324
|
+
let taken = peer.shared().video.take(|frame| {
|
|
325
|
+
header.write(frame.header());
|
|
326
|
+
!bgra.is_null() && bgra.holds(frame.bgra.len()) && metadata.holds(frame.metadata.len())
|
|
327
|
+
});
|
|
328
|
+
Ok(match taken {
|
|
329
|
+
Taken::Item(frame) => {
|
|
330
|
+
bgra.copy_from(&frame.bgra)?;
|
|
331
|
+
metadata.copy_from(&frame.metadata)?;
|
|
332
|
+
Status::Ok
|
|
333
|
+
}
|
|
334
|
+
Taken::TooSmall => Status::BufferTooSmall,
|
|
335
|
+
Taken::Empty => Status::Again,
|
|
336
|
+
Taken::Closed => Status::Closed,
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// Nonblocking: copy the oldest PCM block into caller memory and remove it.
|
|
342
|
+
///
|
|
343
|
+
/// The block is native-endian interleaved `int16_t`. The header is written
|
|
344
|
+
/// for `OK` and for `BUFFER_TOO_SMALL`; a null `pcm` asks for the header alone.
|
|
345
|
+
///
|
|
346
|
+
/// # Safety
|
|
347
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
348
|
+
/// A non-null `header` must be writable for one [`ReactorEffectAudioHeader`],
|
|
349
|
+
/// and a non-null `pcm` for `pcm_cap` samples.
|
|
350
|
+
#[unsafe(no_mangle)]
|
|
351
|
+
pub unsafe extern "C" fn reactor_effect_peer_take_audio(
|
|
352
|
+
peer: *mut ReactorEffectPeer,
|
|
353
|
+
header: *mut ReactorEffectAudioHeader,
|
|
354
|
+
pcm: *mut i16,
|
|
355
|
+
pcm_cap: usize,
|
|
356
|
+
) -> i32 {
|
|
357
|
+
// SAFETY: a non-null `header` is writable for the call.
|
|
358
|
+
let header = unsafe { Out::new(header) };
|
|
359
|
+
// SAFETY: a non-null `pcm` is writable for `pcm_cap` samples.
|
|
360
|
+
let pcm = unsafe { OutSlice::new(pcm, pcm_cap) };
|
|
361
|
+
status_of(Ok(None), || {
|
|
362
|
+
// SAFETY: `peer` is null or live for the call.
|
|
363
|
+
let peer = unsafe { peer_ref(peer) }?;
|
|
364
|
+
let mut pcm = pcm?;
|
|
365
|
+
let Some(mut header) = header? else {
|
|
366
|
+
return Err(BridgeError::invalid("take_audio requires a header"));
|
|
367
|
+
};
|
|
368
|
+
let taken = peer.shared().audio.take(|block| {
|
|
369
|
+
header.write(block.header());
|
|
370
|
+
!pcm.is_null() && pcm.holds(block.pcm.len())
|
|
371
|
+
});
|
|
372
|
+
Ok(match taken {
|
|
373
|
+
Taken::Item(block) => {
|
|
374
|
+
pcm.copy_from(&block.pcm)?;
|
|
375
|
+
Status::Ok
|
|
376
|
+
}
|
|
377
|
+
Taken::TooSmall => Status::BufferTooSmall,
|
|
378
|
+
Taken::Empty => Status::Again,
|
|
379
|
+
Taken::Closed => Status::Closed,
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Fence callback and event admission at once. No callback or event is
|
|
385
|
+
/// admitted after it returns.
|
|
386
|
+
///
|
|
387
|
+
/// # Safety
|
|
388
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`].
|
|
389
|
+
#[unsafe(no_mangle)]
|
|
390
|
+
pub unsafe extern "C" fn reactor_effect_peer_close(peer: *mut ReactorEffectPeer) {
|
|
391
|
+
// SAFETY: `peer` is null or live for the call.
|
|
392
|
+
if let Ok(peer) = unsafe { peer_ref(peer) } {
|
|
393
|
+
// Closing only stores flags and clears queues.
|
|
394
|
+
discard_panic(|| peer.close());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/// Join native ownership: close, then drop the libwebrtc objects on the owner
|
|
399
|
+
/// thread, wait for every admitted callback, and join the owner and notifier
|
|
400
|
+
/// threads. Safe to call more than once.
|
|
401
|
+
///
|
|
402
|
+
/// # Safety
|
|
403
|
+
/// `peer` must be null or a live handle from [`reactor_effect_peer_create`],
|
|
404
|
+
/// and a non-null `failure` must be writable for one [`ReactorEffectFailure`].
|
|
405
|
+
/// The notifier may be waiting for the host to run its callback, so never
|
|
406
|
+
/// call this from the thread that runs that callback.
|
|
407
|
+
#[unsafe(no_mangle)]
|
|
408
|
+
pub unsafe extern "C" fn reactor_effect_peer_shutdown(
|
|
409
|
+
peer: *mut ReactorEffectPeer,
|
|
410
|
+
failure: *mut ReactorEffectFailure,
|
|
411
|
+
) -> i32 {
|
|
412
|
+
// SAFETY: a non-null `failure` is writable for the call.
|
|
413
|
+
let failure = unsafe { Out::new(failure) };
|
|
414
|
+
status_of(failure, || {
|
|
415
|
+
// SAFETY: `peer` is null or live for the call.
|
|
416
|
+
unsafe { peer_ref(peer) }?.shutdown()?;
|
|
417
|
+
Ok(Status::Ok)
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/// Shut a peer down if needed, so the same thread rule applies, then free its
|
|
422
|
+
/// handle.
|
|
423
|
+
///
|
|
424
|
+
/// # Safety
|
|
425
|
+
/// `peer` must be null or a handle from [`reactor_effect_peer_create`] not
|
|
426
|
+
/// passed here before. Every foreign call using the handle must have
|
|
427
|
+
/// returned, including calls still queued in a host FFI executor: joining the
|
|
428
|
+
/// native owner alone does not establish that.
|
|
429
|
+
#[unsafe(no_mangle)]
|
|
430
|
+
pub unsafe extern "C" fn reactor_effect_peer_destroy(peer: *mut ReactorEffectPeer) {
|
|
431
|
+
// A misaligned pointer never came from `reactor_effect_peer_create`, and
|
|
432
|
+
// this call has no status to refuse it with.
|
|
433
|
+
if peer.is_null() || !peer.is_aligned() {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
// SAFETY: `peer` came from `Box::into_raw` in `reactor_effect_peer_create`,
|
|
437
|
+
// is passed here once, and no other call still uses it.
|
|
438
|
+
let peer = unsafe { Box::from_raw(peer) };
|
|
439
|
+
// Dropping shuts the peer down.
|
|
440
|
+
discard_panic(move || drop(peer));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// Run the body of an entry point that returns no status: a panic there has
|
|
444
|
+
/// nothing to report it and must not unwind into C.
|
|
445
|
+
fn discard_panic(body: impl FnOnce()) {
|
|
446
|
+
#[expect(
|
|
447
|
+
clippy::let_underscore_must_use,
|
|
448
|
+
reason = "the entry point has no status to report a caught panic with"
|
|
449
|
+
)]
|
|
450
|
+
let _ = catch_unwind(AssertUnwindSafe(body));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// The status of an entry point's body. A failure's diagnostic goes to
|
|
454
|
+
/// `failure` when the caller passed one, and a caught panic is a native
|
|
455
|
+
/// failure. A misaligned `failure` fails the call before the body runs, since
|
|
456
|
+
/// it cannot hold its own diagnostic.
|
|
457
|
+
fn status_of(
|
|
458
|
+
failure: Result<Option<Out<ReactorEffectFailure>>, BridgeError>,
|
|
459
|
+
body: impl FnOnce() -> Result<Status, BridgeError>,
|
|
460
|
+
) -> i32 {
|
|
461
|
+
let failure = match failure {
|
|
462
|
+
Ok(failure) => failure,
|
|
463
|
+
Err(misaligned) => return misaligned.class.status().code(),
|
|
464
|
+
};
|
|
465
|
+
let error = match catch_unwind(AssertUnwindSafe(body)) {
|
|
466
|
+
Ok(Ok(status)) => return status.code(),
|
|
467
|
+
Ok(Err(error)) => error,
|
|
468
|
+
Err(_) => BridgeError::new(FailureClass::Native, "native bridge panicked"),
|
|
469
|
+
};
|
|
470
|
+
if let Some(mut failure) = failure {
|
|
471
|
+
failure.write(ReactorEffectFailure::new(&error.message));
|
|
472
|
+
}
|
|
473
|
+
error.class.status().code()
|
|
474
|
+
}
|