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}