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