1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use std::ffi::CString;
use std::os::raw::c_int;
use std::ptr;
use std::time::Duration;
use crate::bindings::{mpr_dev, mpr_dev_free, mpr_dev_get_is_ready, mpr_dev_new, mpr_dev_poll, mpr_dir, mpr_sig_new, mpr_type};
use crate::graph::Graph;
use crate::signal::Signal;

/// A device is libmapper's connection to the distributed graph.
/// Each device is a collection of signal instances and their metadata.
///
/// # Examples
/// ```
/// use libmapper_rs::device::Device;
/// use std::time::Duration;
/// // you can create a device with Device::create
/// let dev = Device::create("rust");
/// // you have to poll a device occasionally to make things happen
/// loop {
///     dev.poll_and_block(Duration::from_millis(10)); // poll in 10ms intervals
///     if dev.is_ready() {
///        break;
///     }
/// }
/// // create signals, etc...
/// ```
pub struct Device<'a> {
    pub(crate) handle: mpr_dev,
    owned: bool,
    graph: Option<&'a Graph>
}

unsafe impl Send for Device<'_> {}
unsafe impl Sync for Device<'_> {}

impl Drop for Device<'_> {
    fn drop(&mut self) {
        if self.owned {
            unsafe {
                mpr_dev_free(self.handle);
            }
        }
    }
}

impl Device<'_> {
    /// Create a new device with the given name.
    /// The device will use it's own connection to the graph.
    ///
    /// Before calling any other methods on the device, you should poll it until it is ready.
    /// 
    /// # Notes
    /// If you plan on creating multiple devices, consider using (Device::create_from_graph)[Device::create_from_graph] instead to pool resources.
    pub fn create(name: &str) -> Device {
        let name_ptr = CString::new(name).expect("CString::new failed");
        unsafe {
            Device {
                owned: true,
                handle: mpr_dev_new(name_ptr.as_ptr(), ptr::null_mut()),
                graph: None
            }
        }
    }
    /// Create a new device with a shared graph.
    /// Sharing a graph between devices allows them to pool some resources and networking, potentially improving performance.
    pub fn create_from_graph<'a>(name: &str, graph: &'a Graph) -> Device<'a> {
        let name_ptr = CString::new(name).expect("CString::new failed");
        unsafe {
            Device {
                owned: true,
                handle: mpr_dev_new(name_ptr.as_ptr(), graph.handle),
                graph: Some(graph)
            }
        }
    }
}

impl Device<'_> {
    /// Poll the device without blocking
    ///
    /// # Notes
    /// You may want to use [poll_all](Device::poll_all) in a multithreaded enviroment,
    /// when using non-blocking polling libmapper will use a heuristic to determine how many messages
    /// to parse at once for performance. If you don't care how long this function will take to run,
    /// call Device::poll_all.
    pub fn poll(&self) {
        unsafe {
            mpr_dev_poll(self.handle, 0);
        }
    }
    /// Processes all messages in the device's queue, no matter how long it takes.
    /// If using dedicated threads to poll devices this is probably what you want to use instead of [poll](Device::poll)
    pub fn poll_all(&self) {
        unsafe {
            mpr_dev_poll(self.handle, -1);
        }
    }
    /// Blocks the current thread for a specified amount of time.
    /// Use this function instead of sleeping in a loop.
    pub fn poll_and_block(&self, time: Duration) {
        unsafe {
            mpr_dev_poll(self.handle, time.as_millis() as c_int);
        }
    }
}

impl Device<'_> {
    /// Tests if the device is ready to use.
    /// Do not try to call any other methods until this returns `true`.
    pub fn is_ready(&self) -> bool {
        unsafe {
            mpr_dev_get_is_ready(self.handle) > 0
        }
    }
}

/// Marker trait for types that are bit-compatible with the libmapper C library.
/// If this trait is implemented on a type, that type can be passed to libmapper functions safely.
/// Use the `get_mpr_type` function to pass a type parameter to libmapper.
pub trait MappableType {
    /// Get the `mpr_type` representing this rust type.
    fn get_mpr_type() -> mpr_type;
}

impl MappableType for f64 {
    fn get_mpr_type() -> mpr_type {
        mpr_type::MPR_DBL
    }
}

impl MappableType for f32 {
    fn get_mpr_type() -> mpr_type {
        mpr_type::MPR_FLT
    }
}

impl MappableType for i32 {
    fn get_mpr_type() -> mpr_type {
        mpr_type::MPR_INT32
    }
}

impl MappableType for i64 {
    fn get_mpr_type() -> mpr_type {
        mpr_type::MPR_INT64
    }
}

impl<'a> Device<'a> {
    /// Get the shared graph used by this device.
    /// If the device was created with [Device::create](Device::create) this will return None.
    pub fn get_graph(&self) -> Option<&'a Graph> {
        self.graph
    }
}

impl Device<'_> {
    /// Check if the device was created with a shared graph.
    pub fn has_shared_graph(&self) -> bool {
        self.graph.is_some()
    }
    /// Create a signal with the given name and direction.
    /// 
    /// # Notes
    /// - The signal will have a vector length of 1 (i.e. single value).
    /// - The passed generic parameter controls what type of data the signal will hold.
    /// 
    /// # Examples
    /// ```
    /// use libmapper_rs::device::Device;
    /// use libmapper_rs::constants::mpr_dir;
    /// fn setup_signals(dev: &Device) {
    ///     // create an outgoing signal that outputs a single f64 value
    ///     let sig = dev.create_signal::<f64>("test_signal", mpr_dir::MPR_DIR_OUT);
    /// }
    /// ```
    pub fn create_signal<T: MappableType + Copy>(&self, name: &str, direction: mpr_dir) -> Signal {
        self.create_vector_signal::<T>(name, direction, 1)
    }
    /// Create a signal with the given name, direction, and vector length.
    /// 
    /// # Notes
    /// - The passed generic parameter controls what type of data the signal will hold.
    /// 
    pub fn create_vector_signal<T: MappableType + Copy>(&self, name: &str, direction: mpr_dir, vector_length: u32) -> Signal {
        let data_type: mpr_type = T::get_mpr_type();

        let name_ptr = CString::new(name).expect("CString::new failed");
        unsafe {
            Signal {
                handle: mpr_sig_new(self.handle, direction, name_ptr.as_ptr(), vector_length as i32, 
                    data_type, ptr::null(), ptr::null(), ptr::null(), ptr::null_mut(), None, 0),
                data_type,
                owned: true,
                vector_length
            }
        }
    }
}