zzq 0.7.0 → 0.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/ext/zzq_native/Cargo.toml +3 -3
- data/ext/zzq_native/src/broker.rs +13 -39
- data/ext/zzq_native/src/client.rs +29 -65
- data/ext/zzq_native/src/lib.rs +13 -21
- data/ext/zzq_native/src/op.rs +8 -18
- data/ext/zzq_native/src/rb.rs +99 -42
- data/ext/zzq_native/src/wake.rs +12 -24
- data/lib/zzq/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2131a7723ecf3fde157b22ad8d84d9a03b7703fd30cb439719235047424aaded
|
|
4
|
+
data.tar.gz: a09642a35676ba55d866b1e813a0e4162b895131703166e77bd4c539d5c572ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10270ffcd3f415d9aa38feda85cba120037249a6997beca140b01dbd632b8a1622851ac305d2a812090e9d4a785c547f71523f88e327d4975d153ba337584c55
|
|
7
|
+
data.tar.gz: 961802b3a00cb62b843a93316aefdf29b56c44b582f75bbafee82d5fd8db4a18c6a5557f1248a1bcbf881cc1983138de2a4476b02ec140adae6716f64afca9e5
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## v0.8.0 — 2026-11-03
|
|
6
|
+
|
|
7
|
+
- Built on the zzq 0.6 crates. Bind and connect errors name the endpoint
|
|
8
|
+
as given: a `tls://`, `ws://` or `wss://` bind no longer says `tcp://`,
|
|
9
|
+
and an IPv6 host keeps its brackets.
|
|
10
|
+
|
|
5
11
|
## v0.7.0 — 2026-10-31
|
|
6
12
|
|
|
7
13
|
- Built on the zzq 0.5 crates, with their fixes. With `data_dir:`, a QoS 2
|
data/ext/zzq_native/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "zzq_native"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.93"
|
|
6
6
|
license = "ISC OR Apache-2.0"
|
|
@@ -12,8 +12,8 @@ name = "zzq_native"
|
|
|
12
12
|
crate-type = ["cdylib"]
|
|
13
13
|
|
|
14
14
|
[dependencies]
|
|
15
|
-
zzq-broker = "0.
|
|
16
|
-
zzq-client = "0.
|
|
15
|
+
zzq-broker = "0.6"
|
|
16
|
+
zzq-client = "0.6"
|
|
17
17
|
bytes = "1.12"
|
|
18
18
|
libc = "0.2"
|
|
19
19
|
rb-sys = "0.9"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! `ZZQ::Native::Broker`: a running broker.
|
|
2
2
|
|
|
3
|
-
use std::sync::
|
|
3
|
+
use std::sync::Arc;
|
|
4
4
|
|
|
5
5
|
use rb_sys::VALUE;
|
|
6
6
|
use tokio::sync::{OwnedRwLockReadGuard, RwLock};
|
|
@@ -11,8 +11,7 @@ use crate::op::{self, Out};
|
|
|
11
11
|
use crate::rb::{self, DataType, RbResult, RubyErr};
|
|
12
12
|
use crate::{context, options};
|
|
13
13
|
|
|
14
|
-
static TYPE: DataType = DataType::new
|
|
15
|
-
static CLASS: OnceLock<usize> = OnceLock::new();
|
|
14
|
+
static TYPE: DataType<NativeBroker> = DataType::new(c"ZZQ::Native::Broker");
|
|
16
15
|
|
|
17
16
|
#[derive(Debug)]
|
|
18
17
|
struct NativeBroker {
|
|
@@ -26,7 +25,7 @@ type Held = OwnedRwLockReadGuard<Option<Broker>, Broker>;
|
|
|
26
25
|
|
|
27
26
|
/// The broker for one call, or `Closed` once a shutdown has begun.
|
|
28
27
|
fn get(value: VALUE) -> RbResult<Held> {
|
|
29
|
-
let native: &NativeBroker =
|
|
28
|
+
let native: &NativeBroker = TYPE.get(value)?;
|
|
30
29
|
native
|
|
31
30
|
.broker
|
|
32
31
|
.clone()
|
|
@@ -41,7 +40,7 @@ pub fn wrap(b: Broker) -> RbResult<VALUE> {
|
|
|
41
40
|
let native = NativeBroker {
|
|
42
41
|
broker: Arc::new(RwLock::new(Some(b))),
|
|
43
42
|
};
|
|
44
|
-
TYPE.wrap(
|
|
43
|
+
TYPE.wrap(native)
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
pub fn stats_hash(s: &Stats) -> RbResult<VALUE> {
|
|
@@ -115,7 +114,7 @@ fn stats(this: VALUE) -> RbResult<VALUE> {
|
|
|
115
114
|
/// An op that waits for the calls using the broker to finish, then shuts
|
|
116
115
|
/// it down.
|
|
117
116
|
fn shutdown(this: VALUE) -> RbResult<VALUE> {
|
|
118
|
-
let native: &NativeBroker =
|
|
117
|
+
let native: &NativeBroker = TYPE.get(this)?;
|
|
119
118
|
let broker = native.broker.clone();
|
|
120
119
|
op::new(async move {
|
|
121
120
|
let Some(b) = broker.write_owned().await.take() else {
|
|
@@ -126,39 +125,14 @@ fn shutdown(this: VALUE) -> RbResult<VALUE> {
|
|
|
126
125
|
})
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
unsafe extern "C" fn broker_start(_class: VALUE, opts: VALUE) -> VALUE {
|
|
130
|
-
rb::wrap(|| start(opts))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
unsafe extern "C" fn broker_bind(this: VALUE, uri: VALUE) -> VALUE {
|
|
134
|
-
rb::wrap(|| bind(this, uri))
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
unsafe extern "C" fn broker_publish(
|
|
138
|
-
this: VALUE,
|
|
139
|
-
topic: VALUE,
|
|
140
|
-
payload: VALUE,
|
|
141
|
-
qos: VALUE,
|
|
142
|
-
retain: VALUE,
|
|
143
|
-
) -> VALUE {
|
|
144
|
-
rb::wrap(|| publish(this, topic, payload, qos, retain))
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
unsafe extern "C" fn broker_stats(this: VALUE) -> VALUE {
|
|
148
|
-
rb::wrap(|| stats(this))
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
unsafe extern "C" fn broker_shutdown(this: VALUE) -> VALUE {
|
|
152
|
-
rb::wrap(|| shutdown(this))
|
|
153
|
-
}
|
|
154
|
-
|
|
155
128
|
pub fn register(native: VALUE) -> RbResult<()> {
|
|
156
|
-
let class =
|
|
157
|
-
|
|
158
|
-
rb::
|
|
159
|
-
rb::
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
rb::
|
|
129
|
+
let class = TYPE.register(native, c"Broker")?;
|
|
130
|
+
rb::singleton!(class, c"start", |_class, opts| start(opts))?;
|
|
131
|
+
rb::method!(class, c"bind", |this, uri| bind(this, uri))?;
|
|
132
|
+
rb::method!(class, c"publish", |this, topic, payload, qos, retain| {
|
|
133
|
+
publish(this, topic, payload, qos, retain)
|
|
134
|
+
})?;
|
|
135
|
+
rb::method!(class, c"stats", |this| stats(this))?;
|
|
136
|
+
rb::method!(class, c"shutdown", |this| shutdown(this))?;
|
|
163
137
|
Ok(())
|
|
164
138
|
}
|
|
@@ -18,8 +18,7 @@ use crate::op::{self, Out};
|
|
|
18
18
|
use crate::rb::{self, DataType, RbResult, RubyErr};
|
|
19
19
|
use crate::{context, options, wake};
|
|
20
20
|
|
|
21
|
-
static TYPE: DataType = DataType::new
|
|
22
|
-
static CLASS: OnceLock<usize> = OnceLock::new();
|
|
21
|
+
static TYPE: DataType<NativeClient> = DataType::new(c"ZZQ::Native::Client");
|
|
23
22
|
/// `ZZQ::Message` and a frozen empty Array for messages without
|
|
24
23
|
/// Subscription Identifiers.
|
|
25
24
|
static MESSAGE: OnceLock<(usize, usize)> = OnceLock::new();
|
|
@@ -44,7 +43,7 @@ impl std::fmt::Debug for NativeClient {
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
fn get(value: VALUE) -> RbResult<&'static NativeClient> {
|
|
47
|
-
|
|
46
|
+
TYPE.get(value)
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
/// A Ruby object for a connected client.
|
|
@@ -57,7 +56,7 @@ pub fn wrap(c: Client) -> RbResult<VALUE> {
|
|
|
57
56
|
client: Arc::new(tokio::sync::Mutex::new(c)),
|
|
58
57
|
recv: Mutex::new(None),
|
|
59
58
|
};
|
|
60
|
-
TYPE.wrap(
|
|
59
|
+
TYPE.wrap(native)
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
fn connect(uri: VALUE, opts: VALUE) -> RbResult<VALUE> {
|
|
@@ -223,72 +222,37 @@ fn disconnect(this: VALUE, reason: VALUE) -> RbResult<VALUE> {
|
|
|
223
222
|
})
|
|
224
223
|
}
|
|
225
224
|
|
|
226
|
-
unsafe extern "C" fn client_connect(_class: VALUE, uri: VALUE, opts: VALUE) -> VALUE {
|
|
227
|
-
rb::wrap(|| connect(uri, opts))
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
unsafe extern "C" fn client_publish(
|
|
231
|
-
this: VALUE,
|
|
232
|
-
topic: VALUE,
|
|
233
|
-
payload: VALUE,
|
|
234
|
-
qos: VALUE,
|
|
235
|
-
retain: VALUE,
|
|
236
|
-
) -> VALUE {
|
|
237
|
-
rb::wrap(|| publish(this, topic, payload, qos, retain))
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
unsafe extern "C" fn client_subscribe(this: VALUE, filters: VALUE, sub_id: VALUE) -> VALUE {
|
|
241
|
-
rb::wrap(|| subscribe(this, filters, sub_id))
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
unsafe extern "C" fn client_unsubscribe(this: VALUE, filters: VALUE) -> VALUE {
|
|
245
|
-
rb::wrap(|| unsubscribe(this, filters))
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
unsafe extern "C" fn client_receive(this: VALUE, wake: VALUE, max: VALUE) -> VALUE {
|
|
249
|
-
rb::wrap(|| receive(this, wake, max))
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
unsafe extern "C" fn client_disconnect(this: VALUE, reason: VALUE) -> VALUE {
|
|
253
|
-
rb::wrap(|| disconnect(this, reason))
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
unsafe extern "C" fn client_session_present(this: VALUE) -> VALUE {
|
|
257
|
-
rb::wrap(|| Ok(rb::bool_value(get(this)?.session_present)))
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
unsafe extern "C" fn client_assigned_client_id(this: VALUE) -> VALUE {
|
|
261
|
-
rb::wrap(|| match &get(this)?.assigned_client_id {
|
|
262
|
-
Some(id) => rb::new_utf8_string(id),
|
|
263
|
-
None => Ok(rb::qnil()),
|
|
264
|
-
})
|
|
265
|
-
}
|
|
266
|
-
|
|
267
225
|
pub fn register(zzq: VALUE, native: VALUE) -> RbResult<()> {
|
|
268
226
|
let message = rb::const_get(zzq, c"Message")?;
|
|
269
227
|
let no_ids = rb::freeze(rb::array_new_capa(0)?);
|
|
270
228
|
rb::define_const(native, c"NO_SUBSCRIPTION_IDS", no_ids)?;
|
|
271
229
|
let _ = MESSAGE.set((message as usize, no_ids as usize));
|
|
272
230
|
|
|
273
|
-
let class =
|
|
274
|
-
|
|
275
|
-
rb::
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
rb::
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
rb::
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)?;
|
|
287
|
-
rb::
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)?;
|
|
231
|
+
let class = TYPE.register(native, c"Client")?;
|
|
232
|
+
rb::singleton!(class, c"connect", |_class, uri, opts| connect(uri, opts))?;
|
|
233
|
+
rb::method!(class, c"publish", |this, topic, payload, qos, retain| {
|
|
234
|
+
publish(this, topic, payload, qos, retain)
|
|
235
|
+
})?;
|
|
236
|
+
rb::method!(class, c"subscribe", |this, filters, sub_id| {
|
|
237
|
+
subscribe(this, filters, sub_id)
|
|
238
|
+
})?;
|
|
239
|
+
rb::method!(class, c"unsubscribe", |this, filters| unsubscribe(
|
|
240
|
+
this, filters
|
|
241
|
+
))?;
|
|
242
|
+
rb::method!(class, c"receive", |this, wake, max| receive(
|
|
243
|
+
this, wake, max
|
|
244
|
+
))?;
|
|
245
|
+
rb::method!(class, c"disconnect", |this, reason| disconnect(
|
|
246
|
+
this, reason
|
|
247
|
+
))?;
|
|
248
|
+
rb::method!(class, c"session_present?", |this| {
|
|
249
|
+
Ok(rb::bool_value(get(this)?.session_present))
|
|
250
|
+
})?;
|
|
251
|
+
rb::method!(class, c"assigned_client_id", |this| {
|
|
252
|
+
match &get(this)?.assigned_client_id {
|
|
253
|
+
Some(id) => rb::new_utf8_string(id),
|
|
254
|
+
None => Ok(rb::qnil()),
|
|
255
|
+
}
|
|
256
|
+
})?;
|
|
293
257
|
Ok(())
|
|
294
258
|
}
|
data/ext/zzq_native/src/lib.rs
CHANGED
|
@@ -9,27 +9,8 @@ mod options;
|
|
|
9
9
|
mod rb;
|
|
10
10
|
mod wake;
|
|
11
11
|
|
|
12
|
-
use rb_sys::VALUE;
|
|
13
|
-
|
|
14
12
|
use crate::rb::RbResult;
|
|
15
13
|
|
|
16
|
-
unsafe extern "C" fn io_threads(_module: VALUE) -> VALUE {
|
|
17
|
-
rb::wrap(|| Ok(rb::u64_value(context::io_threads() as u64)))
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
unsafe extern "C" fn set_io_threads(_module: VALUE, n: VALUE) -> VALUE {
|
|
21
|
-
rb::wrap(|| {
|
|
22
|
-
let n = usize::try_from(rb::value_to_i64(n)?)
|
|
23
|
-
.map_err(|_| rb::RubyErr::arg("io_threads must be positive"))?;
|
|
24
|
-
if !context::set_io_threads(n) {
|
|
25
|
-
return Err(error::zzq(
|
|
26
|
-
"set io_threads before the first client or broker",
|
|
27
|
-
));
|
|
28
|
-
}
|
|
29
|
-
Ok(rb::qnil())
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
14
|
/// Called once by Ruby when it loads the extension.
|
|
34
15
|
#[unsafe(no_mangle)]
|
|
35
16
|
pub extern "C" fn Init_zzq_native() {
|
|
@@ -42,8 +23,19 @@ fn init() -> RbResult<()> {
|
|
|
42
23
|
let zzq = rb::define_module(c"ZZQ")?;
|
|
43
24
|
let native = rb::define_module_under(zzq, c"Native")?;
|
|
44
25
|
error::init(zzq)?;
|
|
45
|
-
rb::
|
|
46
|
-
|
|
26
|
+
rb::singleton!(native, c"io_threads", |_module| {
|
|
27
|
+
Ok(rb::u64_value(context::io_threads() as u64))
|
|
28
|
+
})?;
|
|
29
|
+
rb::singleton!(native, c"io_threads=", |_module, n| {
|
|
30
|
+
let n = usize::try_from(rb::value_to_i64(n)?)
|
|
31
|
+
.map_err(|_| rb::RubyErr::arg("io_threads must be positive"))?;
|
|
32
|
+
if !context::set_io_threads(n) {
|
|
33
|
+
return Err(error::zzq(
|
|
34
|
+
"set io_threads before the first client or broker",
|
|
35
|
+
));
|
|
36
|
+
}
|
|
37
|
+
Ok(rb::qnil())
|
|
38
|
+
})?;
|
|
47
39
|
wake::register(native)?;
|
|
48
40
|
op::register(native)?;
|
|
49
41
|
client::register(zzq, native)?;
|
data/ext/zzq_native/src/op.rs
CHANGED
|
@@ -14,8 +14,7 @@ use crate::error::Error;
|
|
|
14
14
|
use crate::rb::{self, DataType, RbResult};
|
|
15
15
|
use crate::{broker, client, wake};
|
|
16
16
|
|
|
17
|
-
static TYPE: DataType = DataType::new
|
|
18
|
-
static CLASS: OnceLock<usize> = OnceLock::new();
|
|
17
|
+
static TYPE: DataType<Op> = DataType::new(c"ZZQ::Native::Op");
|
|
19
18
|
static PENDING: OnceLock<usize> = OnceLock::new();
|
|
20
19
|
|
|
21
20
|
/// What a finished op returns to Ruby.
|
|
@@ -65,11 +64,11 @@ fn wrap(task: Task) -> RbResult<VALUE> {
|
|
|
65
64
|
let op = Op {
|
|
66
65
|
task: Mutex::new(Some(task)),
|
|
67
66
|
};
|
|
68
|
-
TYPE.wrap(
|
|
67
|
+
TYPE.wrap(op)
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
fn get(value: VALUE) -> RbResult<&'static Op> {
|
|
72
|
-
|
|
71
|
+
TYPE.get(value)
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
fn poll(this: VALUE, wake: VALUE) -> RbResult<VALUE> {
|
|
@@ -109,12 +108,10 @@ fn to_ruby(out: Out) -> RbResult<VALUE> {
|
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
unsafe extern "C" fn op_cancel(this: VALUE) -> VALUE {
|
|
117
|
-
rb::wrap(|| {
|
|
111
|
+
pub fn register(native: VALUE) -> RbResult<()> {
|
|
112
|
+
let class = TYPE.register(native, c"Op")?;
|
|
113
|
+
rb::method!(class, c"poll", |this, wake| poll(this, wake))?;
|
|
114
|
+
rb::method!(class, c"cancel", |this| {
|
|
118
115
|
let task = get(this)?
|
|
119
116
|
.task
|
|
120
117
|
.lock()
|
|
@@ -122,14 +119,7 @@ unsafe extern "C" fn op_cancel(this: VALUE) -> VALUE {
|
|
|
122
119
|
.take();
|
|
123
120
|
drop(task);
|
|
124
121
|
Ok(rb::qnil())
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
pub fn register(native: VALUE) -> RbResult<()> {
|
|
129
|
-
let class = rb::define_class_under(native, c"Op")?;
|
|
130
|
-
let _ = CLASS.set(class as usize);
|
|
131
|
-
rb::define_method(class, c"poll", op_poll as *const (), 1)?;
|
|
132
|
-
rb::define_method(class, c"cancel", op_cancel as *const (), 0)?;
|
|
122
|
+
})?;
|
|
133
123
|
let pending = rb::freeze(rb::protect(|| unsafe {
|
|
134
124
|
rb_sys::rb_obj_alloc(rb_sys::rb_cObject)
|
|
135
125
|
})?);
|
data/ext/zzq_native/src/rb.rs
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
//! `rb_protect`, so a Ruby exception never unwinds through Rust frames.
|
|
3
3
|
|
|
4
4
|
use std::ffi::{CStr, CString, c_char, c_long, c_void};
|
|
5
|
+
use std::marker::PhantomData;
|
|
5
6
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
6
7
|
use std::ptr;
|
|
8
|
+
use std::sync::OnceLock;
|
|
7
9
|
|
|
8
10
|
use rb_sys::{VALUE, rb_data_type_struct__bindgen_ty_1, rb_data_type_t, size_t};
|
|
9
11
|
|
|
@@ -310,47 +312,67 @@ pub fn const_get(module: VALUE, name: &CStr) -> RbResult<VALUE> {
|
|
|
310
312
|
protect(|| unsafe { rb_sys::rb_const_get(module, rb_sys::rb_intern(name.as_ptr())) })
|
|
311
313
|
}
|
|
312
314
|
|
|
313
|
-
/// A Ruby
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
315
|
+
/// A Ruby class whose objects each hold a boxed `T`, which GC frees with
|
|
316
|
+
/// the object.
|
|
317
|
+
pub struct DataType<T> {
|
|
318
|
+
raw: rb_data_type_t,
|
|
319
|
+
/// The class, once [`DataType::register`] defined it.
|
|
320
|
+
class: OnceLock<usize>,
|
|
321
|
+
_value: PhantomData<fn() -> T>,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// SAFETY: the raw type holds only pointers to statics and functions,
|
|
325
|
+
// which Ruby reads and nothing writes.
|
|
326
|
+
unsafe impl<T> Send for DataType<T> {}
|
|
327
|
+
unsafe impl<T> Sync for DataType<T> {}
|
|
328
|
+
|
|
329
|
+
impl<T> DataType<T> {
|
|
330
|
+
pub const fn new(name: &'static CStr) -> Self {
|
|
331
|
+
DataType {
|
|
332
|
+
raw: rb_data_type_t {
|
|
333
|
+
wrap_struct_name: name.as_ptr(),
|
|
334
|
+
function: rb_data_type_struct__bindgen_ty_1 {
|
|
335
|
+
dmark: None,
|
|
336
|
+
dfree: Some(free_box::<T>),
|
|
337
|
+
dsize: Some(size_box::<T>),
|
|
338
|
+
dcompact: None,
|
|
339
|
+
reserved: [ptr::null_mut(); 1],
|
|
340
|
+
},
|
|
341
|
+
parent: ptr::null(),
|
|
342
|
+
data: ptr::null_mut(),
|
|
343
|
+
// RUBY_TYPED_FREE_IMMEDIATELY: dropping never blocks or calls Ruby.
|
|
344
|
+
flags: 1,
|
|
329
345
|
},
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
flags: 1,
|
|
334
|
-
})
|
|
346
|
+
class: OnceLock::new(),
|
|
347
|
+
_value: PhantomData,
|
|
348
|
+
}
|
|
335
349
|
}
|
|
336
350
|
|
|
337
351
|
const fn ptr(&'static self) -> *const rb_data_type_t {
|
|
338
|
-
&raw const self.
|
|
352
|
+
&raw const self.raw
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Defines the class, `name` under `outer`, whose objects hold a `T`.
|
|
356
|
+
pub fn register(&'static self, outer: VALUE, name: &CStr) -> RbResult<VALUE> {
|
|
357
|
+
let class = define_class_under(outer, name)?;
|
|
358
|
+
let _ = self.class.set(class as usize);
|
|
359
|
+
Ok(class)
|
|
339
360
|
}
|
|
340
361
|
|
|
341
|
-
/// Wraps `value` in a new object of
|
|
342
|
-
pub fn wrap
|
|
362
|
+
/// Wraps `value` in a new object of the class.
|
|
363
|
+
pub fn wrap(&'static self, value: T) -> RbResult<VALUE> {
|
|
364
|
+
let class = *self
|
|
365
|
+
.class
|
|
366
|
+
.get()
|
|
367
|
+
.ok_or_else(|| RubyErr::runtime("native class not registered"))?
|
|
368
|
+
as VALUE;
|
|
343
369
|
let raw = Box::into_raw(Box::new(value));
|
|
344
370
|
protect(|| unsafe { rb_sys::rb_data_typed_object_wrap(class, raw.cast(), self.ptr()) })
|
|
345
371
|
.inspect_err(|_| drop(unsafe { Box::from_raw(raw) }))
|
|
346
372
|
}
|
|
347
373
|
|
|
348
|
-
/// The `T` inside `value
|
|
349
|
-
|
|
350
|
-
/// # Safety
|
|
351
|
-
///
|
|
352
|
-
/// `T` must be the type this data type was made for.
|
|
353
|
-
pub unsafe fn get<T>(&'static self, value: VALUE) -> RbResult<&'static T> {
|
|
374
|
+
/// The `T` inside `value`, which must be an object of the class.
|
|
375
|
+
pub fn get(&'static self, value: VALUE) -> RbResult<&'static T> {
|
|
354
376
|
let mut ptr = ptr::null_mut();
|
|
355
377
|
protect(|| unsafe {
|
|
356
378
|
ptr = rb_sys::rb_check_typeddata(value, self.ptr());
|
|
@@ -359,6 +381,8 @@ impl DataType {
|
|
|
359
381
|
if ptr.is_null() {
|
|
360
382
|
return Err(RubyErr::runtime("native object is empty"));
|
|
361
383
|
}
|
|
384
|
+
// SAFETY: Ruby checked that `value` has this data type, whose
|
|
385
|
+
// objects hold a boxed `T` that lives as long as the object.
|
|
362
386
|
Ok(unsafe { &*ptr.cast::<T>() })
|
|
363
387
|
}
|
|
364
388
|
}
|
|
@@ -403,27 +427,60 @@ pub fn define_const(module: VALUE, name: &CStr, value: VALUE) -> RbResult<()> {
|
|
|
403
427
|
Ok(())
|
|
404
428
|
}
|
|
405
429
|
|
|
406
|
-
///
|
|
407
|
-
/// argument as a `VALUE`.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
430
|
+
/// Defines method `$name` of `$class`, which runs `$body` with the
|
|
431
|
+
/// receiver as `$this` and each argument as a `VALUE`. The macro writes
|
|
432
|
+
/// the C function and counts its arguments, so Ruby's arity matches it.
|
|
433
|
+
macro_rules! method {
|
|
434
|
+
($class:expr, $name:expr, |$this:ident $(, $arg:ident)*| $body:expr) => {{
|
|
435
|
+
unsafe extern "C" fn shim(
|
|
436
|
+
$this: ::rb_sys::VALUE $(, $arg: ::rb_sys::VALUE)*
|
|
437
|
+
) -> ::rb_sys::VALUE {
|
|
438
|
+
$crate::rb::wrap(|| $body)
|
|
439
|
+
}
|
|
440
|
+
let arity = 0 $(+ $crate::rb::one(stringify!($arg)))*;
|
|
441
|
+
$crate::rb::define(false, $class, $name, shim as *const (), arity)
|
|
442
|
+
}};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// [`method!`] on the singleton class: `$this` is the class or module.
|
|
446
|
+
macro_rules! singleton {
|
|
447
|
+
($object:expr, $name:expr, |$this:ident $(, $arg:ident)*| $body:expr) => {{
|
|
448
|
+
unsafe extern "C" fn shim(
|
|
449
|
+
$this: ::rb_sys::VALUE $(, $arg: ::rb_sys::VALUE)*
|
|
450
|
+
) -> ::rb_sys::VALUE {
|
|
451
|
+
$crate::rb::wrap(|| $body)
|
|
452
|
+
}
|
|
453
|
+
let arity = 0 $(+ $crate::rb::one(stringify!($arg)))*;
|
|
454
|
+
$crate::rb::define(true, $object, $name, shim as *const (), arity)
|
|
455
|
+
}};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
pub(crate) use {method, singleton};
|
|
459
|
+
|
|
460
|
+
/// One argument, for the arity [`method!`] counts.
|
|
461
|
+
pub const fn one(_: &str) -> i32 {
|
|
462
|
+
1
|
|
415
463
|
}
|
|
416
464
|
|
|
417
|
-
///
|
|
418
|
-
|
|
465
|
+
/// Defines `func`, a C function of `arity` arguments after the receiver,
|
|
466
|
+
/// as a method of `object`, or of its singleton class. Only [`method!`]
|
|
467
|
+
/// and [`singleton!`] call it, with a `func` they made for that arity.
|
|
468
|
+
pub fn define(
|
|
469
|
+
singleton: bool,
|
|
419
470
|
object: VALUE,
|
|
420
471
|
name: &CStr,
|
|
421
472
|
func: *const (),
|
|
422
473
|
arity: i32,
|
|
423
474
|
) -> RbResult<()> {
|
|
475
|
+
// SAFETY: Ruby calls `func` with the receiver and `arity` VALUEs,
|
|
476
|
+
// the parameters `method!` gave it.
|
|
424
477
|
let func = unsafe { std::mem::transmute::<*const (), unsafe extern "C" fn() -> VALUE>(func) };
|
|
425
478
|
protect(|| unsafe {
|
|
426
|
-
|
|
479
|
+
if singleton {
|
|
480
|
+
rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(func), arity);
|
|
481
|
+
} else {
|
|
482
|
+
rb_sys::rb_define_method(object, name.as_ptr(), Some(func), arity);
|
|
483
|
+
}
|
|
427
484
|
qnil()
|
|
428
485
|
})?;
|
|
429
486
|
Ok(())
|
data/ext/zzq_native/src/wake.rs
CHANGED
|
@@ -16,8 +16,7 @@ use rb_sys::VALUE;
|
|
|
16
16
|
|
|
17
17
|
use crate::rb::{self, DataType, RbResult, RubyErr};
|
|
18
18
|
|
|
19
|
-
static TYPE: DataType = DataType::new
|
|
20
|
-
static CLASS: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
|
|
19
|
+
static TYPE: DataType<Wake> = DataType::new(c"ZZQ::Native::Wake");
|
|
21
20
|
|
|
22
21
|
/// A pipe one fiber waits on.
|
|
23
22
|
#[derive(Debug)]
|
|
@@ -124,32 +123,21 @@ unsafe fn pipe(fds: &mut [RawFd; 2]) -> i32 {
|
|
|
124
123
|
|
|
125
124
|
/// The `Wake` inside `value`.
|
|
126
125
|
pub fn get(value: VALUE) -> RbResult<&'static Wake> {
|
|
127
|
-
|
|
126
|
+
TYPE.get(value)
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
pub fn register(native: VALUE) -> RbResult<()> {
|
|
130
|
+
let class = TYPE.register(native, c"Wake")?;
|
|
131
|
+
rb::singleton!(class, c"new", |_class| {
|
|
132
132
|
let wake = Wake::new().map_err(|e| RubyErr::runtime(format!("pipe failed: {e}")))?;
|
|
133
|
-
TYPE.wrap(
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
rb::
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
unsafe extern "C" fn wake_clear(this: VALUE) -> VALUE {
|
|
142
|
-
rb::wrap(|| {
|
|
133
|
+
TYPE.wrap(wake)
|
|
134
|
+
})?;
|
|
135
|
+
rb::method!(class, c"fd", |this| Ok(rb::u64_value(
|
|
136
|
+
get(this)?.pipe.read_fd as u64
|
|
137
|
+
)))?;
|
|
138
|
+
rb::method!(class, c"clear", |this| {
|
|
143
139
|
get(this)?.clear();
|
|
144
140
|
Ok(rb::qnil())
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
pub fn register(native: VALUE) -> RbResult<()> {
|
|
149
|
-
let class = rb::define_class_under(native, c"Wake")?;
|
|
150
|
-
let _ = CLASS.set(class as usize);
|
|
151
|
-
rb::define_singleton_method(class, c"new", wake_new as *const (), 0)?;
|
|
152
|
-
rb::define_method(class, c"fd", wake_fd as *const (), 0)?;
|
|
153
|
-
rb::define_method(class, c"clear", wake_clear as *const (), 0)?;
|
|
141
|
+
})?;
|
|
154
142
|
Ok(())
|
|
155
143
|
}
|
data/lib/zzq/version.rb
CHANGED