libmapper_rs/
device.rs

1use std::ffi::CString;
2use std::os::raw::c_int;
3use std::ptr;
4use std::time::Duration;
5use crate::bindings::{mpr_dev, mpr_dev_free, mpr_dev_get_is_ready, mpr_dev_get_sigs, mpr_dev_new, mpr_dev_poll, mpr_dir, mpr_obj, mpr_prop, mpr_sig_new, mpr_type};
6use crate::graph::Graph;
7use crate::object::MapperObject;
8use crate::signal::Signal;
9
10/// A device is libmapper's connection to the distributed graph.
11/// Each device is a collection of signal instances and their metadata.
12///
13/// # Examples
14/// ```
15/// use libmapper_rs::device::Device;
16/// use std::time::Duration;
17/// // you can create a device with Device::create
18/// let dev = Device::create("rust");
19/// // you have to poll a device occasionally to make things happen
20/// loop {
21///     dev.poll_and_block(Duration::from_millis(10)); // poll in 10ms intervals
22///     if dev.is_ready() {
23///        break;
24///     }
25/// }
26/// // create signals, etc...
27/// ```
28pub struct Device<'a> {
29    pub(crate) handle: mpr_dev,
30    pub(crate) owned: bool,
31    pub(crate) graph: Option<&'a Graph>
32}
33
34unsafe impl Send for Device<'_> {}
35unsafe impl Sync for Device<'_> {}
36
37impl Drop for Device<'_> {
38    fn drop(&mut self) {
39        if self.owned {
40            unsafe {
41                mpr_dev_free(self.handle);
42            }
43        }
44    }
45}
46
47impl Device<'_> {
48    /// Create a new device with the given name.
49    /// The device will use it's own connection to the graph.
50    ///
51    /// Before calling any other methods on the device, you should poll it until it is ready.
52    /// 
53    /// # Notes
54    /// If you plan on creating multiple devices, consider using (Device::create_from_graph)[Device::create_from_graph] instead to pool resources.
55    pub fn create(name: &str) -> Device {
56        let name_ptr = CString::new(name).expect("CString::new failed");
57        unsafe {
58            Device {
59                owned: true,
60                handle: mpr_dev_new(name_ptr.as_ptr(), ptr::null_mut()),
61                graph: None
62            }
63        }
64    }
65    /// Create a new device with a shared graph.
66    /// Sharing a graph between devices allows them to pool some resources and networking, potentially improving performance.
67    pub fn create_from_graph<'a>(name: &str, graph: &'a Graph) -> Device<'a> {
68        let name_ptr = CString::new(name).expect("CString::new failed");
69        unsafe {
70            Device {
71                owned: true,
72                handle: mpr_dev_new(name_ptr.as_ptr(), graph.handle),
73                graph: Some(graph)
74            }
75        }
76    }
77}
78
79impl Device<'_> {
80    /// Poll the device without blocking
81    ///
82    /// # Notes
83    /// You may want to use [poll_all](Device::poll_all) in a multithreaded enviroment,
84    /// when using non-blocking polling libmapper will use a heuristic to determine how many messages
85    /// to parse at once for performance. If you don't care how long this function will take to run,
86    /// call Device::poll_all.
87    pub fn poll(&self) {
88        unsafe {
89            mpr_dev_poll(self.handle, 0);
90        }
91    }
92    /// Processes all messages in the device's queue, no matter how long it takes.
93    /// If using dedicated threads to poll devices this is probably what you want to use instead of [poll](Device::poll)
94    pub fn poll_all(&self) {
95        unsafe {
96            mpr_dev_poll(self.handle, -1);
97        }
98    }
99    /// Blocks the current thread for a specified amount of time.
100    /// Use this function instead of sleeping in a loop.
101    pub fn poll_and_block(&self, time: Duration) {
102        unsafe {
103            mpr_dev_poll(self.handle, time.as_millis() as c_int);
104        }
105    }
106}
107
108impl Device<'_> {
109    /// Tests if the device is ready to use.
110    /// Do not try to call any other methods until this returns `true`.
111    pub fn is_ready(&self) -> bool {
112        unsafe {
113            mpr_dev_get_is_ready(self.handle) > 0
114        }
115    }
116}
117
118/// Marker trait for types that are bit-compatible with the libmapper C library.
119/// If this trait is implemented on a type, that type can be passed to libmapper functions safely.
120/// Use the `get_mpr_type` function to pass a type parameter to libmapper.
121pub trait MappableType {
122    /// Get the `mpr_type` representing this rust type.
123    fn get_mpr_type() -> mpr_type;
124}
125
126impl MappableType for f64 {
127    fn get_mpr_type() -> mpr_type {
128        mpr_type::MPR_DBL
129    }
130}
131
132impl MappableType for mpr_type {
133    fn get_mpr_type() -> mpr_type {
134        mpr_type::MPR_TYPE
135    }
136}
137
138impl MappableType for f32 {
139    fn get_mpr_type() -> mpr_type {
140        mpr_type::MPR_FLT
141    }
142}
143
144impl MappableType for i32 {
145    fn get_mpr_type() -> mpr_type {
146        mpr_type::MPR_INT32
147    }
148}
149
150impl MappableType for i64 {
151    fn get_mpr_type() -> mpr_type {
152        mpr_type::MPR_INT64
153    }
154}
155
156impl<'a> Device<'a> {
157    /// Get the shared graph used by this device.
158    /// If the device was created with [Device::create](Device::create) this will return None.
159    pub fn get_graph(&self) -> Option<&'a Graph> {
160        self.graph
161    }
162}
163
164impl Device<'_> {
165    /// Check if the device was created with a shared graph.
166    pub fn has_shared_graph(&self) -> bool {
167        self.graph.is_some()
168    }
169    /// Create a signal with the given name and direction.
170    /// 
171    /// # Notes
172    /// - The signal will have a vector length of 1 (i.e. single value).
173    /// - The passed generic parameter controls what type of data the signal will hold.
174    /// 
175    /// # Examples
176    /// ```
177    /// use libmapper_rs::device::Device;
178    /// use libmapper_rs::constants::mpr_dir;
179    /// fn setup_signals(dev: &Device) {
180    ///     // create an outgoing signal that outputs a single f64 value
181    ///     let sig = dev.create_signal::<f64>("test_signal", mpr_dir::MPR_DIR_OUT);
182    /// }
183    /// ```
184    pub fn create_signal<T: MappableType + Copy>(&self, name: &str, direction: mpr_dir) -> Signal {
185        self.create_vector_signal::<T>(name, direction, 1)
186    }
187    /// Create a signal with the given name, direction, and vector length.
188    /// 
189    /// # Notes
190    /// - The passed generic parameter controls what type of data the signal will hold.
191    /// 
192    pub fn create_vector_signal<T: MappableType + Copy>(&self, name: &str, direction: mpr_dir, vector_length: u32) -> Signal {
193        let data_type: mpr_type = T::get_mpr_type();
194
195        let name_ptr = CString::new(name).expect("CString::new failed");
196        unsafe {
197            Signal {
198                handle: mpr_sig_new(self.handle, direction, name_ptr.as_ptr(), vector_length as i32, 
199                    data_type, ptr::null(), ptr::null(), ptr::null(), ptr::null_mut(), None, 0),
200                data_type,
201                owned: true,
202                vector_length
203            }
204        }
205    }
206    /// Get a list of all signals owned by this device.
207    pub fn get_signals(&self, direction: mpr_dir) -> Vec<Signal> {
208        let list = unsafe {mpr_dev_get_sigs(self.handle, direction)};
209        crate::util::read_list(list, |ptr| {
210            let data_type = (ptr as mpr_obj).get_property::<mpr_type>(mpr_prop::MPR_PROP_TYPE).unwrap();
211            let vector_length = (ptr as mpr_obj).get_property::<i32>(mpr_prop::MPR_PROP_LEN).unwrap() as u32;
212            Signal {
213                handle: ptr,
214                data_type,
215                owned: false,
216                vector_length 
217            }
218        })
219    }
220}