Skip to main content

rill_core/traits/
port.rs

1//! Port types and identifiers for the Rill ecosystem
2//!
3//! Ports are the connection points between nodes in the signal graph.
4//! Each output port owns a `FixedBuffer<T, BUF_SIZE>` and an optional `Action`
5//! that defines how data is produced. Input ports are connection endpoints
6//! that receive data from upstream output ports.
7
8use crate::buffer::{Buffer, FixedBuffer};
9use crate::math::vector::scalar::ScalarVector4;
10use crate::math::vector::traits::Vector as VecTrait;
11use crate::math::Transcendental;
12use crate::time::ClockTick;
13use crate::traits::algorithm::Algorithm;
14use crate::traits::node::NodeId;
15use crate::traits::processable::Processable;
16use crate::traits::PortError;
17use crate::traits::{Node, ProcessResult};
18use std::fmt;
19
20// ============================================================================
21// Port Type
22// ============================================================================
23
24/// Type of a port - what kind of signal it carries
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum PortType {
27    /// Signal port - carries signal blocks (signal data, sensor data, etc.)
28    Signal,
29
30    /// Control signal port - carries modulation/automation
31    Control,
32
33    /// Clock signal port - carries timing information
34    Clock,
35
36    /// Feedback port - stores state between blocks
37    Feedback,
38
39    /// Parameter port - for node parameters (special)
40    Param,
41}
42
43impl PortType {
44    /// Get the name of the port type
45    pub const fn name(&self) -> &'static str {
46        match self {
47            Self::Signal => "signal",
48            Self::Control => "control",
49            Self::Clock => "clock",
50            Self::Feedback => "feedback",
51            Self::Param => "param",
52        }
53    }
54
55    /// Check if this port carries signal-rate signals
56    pub const fn is_signal_rate(&self) -> bool {
57        matches!(self, Self::Signal)
58    }
59
60    /// Check if this port carries control-rate signals
61    pub const fn is_control_rate(&self) -> bool {
62        matches!(self, Self::Control)
63    }
64
65    /// Check if this port carries clock signals
66    pub const fn is_clock(&self) -> bool {
67        matches!(self, Self::Clock)
68    }
69}
70
71impl fmt::Display for PortType {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{}", self.name())
74    }
75}
76
77// ============================================================================
78// Port Direction
79// ============================================================================
80
81/// Direction of a port (input or output)
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub enum PortDirection {
84    /// Input port (receives data into the node)
85    Input,
86
87    /// Output port (sends data out of the node)
88    Output,
89}
90
91impl PortDirection {
92    /// Get the name of the direction
93    pub const fn name(&self) -> &'static str {
94        match self {
95            Self::Input => "input",
96            Self::Output => "output",
97        }
98    }
99
100    /// Check if this is an input port
101    pub const fn is_input(&self) -> bool {
102        matches!(self, Self::Input)
103    }
104
105    /// Check if this is an output port
106    pub const fn is_output(&self) -> bool {
107        matches!(self, Self::Output)
108    }
109}
110
111impl fmt::Display for PortDirection {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}", self.name())
114    }
115}
116
117// ============================================================================
118// Port ID
119// ============================================================================
120
121/// Unique identifier for a port within a graph
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub struct PortId {
124    node: NodeId,
125    port_type: PortType,
126    direction: PortDirection,
127    index: u16,
128}
129
130impl PortId {
131    /// Create a new port ID
132    pub const fn new(
133        node: NodeId,
134        port_type: PortType,
135        direction: PortDirection,
136        index: u16,
137    ) -> Self {
138        Self {
139            node,
140            port_type,
141            direction,
142            index,
143        }
144    }
145
146    // ========================================================================
147    // Signal Port Constructors
148    // ========================================================================
149
150    /// Create a new signal input port
151    pub const fn signal_in(node: NodeId, index: u16) -> Self {
152        Self::new(node, PortType::Signal, PortDirection::Input, index)
153    }
154
155    /// Create a new signal output port
156    pub const fn signal_out(node: NodeId, index: u16) -> Self {
157        Self::new(node, PortType::Signal, PortDirection::Output, index)
158    }
159
160    // ========================================================================
161    // Control Port Constructors
162    // ========================================================================
163
164    /// Create a new control input port
165    pub const fn control_in(node: NodeId, index: u16) -> Self {
166        Self::new(node, PortType::Control, PortDirection::Input, index)
167    }
168
169    /// Create a new control output port
170    pub const fn control_out(node: NodeId, index: u16) -> Self {
171        Self::new(node, PortType::Control, PortDirection::Output, index)
172    }
173
174    // ========================================================================
175    // Clock Port Constructors
176    // ========================================================================
177
178    /// Create a new clock input port
179    pub const fn clock_in(node: NodeId, index: u16) -> Self {
180        Self::new(node, PortType::Clock, PortDirection::Input, index)
181    }
182
183    /// Create a new clock output port
184    pub const fn clock_out(node: NodeId, index: u16) -> Self {
185        Self::new(node, PortType::Clock, PortDirection::Output, index)
186    }
187
188    // ========================================================================
189    // Feedback Port Constructors
190    // ========================================================================
191
192    /// Create a new feedback input port
193    pub const fn feedback_in(node: NodeId, index: u16) -> Self {
194        Self::new(node, PortType::Feedback, PortDirection::Input, index)
195    }
196
197    /// Create a new feedback output port
198    pub const fn feedback_out(node: NodeId, index: u16) -> Self {
199        Self::new(node, PortType::Feedback, PortDirection::Output, index)
200    }
201
202    // ========================================================================
203    // Parameter Port Constructors
204    // ========================================================================
205
206    /// Create a new parameter port (always input)
207    pub const fn param(node: NodeId, index: u16) -> Self {
208        Self::new(node, PortType::Param, PortDirection::Input, index)
209    }
210
211    // ========================================================================
212    // Getters
213    // ========================================================================
214
215    /// Get the node ID
216    pub const fn node_id(&self) -> NodeId {
217        self.node
218    }
219
220    /// Get the port type
221    pub const fn port_type(&self) -> PortType {
222        self.port_type
223    }
224
225    /// Get the port direction
226    pub const fn direction(&self) -> PortDirection {
227        self.direction
228    }
229
230    /// Get the port index
231    pub const fn index(&self) -> u16 {
232        self.index
233    }
234
235    // ========================================================================
236    // Predicates
237    // ========================================================================
238
239    /// Check if this is an input port
240    pub const fn is_input(&self) -> bool {
241        self.direction.is_input()
242    }
243
244    /// Check if this is an output port
245    pub const fn is_output(&self) -> bool {
246        self.direction.is_output()
247    }
248
249    /// Check if this is a signal port
250    pub const fn is_signal(&self) -> bool {
251        matches!(self.port_type, PortType::Signal)
252    }
253
254    /// Check if this is a control port
255    pub const fn is_control(&self) -> bool {
256        matches!(self.port_type, PortType::Control)
257    }
258
259    /// Check if this is a clock port
260    pub const fn is_clock(&self) -> bool {
261        matches!(self.port_type, PortType::Clock)
262    }
263
264    /// Check if this is a feedback port
265    pub const fn is_feedback(&self) -> bool {
266        matches!(self.port_type, PortType::Feedback)
267    }
268
269    /// Check if this is a parameter port
270    pub const fn is_param(&self) -> bool {
271        matches!(self.port_type, PortType::Param)
272    }
273}
274
275impl fmt::Display for PortId {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(
278            f,
279            "Node({}).{}_{}[{}]",
280            self.node.inner(),
281            self.port_type.name(),
282            self.direction.name(),
283            self.index
284        )
285    }
286}
287
288// ============================================================================
289// Port Structure
290// ============================================================================
291
292/// A port on a node.
293///
294/// Each port has an owned `FixedBuffer<T, BUF_SIZE>` for its data and an optional
295/// `Action` that defines per-port processing. Output ports typically have
296/// an action; input ports may have one for preprocessing.
297///
298/// Ports can optionally participate in feedback edges:
299/// - On an output port in a feedback edge, `feedback_buffer` stores the
300///   previous block's output, snapshotted after DSP via `snapshot_feedback()`.
301/// - On an input port in a feedback edge, `feedback_buffer` holds the delayed
302///   feedback value that gets mixed into `buffer` by `pre_process()`.
303/// - `downstream` lists signal connections from this output port to input ports
304///   of other nodes, populated at build time by the graph builder.
305/// - `upstream_buffer` on input ports: direct pointer to the upstream output
306///   port's buffer for zero-copy routing. `None` for fan-in/feedback ports.
307///
308/// # Safety
309/// `upstream_buffer` is safe because the graph topology is immutable and
310/// processing is strictly single-threaded in topological order. The
311/// upstream output buffer is guaranteed to outlive the downstream input
312/// port that references it.
313pub struct Port<T: Transcendental, const BUF_SIZE: usize> {
314    /// Port identifier
315    pub id: PortId,
316    /// Port name
317    pub name: String,
318    /// Port direction (input/output)
319    pub direction: PortDirection,
320    /// Per-port processing algorithm (None for simple input ports)
321    pub action: Option<Box<dyn Algorithm<T>>>,
322    /// Pending command value from the control path
323    pub pending_command: Option<T>,
324    /// Owned signal buffer (for output ports and input ports without upstream)
325    pub buffer: FixedBuffer<T, BUF_SIZE>,
326    /// Delayed feedback state (None if not on a feedback edge)
327    pub feedback_buffer: Option<FixedBuffer<T, BUF_SIZE>>,
328    /// Downstream signal connections: (target_node_index, target_port_index).
329    /// Used for serialization and by `GraphBuilder::build()`.
330    pub downstream: Vec<(usize, usize)>,
331    /// Direct pointers to downstream input ports. Filled by
332    /// `GraphBuilder::build()`. Used by `propagate` to copy data.
333    pub downstream_input_ptrs: Vec<*mut Port<T, BUF_SIZE>>,
334    /// Unique downstream nodes (one per target, deduplicated at build time).
335    /// Filled by `GraphBuilder::build()`. Used by `propagate` to recurse
336    /// into downstream nodes — no runtime deduplication needed.
337    pub downstream_nodes: Vec<*mut crate::traits::NodeVariant<T, BUF_SIZE>>,
338    /// Pointer to the [`NodeVariant`](crate::traits::NodeVariant) that
339    /// owns this port. Set after graph construction. Enables recursive
340    /// signal propagation without a `nodes` slice.
341    pub parent: *mut crate::traits::NodeVariant<T, BUF_SIZE>,
342    /// Direct pointer to upstream output buffer for zero-copy routing.
343    /// `Some` for input ports in 1:1 or fan-out connections (first upstream).
344    /// `None` for output ports, fan-in (second+ upstream), or unconnected.
345    /// Valid for the engine's lifetime.
346    pub upstream_buffer: Option<*const FixedBuffer<T, BUF_SIZE>>,
347    /// Feedback edge targets from this output port (for serialization)
348    pub feedback_downstream: Vec<(usize, usize)>,
349
350    /// Direct pointers to `feedback_buffer` on downstream input ports.
351    ///
352    /// Set by `GraphBuilder::build()` for feedback edges.
353    /// `snapshot_feedback()` copies its buffer into each target.
354    pub feedback_ptrs: Vec<*mut Option<FixedBuffer<T, BUF_SIZE>>>,
355
356    /// Whether this input port has received new data in the current graph cycle.
357    ///
358    /// Set by `propagate` when a downstream input port receives a buffer copy.
359    /// Consumer nodes (esp. Sinks) check this flag to decide whether all
360    /// input channels are fresh before producing output.
361    pub data_received: bool,
362}
363
364impl<T: Transcendental, const BUF_SIZE: usize> fmt::Debug for Port<T, BUF_SIZE> {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        f.debug_struct("Port")
367            .field("id", &self.id)
368            .field("name", &self.name)
369            .field("direction", &self.direction)
370            .field("has_action", &self.action.is_some())
371            .field("has_feedback", &self.feedback_buffer.is_some())
372            .field("downstream_len", &self.downstream.len())
373            .finish()
374    }
375}
376
377impl<T: Transcendental, const BUF_SIZE: usize> Port<T, BUF_SIZE> {
378    /// Create a new signal output port
379    pub fn output(node_id: NodeId, index: u16, name: &str) -> Self {
380        Self {
381            id: PortId::signal_out(node_id, index),
382            name: name.to_string(),
383            direction: PortDirection::Output,
384            action: None,
385            pending_command: None,
386            buffer: FixedBuffer::new(),
387            feedback_buffer: None,
388            downstream: Vec::new(),
389            feedback_downstream: Vec::new(),
390            feedback_ptrs: Vec::new(),
391            downstream_input_ptrs: Vec::new(),
392            downstream_nodes: Vec::new(),
393            parent: std::ptr::null_mut(),
394            upstream_buffer: None,
395            data_received: false,
396        }
397    }
398
399    /// Create a new signal input port
400    pub fn input(node_id: NodeId, index: u16, name: &str) -> Self {
401        Self {
402            id: PortId::signal_in(node_id, index),
403            name: name.to_string(),
404            direction: PortDirection::Input,
405            action: None,
406            pending_command: None,
407            buffer: FixedBuffer::new(),
408            feedback_buffer: None,
409            downstream: Vec::new(),
410            feedback_downstream: Vec::new(),
411            feedback_ptrs: Vec::new(),
412            downstream_input_ptrs: Vec::new(),
413            downstream_nodes: Vec::new(),
414            parent: std::ptr::null_mut(),
415            upstream_buffer: None,
416            data_received: false,
417        }
418    }
419
420    /// Create a new control output port
421    pub fn control_output(node_id: NodeId, index: u16, name: &str) -> Self {
422        Self {
423            id: PortId::control_out(node_id, index),
424            name: name.to_string(),
425            direction: PortDirection::Output,
426            action: None,
427            pending_command: None,
428            buffer: FixedBuffer::new(),
429            feedback_buffer: None,
430            downstream: Vec::new(),
431            feedback_downstream: Vec::new(),
432            feedback_ptrs: Vec::new(),
433            downstream_input_ptrs: Vec::new(),
434            downstream_nodes: Vec::new(),
435            parent: std::ptr::null_mut(),
436            upstream_buffer: None,
437            data_received: false,
438        }
439    }
440
441    /// Create a new control output port with an algorithm
442    pub fn control_output_with_action(
443        node_id: NodeId,
444        index: u16,
445        name: &str,
446        action: Box<dyn Algorithm<T>>,
447    ) -> Self {
448        Self {
449            id: PortId::control_out(node_id, index),
450            name: name.to_string(),
451            direction: PortDirection::Output,
452            action: Some(action),
453            pending_command: None,
454            buffer: FixedBuffer::new(),
455            feedback_buffer: None,
456            downstream: Vec::new(),
457            feedback_downstream: Vec::new(),
458            feedback_ptrs: Vec::new(),
459            downstream_input_ptrs: Vec::new(),
460            downstream_nodes: Vec::new(),
461            parent: std::ptr::null_mut(),
462            upstream_buffer: None,
463            data_received: false,
464        }
465    }
466
467    /// Create a new control input port
468    pub fn control_input(node_id: NodeId, index: u16, name: &str) -> Self {
469        Self {
470            id: PortId::control_in(node_id, index),
471            name: name.to_string(),
472            direction: PortDirection::Input,
473            action: None,
474            pending_command: None,
475            buffer: FixedBuffer::new(),
476            feedback_buffer: None,
477            downstream: Vec::new(),
478            feedback_downstream: Vec::new(),
479            feedback_ptrs: Vec::new(),
480            downstream_input_ptrs: Vec::new(),
481            downstream_nodes: Vec::new(),
482            parent: std::ptr::null_mut(),
483            upstream_buffer: None,
484            data_received: false,
485        }
486    }
487
488    /// Get the port ID
489    pub fn id(&self) -> PortId {
490        self.id
491    }
492
493    /// Get the port name
494    pub fn name(&self) -> &str {
495        &self.name
496    }
497
498    /// Check if port is an input
499    pub fn is_input(&self) -> bool {
500        self.direction.is_input()
501    }
502
503    /// Check if port is an output
504    pub fn is_output(&self) -> bool {
505        self.direction.is_output()
506    }
507
508    /// Get a reference to the buffer
509    pub fn buffer(&self) -> &FixedBuffer<T, BUF_SIZE> {
510        &self.buffer
511    }
512
513    /// Get a mutable reference to the buffer
514    pub fn buffer_mut(&mut self) -> &mut FixedBuffer<T, BUF_SIZE> {
515        &mut self.buffer
516    }
517
518    /// Get the effective signal buffer for this port.
519    ///
520    /// For zero-copy input ports returns the upstream output buffer.
521    /// For output ports and copy-based input ports returns the local buffer.
522    #[allow(unsafe_code)]
523    pub fn signal_buffer(&self) -> &FixedBuffer<T, BUF_SIZE> {
524        match self.upstream_buffer {
525            Some(ptr) => unsafe { &*ptr },
526            None => &self.buffer,
527        }
528    }
529
530    /// Pre-process this port before node DSP.
531    ///
532    /// For input ports on a feedback edge, mixes the delayed feedback
533    /// (from `feedback_buffer`) into the current `buffer`.
534    pub fn pre_process(&mut self, _tick: &ClockTick) {
535        if let Some(ref fb) = self.feedback_buffer {
536            let arr = self.buffer.as_mut_array();
537            let fb_arr = fb.as_array();
538            let chunks = BUF_SIZE / 4;
539
540            for chunk in 0..chunks {
541                let o = chunk * 4;
542                let a = ScalarVector4::load(&arr[o..o + 4]);
543                let b = ScalarVector4::load(&fb_arr[o..o + 4]);
544                a.add(&b).store(&mut arr[o..o + 4]);
545            }
546
547            for i in chunks * 4..BUF_SIZE {
548                arr[i] += fb_arr[i];
549            }
550        }
551    }
552
553    /// Snapshot the buffer into `feedback_buffer` and propagate to
554    /// downstream input ports via `feedback_ptrs`.
555    ///
556    /// For output ports on a feedback edge, saves the current buffer
557    /// so it can be used as delayed feedback in the next block, then
558    /// copies it into each target input port's `feedback_buffer`.
559    /// No-op when `feedback_buffer` is `None`.
560    #[allow(unsafe_code)]
561    pub fn snapshot_feedback(&mut self) {
562        if let Some(ref mut fb) = self.feedback_buffer {
563            fb.copy_from(self.buffer.as_array());
564            for &ptr in &self.feedback_ptrs {
565                unsafe {
566                    if let Some(ref mut target) = *ptr {
567                        target.copy_from(fb.as_array());
568                    }
569                }
570            }
571        }
572    }
573
574    /// Propagate this port's buffer to all downstream input ports.
575    ///
576    /// Iterates over `downstream` and copies `buffer` into each target
577    /// input port's buffer. The caller must ensure no aliasing between
578    /// this port's node and any target node (guaranteed by DAG topology).
579    ///
580    /// `tick` is the current clock tick, available for future
581    /// sample-accurate or time-varying port-level propagation.
582    /// Copy `buffer` into every downstream input port (unless zero-copy),
583    /// run each port's algorithm, then process the downstream node and
584    /// recurse through its output ports.
585    ///
586    /// No heap allocations — `downstream_nodes` is pre‑filled at build time.
587    #[allow(unsafe_code)]
588    pub fn propagate(
589        &self,
590        buffer: &FixedBuffer<T, BUF_SIZE>,
591        ctx: &crate::traits::algorithm::ActionContext,
592    ) -> ProcessResult<()> {
593        for &ptr in &self.downstream_input_ptrs {
594            unsafe {
595                if (*ptr).upstream_buffer.is_none() {
596                    (*ptr).buffer.copy_from(buffer.as_array());
597                }
598                (*ptr).run_action(Some(buffer.as_array()), ctx)?;
599                (*ptr).data_received = true;
600            }
601        }
602        let mut proc_ctx = crate::traits::processable::ProcessContext { clock: ctx.tick };
603        for &parent in &self.downstream_nodes {
604            unsafe {
605                let nv = &mut *parent;
606                // Pre-process input ports (feedback mix from previous block)
607                for pi in 0..nv.num_signal_inputs() {
608                    if let Some(p) = nv.input_port_mut(pi) {
609                        p.pre_process(ctx.tick);
610                    }
611                }
612                nv.process_block(&mut proc_ctx)?;
613                for po in 0..nv.num_signal_outputs() {
614                    if let Some(p) = nv.output_port_mut(po) {
615                        p.snapshot_feedback();
616                    }
617                }
618                for po in 0..nv.num_signal_outputs() {
619                    if let Some(p) = nv.output_port(po) {
620                        p.propagate(p.buffer(), ctx)?;
621                    }
622                }
623            }
624        }
625        Ok(())
626    }
627
628    /// Run the port's algorithm.
629    ///
630    /// Delivers any pending command via `Algorithm::apply_command()`, then
631    /// calls `Algorithm::process()` with the input and output slices.
632    /// When no algorithm is attached, the pending command value (if any)
633    /// is written directly into the buffer; otherwise input is passed
634    /// through or zero-filled.
635    pub fn run_action(
636        &mut self,
637        input: Option<&[T; BUF_SIZE]>,
638        ctx: &crate::traits::algorithm::ActionContext,
639    ) -> crate::traits::ProcessResult<()> {
640        match &mut self.action {
641            Some(action) => {
642                // Deliver any pending command to the algorithm
643                if let Some(cmd) = self.pending_command.take() {
644                    action.apply_command(cmd);
645                }
646                let input_slice = input.map(|arr| arr.as_slice());
647                action.process(input_slice, self.buffer.as_mut_slice(), ctx)
648            }
649            None => {
650                // No algorithm — use pending command value if set,
651                // otherwise pass through input or zero-fill.
652                if let Some(cmd) = self.pending_command.take() {
653                    self.buffer.fill(cmd);
654                } else if let Some(input_data) = input {
655                    self.buffer.copy_from(input_data);
656                } else {
657                    self.buffer.fill(T::ZERO);
658                }
659                Ok(())
660            }
661        }
662    }
663
664    /// Set a command value for this port.
665    ///
666    /// The value is stored as a pending command and delivered to the
667    /// algorithm (or written directly to the buffer) on the next
668    /// `run_action()` call.
669    pub fn set_value(&mut self, value: T) {
670        self.pending_command = Some(value);
671    }
672}
673
674// ============================================================================
675// Active Port Trait
676// ============================================================================
677
678/// Trait for ports that can actively pull/push data.
679pub trait ActivePort<T: Transcendental, const BUF_SIZE: usize> {
680    /// Pull data from the port (for input ports).
681    fn pull(&mut self) -> Option<[T; BUF_SIZE]>;
682
683    /// Push data into the port (for output ports).
684    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError>;
685
686    /// Check if the port is connected.
687    fn is_connected(&self) -> bool;
688
689    /// Called on each clock tick (optional).
690    fn on_tick(&mut self, _tick: &ClockTick) {}
691}
692
693impl<T: Transcendental, const BUF_SIZE: usize> ActivePort<T, BUF_SIZE> for Port<T, BUF_SIZE> {
694    #[inline]
695    fn pull(&mut self) -> Option<[T; BUF_SIZE]> {
696        if self.is_input() {
697            Some(*self.buffer.as_array())
698        } else {
699            None
700        }
701    }
702
703    #[inline]
704    fn push(&mut self, data: [T; BUF_SIZE]) -> Result<(), PortError> {
705        if self.is_output() {
706            self.buffer = FixedBuffer::from_array(data);
707            Ok(())
708        } else {
709            Err(PortError::NotFound(self.id.to_string()))
710        }
711    }
712
713    #[inline]
714    fn is_connected(&self) -> bool {
715        self.action.is_some()
716    }
717
718    #[inline]
719    fn on_tick(&mut self, _tick: &ClockTick) {}
720}
721
722// SAFETY: `upstream_buffer` is a raw pointer to a buffer owned by another
723// Port in the same static graph. The graph is immutable during processing
724// and runs single-threaded in topological order. The pointer target
725// outlives the pointer for the entire processing session.
726#[allow(unsafe_code)]
727unsafe impl<T: Transcendental + Send, const BUF_SIZE: usize> Send for Port<T, BUF_SIZE> {}
728#[allow(unsafe_code)]
729unsafe impl<T: Transcendental + Sync, const BUF_SIZE: usize> Sync for Port<T, BUF_SIZE> {}
730
731// ============================================================================
732// Tests
733// ============================================================================
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn test_port_id_creation() {
741        let node = NodeId(42);
742
743        let signal_in = PortId::signal_in(node, 0);
744        assert_eq!(signal_in.port_type(), PortType::Signal);
745        assert!(signal_in.is_input());
746
747        let clock_out = PortId::clock_out(node, 0);
748        assert_eq!(clock_out.port_type(), PortType::Clock);
749        assert!(clock_out.is_output());
750
751        let feedback_in = PortId::feedback_in(node, 0);
752        assert_eq!(feedback_in.port_type(), PortType::Feedback);
753        assert!(feedback_in.is_input());
754    }
755}