From 7491bf682b8a4708d92e9efc7596fe41c150c88b Mon Sep 17 00:00:00 2001 From: korbinian Date: Sat, 7 Jun 2025 15:22:07 +0200 Subject: [PATCH 1/5] feat: allow non Vec types to be pushed --- src/lib.rs | 125 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 81d02b1..7eb4995 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,6 @@ pub fn local_clock() -> f64 { unsafe { lsl_local_clock() } } - // ========================== // === Stream Declaration === // ========================== @@ -270,7 +269,9 @@ impl StreamInfo { source_id.as_ptr(), ); match handle.is_null() { - false => Ok(StreamInfo { handle: rc::Rc::new(StreamInfoHandle { handle }) }), + false => Ok(StreamInfo { + handle: rc::Rc::new(StreamInfoHandle { handle }), + }), true => Err(Error::ResourceCreation), } } @@ -290,7 +291,7 @@ impl StreamInfo { experimenter). */ pub fn stream_name(&self) -> String { - unsafe { make_string(lsl_get_name(self.handle.handle )) } + unsafe { make_string(lsl_get_name(self.handle.handle)) } } /** @@ -303,7 +304,7 @@ impl StreamInfo { search for: XDF meta-data). */ pub fn stream_type(&self) -> String { - unsafe { make_string(lsl_get_type(self.handle.handle )) } + unsafe { make_string(lsl_get_type(self.handle.handle)) } } /** @@ -311,7 +312,7 @@ impl StreamInfo { A stream has at least one channel; the channel count stays constant for all samples. */ pub fn channel_count(&self) -> i32 { - unsafe { lsl_get_channel_count(self.handle.handle ) } + unsafe { lsl_get_channel_count(self.handle.handle) } } /** @@ -418,7 +419,7 @@ impl StreamInfo { cursor: lsl_get_desc(self.handle.handle), // keep a shared ref of the underlying native handle since the xml element or // elements obtained from it may outlive the StreamInfo object - doc: self.handle.clone() + doc: self.handle.clone(), } } } @@ -487,7 +488,9 @@ impl StreamInfo { unsafe { let handle = lsl_streaminfo_from_xml(xml.as_ptr()); match handle.is_null() { - false => Ok(StreamInfo { handle: rc::Rc::new(StreamInfoHandle { handle }) }), + false => Ok(StreamInfo { + handle: rc::Rc::new(StreamInfoHandle { handle }), + }), true => Err(Error::ResourceCreation), } } @@ -506,7 +509,9 @@ impl StreamInfo { !handle.is_null(), "Attempted to create a StreamInfo from a NULL handle." ); - StreamInfo { handle: rc::Rc::new(StreamInfoHandle { handle } ) } + StreamInfo { + handle: rc::Rc::new(StreamInfoHandle { handle }), + } } // Get the native implementation handle. @@ -523,7 +528,9 @@ impl Clone for StreamInfo { !handle.is_null(), "Failed to clone native lsl_streaminfo object." ); - StreamInfo { handle: rc::Rc::new(StreamInfoHandle { handle }) } + StreamInfo { + handle: rc::Rc::new(StreamInfoHandle { handle }), + } } } } @@ -669,16 +676,22 @@ impl StreamOutlet { with subsequent samples. Typically this would be `true`. Note that the `chunk_size`, if specified at outlet construction, takes precedence over the pushthrough flag. */ - fn safe_push_numeric( + fn safe_push_numeric, V>( &self, - func: NativePushFunction, - data: &vec::Vec, + func: NativePushFunction, + data: &T, timestamp: f64, pushthrough: bool, ) -> Result<()> { + let data = data.as_ref(); self.assert_len(data.len()); unsafe { - errcode_to_result(func(self.handle, data.as_ptr(), timestamp, pushthrough as i32))?; + errcode_to_result(func( + self.handle, + data.as_ptr(), + timestamp, + pushthrough as i32, + ))?; } Ok(()) } @@ -695,12 +708,13 @@ impl StreamOutlet { with subsequent samples. Typically this would be `true`. Note that the `chunk_size`, if specified at outlet construction, takes precedence over the pushthrough flag. */ - fn safe_push_blob>( + fn safe_push_blob, T: AsRef<[u8]>>( &self, - data: &vec::Vec, + data: &U, timestamp: f64, pushthrough: bool, ) -> Result<()> { + let data = data.as_ref(); self.assert_len(data.len()); let ptrs: Vec<_> = data.iter().map(|x| x.as_ref().as_ptr()).collect(); let lens: Vec<_> = data @@ -730,7 +744,7 @@ See also the `ExPushable` trait for the extended-argument versions of these meth **Note:** If you push in data that as the wrong size (array length not matching the declared number of channels), these functions will trigger an assertion and panic. */ -pub trait Pushable { +pub trait Pushable, V> { /** Push a vector of values of some type as a sample into the outlet. Each entry in the vector corresponds to one channel. The function handles type checking & conversion. @@ -772,7 +786,11 @@ pub trait Pushable { } // Pushable is basically a convenience layer on top of ExPushable -impl> Pushable for U { +impl Pushable for U +where + T: AsRef<[V]>, + U: ExPushable, +{ fn push_sample(&self, data: &T) -> Result<()> { self.push_sample_ex(data, 0.0, true) } @@ -795,7 +813,7 @@ See also the `Pushable` trait for the simpler methods `push_sample()` and `pu **Note:** If you push in data that as the wrong size (array length not matching the declared number of channels), these functions will trigger an assertion and panic. */ -pub trait ExPushable: HasNominalRate { +pub trait ExPushable, V>: HasNominalRate { /** Push a vector of values of some type as a sample into the outlet. Each entry in the vector corresponds to one channel. The function handles type checking & @@ -887,57 +905,86 @@ pub trait ExPushable: HasNominalRate { } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[f32]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_ftp, data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[f64]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_dtp, data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[i8]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_ctp, data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[i16]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_stp, data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[i32]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_itp, data, timestamp, pushthrough) } } #[cfg(not(windows))] // TODO: once we upgrade to liblsl 1.14, we can drop this platform restriction impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { + fn push_sample_ex( + &self, + data: &vec::Vec, + timestamp: f64, + pushthrough: bool, + ) -> Result<()> { self.safe_push_numeric(lsl_push_sample_ltp, data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec, timestamp: f64, pushthrough: bool) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[String]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_blob(data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec<&str>, timestamp: f64, pushthrough: bool) -> Result<()> { +impl<'a, T> ExPushable for StreamOutlet +where + T: AsRef<[&'a str]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_blob(data, timestamp, pushthrough) } } -impl ExPushable> for StreamOutlet { - fn push_sample_ex(&self, data: &vec::Vec<&[u8]>, timestamp: f64, pushthrough: bool) -> Result<()> { +impl<'a, T> ExPushable for StreamOutlet +where + T: AsRef<[&'a [u8]]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_blob(data, timestamp, pushthrough) } } @@ -2110,7 +2157,9 @@ impl Drop for ContinuousResolver { // wrapper around a native streaminfo handle #[derive(Debug)] -struct StreamInfoHandle { handle: lsl_streaminfo } +struct StreamInfoHandle { + handle: lsl_streaminfo, +} impl Drop for StreamInfoHandle { fn drop(&mut self) { @@ -2213,9 +2262,7 @@ impl std::error::Error for Error {} fn make_cstring(s: &str) -> ffi::CString { // If you're getting this, you passed a string containing 0 bytes to the library. In the // context where it happened, this is a fatal error. - ffi::CString::new(s).expect( - "Embedded zero bytes are invalid in strings passed to liblsl.", - ) + ffi::CString::new(s).expect("Embedded zero bytes are invalid in strings passed to liblsl.") } // Internal function that creates a String from a const char* returned by a trusted C routine. From 1438883d1ba2f04c65d48c965ce6a5de0559f504 Mon Sep 17 00:00:00 2001 From: korbinian Date: Sat, 7 Jun 2025 15:40:22 +0200 Subject: [PATCH 2/5] chore: update docs --- src/lib.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7eb4995..4390251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -669,7 +669,7 @@ impl StreamOutlet { Arguments: * `func`: the native FFI function to call to push a sample - * `data`: A vector of values to push (one for each channel). + * `data`: A collection of values, treatable as a slice, to push (one for each channel). * `timestamp`: Optionally the capture time of the sample, in agreement with `local_clock()`; if passed as 0.0, the current time is used. * `pushthrough`: Whether to push the sample through to the receivers instead of buffering it @@ -701,7 +701,7 @@ impl StreamOutlet { byte slices via `.as_ref()`. Arguments: - * `data`: A vector of values to push (one for each channel). + * `data`: A collection of values, treatable as a slice, to push (one for each channel). * `timestamp`: Optionally the capture time of the sample, in agreement with `local_clock()`; if passed as 0.0, the current time is used. * `pushthrough`: Whether to push the sample through to the receivers instead of buffering it @@ -739,15 +739,16 @@ A trait that enables the methods `push_sample()` and `push_chunk()`. Imple StreamOutlet. See also the `ExPushable` trait for the extended-argument versions of these methods, -`push_sample_ex()` and `push_chunk_ex()`. +`push_sample_ex()` and `push_chunk_ex()`. **Note:** If you push in data that as the wrong size (array length not matching the declared number of channels), these functions will trigger an assertion and panic. */ pub trait Pushable, V> { /** - Push a vector of values of some type as a sample into the outlet. Each entry in the vector - corresponds to one channel. The function handles type checking & conversion. + Push a collection, treatable as a slice, of values of some type as a sample into the outlet. + Each entry in the collection corresponds to one channel. The function handles type + checking & conversion. The data are time-stamped with the current time (using `local_clock()`), and immediately transmitted (unless a `chunk_size` was provided at outlet construction, which overrides in what @@ -759,7 +760,8 @@ pub trait Pushable, V> { /** Push a chunk of samples (batched into a `Vec`) into the outlet. Each element of the given - vector must itself be in a format accepted by `push_sample()` (e.g., `Vec`). + vector must itself be in a format accepted by `push_sample()` (e.g. all types that can be + treated as a slice). The data are time-stamped with the current time (using `local_clock()`), and immediately transmitted (unless a `chunk_size` was provided at outlet construction, which causes the data @@ -774,7 +776,8 @@ pub trait Pushable, V> { sample (for irregular-rate streams) into the outlet. Arguments: - * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g., `Vec`). + * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g. all types + that can be treated as a slice). * `timestamps`: A `Vec` of capture times for each sample, in agreement with `local_clock()`. The data are immediately transmitted (unless a `chunk_size` was provided at outlet @@ -805,7 +808,7 @@ where } /** -A trait that enables the methods `push_sample_ex()` and `push_chunk_ex()`. +A trait that enables the methods `push_sample_ex()` and `push_chunk_ex()`. Implemented by StreamOutlet. See also the `Pushable` trait for the simpler methods `push_sample()` and `push_chunk()`. @@ -820,7 +823,7 @@ pub trait ExPushable, V>: HasNominalRate { conversion. Arguments: - * `data`: A vector of values to push (one for each channel). + * `data`: A collection of values, treatable as a slice, to push (one for each channel). * `timestamp`: Optionally the capture time of the sample, in agreement with `local_clock()`; if passed as 0.0, the current time is used. * `pushthrough`: Whether to push the sample through to the receivers instead of buffering it @@ -836,7 +839,8 @@ pub trait ExPushable, V>: HasNominalRate { Push a chunk of samples (batched into a `Vec`) into the outlet. Arguments: - * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g., `Vec`). + * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g. all types + that can be treated as a slice). * `timestamp`: Optionally the capture time of the most recent sample, in agreement with `local_clock()`; if specified as 0.0, the current time is used. The time stamps of other samples are automatically derived according to the sampling rate of the stream. @@ -879,7 +883,8 @@ pub trait ExPushable, V>: HasNominalRate { Allows for specifying a separate time stamp for each sample (for irregular-rate streams). Arguments: - * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g., `Vec`). + * `samples`: A `Vec` of samples, each in a format accepted by `push_sample()` (e.g. all types + that can be treated as a slice). * `timestamps`: A `Vec` of capture times for each sample, in agreement with `local_clock()`. * `pushthrough`: Whether to push the chunk through to the receivers instead of buffering it with subsequent samples. Typically this would be `true`. Note that the `chunk_size`, if @@ -950,14 +955,11 @@ where } } -#[cfg(not(windows))] // TODO: once we upgrade to liblsl 1.14, we can drop this platform restriction -impl ExPushable> for StreamOutlet { - fn push_sample_ex( - &self, - data: &vec::Vec, - timestamp: f64, - pushthrough: bool, - ) -> Result<()> { +impl ExPushable for StreamOutlet +where + T: AsRef<[i64]>, +{ + fn push_sample_ex(&self, data: &T, timestamp: f64, pushthrough: bool) -> Result<()> { self.safe_push_numeric(lsl_push_sample_ltp, data, timestamp, pushthrough) } } From 57a833ec97d858f98249140820977b2957641f44 Mon Sep 17 00:00:00 2001 From: korbinian Date: Sat, 7 Jun 2025 15:52:08 +0200 Subject: [PATCH 3/5] chore: minor version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbe9ed5..85b2d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lsl" -version = "0.1.1" +version = "0.1.2" authors = ["Intheon ", "Christian Kothe "] edition = "2018" description = "Lab streaming layer (liblsl) bindings for rust." From 0dbc7400b48135021a633362bf5ef275ec563f22 Mon Sep 17 00:00:00 2001 From: korbinian Date: Wed, 9 Jul 2025 23:34:33 +0200 Subject: [PATCH 4/5] fix: use arc instead of rc to allow send and sync --- src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4390251..5906d9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ use lsl_sys::*; use std::convert::{From, TryFrom}; use std::ffi; use std::fmt; -use std::rc; +use std::sync::Arc; use std::vec; /// Constant to indicate that a stream has variable sampling rate. @@ -216,7 +216,7 @@ github repository). You can find various uses of the `StreamInfo` object in most #[derive(Debug)] pub struct StreamInfo { // internal fields - handle: rc::Rc, + handle: Arc, } impl StreamInfo { @@ -270,7 +270,7 @@ impl StreamInfo { ); match handle.is_null() { false => Ok(StreamInfo { - handle: rc::Rc::new(StreamInfoHandle { handle }), + handle: Arc::new(StreamInfoHandle { handle }), }), true => Err(Error::ResourceCreation), } @@ -489,7 +489,7 @@ impl StreamInfo { let handle = lsl_streaminfo_from_xml(xml.as_ptr()); match handle.is_null() { false => Ok(StreamInfo { - handle: rc::Rc::new(StreamInfoHandle { handle }), + handle: Arc::new(StreamInfoHandle { handle }), }), true => Err(Error::ResourceCreation), } @@ -510,7 +510,7 @@ impl StreamInfo { "Attempted to create a StreamInfo from a NULL handle." ); StreamInfo { - handle: rc::Rc::new(StreamInfoHandle { handle }), + handle: Arc::new(StreamInfoHandle { handle }), } } @@ -529,7 +529,7 @@ impl Clone for StreamInfo { "Failed to clone native lsl_streaminfo object." ); StreamInfo { - handle: rc::Rc::new(StreamInfoHandle { handle }), + handle: Arc::new(StreamInfoHandle { handle }), } } } @@ -1754,7 +1754,7 @@ intermittent zero bytes (otherwise this will trigger an assertion). pub struct XMLElement { // internal fields cursor: lsl_xml_ptr, - doc: rc::Rc, + doc: Arc, } impl XMLElement { From 0804c86ad986438f9049346108f8fe4616651199 Mon Sep 17 00:00:00 2001 From: korbinian Date: Wed, 9 Jul 2025 23:37:36 +0200 Subject: [PATCH 5/5] chore: bump minor version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85b2d26..5a6120a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lsl" -version = "0.1.2" +version = "0.1.3" authors = ["Intheon ", "Christian Kothe "] edition = "2018" description = "Lab streaming layer (liblsl) bindings for rust."