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::RenderContext;
13use crate::traits::algorithm::Algorithm;
14use crate::traits::node::NodeId;
15use crate::traits::processable::Processable;
16use crate::traits::{Node, ProcessResult};
17use std::fmt;
18
19// ============================================================================
20// Port Type
21// ============================================================================
22
23/// Type of a port - what kind of signal it carries
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum PortType {
26 /// Signal port - carries signal blocks (signal data, sensor data, etc.)
27 Signal,
28
29 /// Control signal port - carries modulation/automation
30 Control,
31
32 /// Clock signal port - carries timing information
33 Clock,
34
35 /// Feedback port - stores state between blocks
36 Feedback,
37
38 /// Parameter port - for node parameters (special)
39 Param,
40}
41
42impl PortType {
43 /// Get the name of the port type
44 pub const fn name(&self) -> &'static str {
45 match self {
46 Self::Signal => "signal",
47 Self::Control => "control",
48 Self::Clock => "clock",
49 Self::Feedback => "feedback",
50 Self::Param => "param",
51 }
52 }
53
54 /// Check if this port carries signal-rate signals
55 pub const fn is_signal_rate(&self) -> bool {
56 matches!(self, Self::Signal)
57 }
58
59 /// Check if this port carries control-rate signals
60 pub const fn is_control_rate(&self) -> bool {
61 matches!(self, Self::Control)
62 }
63
64 /// Check if this port carries clock signals
65 pub const fn is_clock(&self) -> bool {
66 matches!(self, Self::Clock)
67 }
68}
69
70impl fmt::Display for PortType {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.name())
73 }
74}
75
76// ============================================================================
77// Port Direction
78// ============================================================================
79
80/// Direction of a port (input or output)
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub enum PortDirection {
83 /// Input port (receives data into the node)
84 Input,
85
86 /// Output port (sends data out of the node)
87 Output,
88}
89
90impl PortDirection {
91 /// Get the name of the direction
92 pub const fn name(&self) -> &'static str {
93 match self {
94 Self::Input => "input",
95 Self::Output => "output",
96 }
97 }
98
99 /// Check if this is an input port
100 pub const fn is_input(&self) -> bool {
101 matches!(self, Self::Input)
102 }
103
104 /// Check if this is an output port
105 pub const fn is_output(&self) -> bool {
106 matches!(self, Self::Output)
107 }
108}
109
110impl fmt::Display for PortDirection {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "{}", self.name())
113 }
114}
115
116// ============================================================================
117// Port ID
118// ============================================================================
119
120/// Unique identifier for a port within a graph
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct PortId {
123 node: NodeId,
124 port_type: PortType,
125 direction: PortDirection,
126 index: u16,
127}
128
129impl PortId {
130 /// Create a new port ID
131 pub const fn new(
132 node: NodeId,
133 port_type: PortType,
134 direction: PortDirection,
135 index: u16,
136 ) -> Self {
137 Self {
138 node,
139 port_type,
140 direction,
141 index,
142 }
143 }
144
145 // ========================================================================
146 // Signal Port Constructors
147 // ========================================================================
148
149 /// Create a new signal input port
150 pub const fn signal_in(node: NodeId, index: u16) -> Self {
151 Self::new(node, PortType::Signal, PortDirection::Input, index)
152 }
153
154 /// Create a new signal output port
155 pub const fn signal_out(node: NodeId, index: u16) -> Self {
156 Self::new(node, PortType::Signal, PortDirection::Output, index)
157 }
158
159 // ========================================================================
160 // Control Port Constructors
161 // ========================================================================
162
163 /// Create a new control input port
164 pub const fn control_in(node: NodeId, index: u16) -> Self {
165 Self::new(node, PortType::Control, PortDirection::Input, index)
166 }
167
168 /// Create a new control output port
169 pub const fn control_out(node: NodeId, index: u16) -> Self {
170 Self::new(node, PortType::Control, PortDirection::Output, index)
171 }
172
173 // ========================================================================
174 // Clock Port Constructors
175 // ========================================================================
176
177 /// Create a new clock input port
178 pub const fn clock_in(node: NodeId, index: u16) -> Self {
179 Self::new(node, PortType::Clock, PortDirection::Input, index)
180 }
181
182 /// Create a new clock output port
183 pub const fn clock_out(node: NodeId, index: u16) -> Self {
184 Self::new(node, PortType::Clock, PortDirection::Output, index)
185 }
186
187 // ========================================================================
188 // Feedback Port Constructors
189 // ========================================================================
190
191 /// Create a new feedback input port
192 pub const fn feedback_in(node: NodeId, index: u16) -> Self {
193 Self::new(node, PortType::Feedback, PortDirection::Input, index)
194 }
195
196 /// Create a new feedback output port
197 pub const fn feedback_out(node: NodeId, index: u16) -> Self {
198 Self::new(node, PortType::Feedback, PortDirection::Output, index)
199 }
200
201 // ========================================================================
202 // Parameter Port Constructors
203 // ========================================================================
204
205 /// Create a new parameter port (always input)
206 pub const fn param(node: NodeId, index: u16) -> Self {
207 Self::new(node, PortType::Param, PortDirection::Input, index)
208 }
209
210 // ========================================================================
211 // Getters
212 // ========================================================================
213
214 /// Get the node ID
215 pub const fn node_id(&self) -> NodeId {
216 self.node
217 }
218
219 /// Get the port type
220 pub const fn port_type(&self) -> PortType {
221 self.port_type
222 }
223
224 /// Get the port direction
225 pub const fn direction(&self) -> PortDirection {
226 self.direction
227 }
228
229 /// Get the port index
230 pub const fn index(&self) -> u16 {
231 self.index
232 }
233
234 // ========================================================================
235 // Predicates
236 // ========================================================================
237
238 /// Check if this is an input port
239 pub const fn is_input(&self) -> bool {
240 self.direction.is_input()
241 }
242
243 /// Check if this is an output port
244 pub const fn is_output(&self) -> bool {
245 self.direction.is_output()
246 }
247
248 /// Check if this is a signal port
249 pub const fn is_signal(&self) -> bool {
250 matches!(self.port_type, PortType::Signal)
251 }
252
253 /// Check if this is a control port
254 pub const fn is_control(&self) -> bool {
255 matches!(self.port_type, PortType::Control)
256 }
257
258 /// Check if this is a clock port
259 pub const fn is_clock(&self) -> bool {
260 matches!(self.port_type, PortType::Clock)
261 }
262
263 /// Check if this is a feedback port
264 pub const fn is_feedback(&self) -> bool {
265 matches!(self.port_type, PortType::Feedback)
266 }
267
268 /// Check if this is a parameter port
269 pub const fn is_param(&self) -> bool {
270 matches!(self.port_type, PortType::Param)
271 }
272}
273
274impl fmt::Display for PortId {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(
277 f,
278 "Node({}).{}_{}[{}]",
279 self.node.inner(),
280 self.port_type.name(),
281 self.direction.name(),
282 self.index
283 )
284 }
285}
286
287// ============================================================================
288// Port Structure
289// ============================================================================
290
291/// A port on a node.
292///
293/// Each port has an owned `FixedBuffer<T, BUF_SIZE>` for its data and an optional
294/// `Action` that defines per-port processing. Output ports typically have
295/// an action; input ports may have one for preprocessing.
296///
297/// Ports can optionally participate in feedback edges:
298/// - On an output port in a feedback edge, `feedback_buffer` stores the
299/// previous block's output, snapshotted after DSP via `snapshot_feedback()`.
300/// - On an input port in a feedback edge, `feedback_buffer` holds the delayed
301/// feedback value that gets mixed into `buffer` by `pre_process()`.
302/// - `downstream` lists signal connections from this output port to input ports
303/// of other nodes, populated at build time by the graph builder.
304/// - `upstream_buffer` on input ports: direct pointer to the upstream output
305/// port's buffer for zero-copy routing on **exclusive 1:1** edges only.
306/// `None` for fan-in, fan-out, and feedback ports (each materializes an
307/// independent copy).
308///
309/// # Safety
310/// `upstream_buffer` is safe because the graph topology is immutable and
311/// processing is strictly single-threaded in topological order. The
312/// upstream output buffer is guaranteed to outlive the downstream input
313/// port that references it.
314pub struct Port<T: Transcendental, const BUF_SIZE: usize> {
315 /// Port identifier
316 pub id: PortId,
317 /// Port name
318 pub name: String,
319 /// Port direction (input/output)
320 pub direction: PortDirection,
321 /// Per-port processing algorithm (None for simple input ports)
322 action: Option<Box<dyn Algorithm<T>>>,
323 /// Pending command value from the control path
324 pending_command: Option<T>,
325 /// Owned signal buffer (for output ports and input ports without upstream).
326 ///
327 /// Private: nodes access signal data through [`read`](Self::read) /
328 /// [`write`](Self::write) / [`write_from`](Self::write_from) /
329 /// [`feedback`](Self::feedback), which encapsulate the zero-copy routing.
330 /// The engine uses [`buffer`](Self::buffer) / `buffer_mut`.
331 buffer: FixedBuffer<T, BUF_SIZE>,
332 /// Delayed feedback state (None if not on a feedback edge)
333 feedback_buffer: Option<FixedBuffer<T, BUF_SIZE>>,
334 /// Downstream signal connections: (target_node_index, target_port_index).
335 /// Used for serialization and by `GraphBuilder::build()`.
336 downstream: Vec<(usize, usize)>,
337 /// Direct pointers to downstream input ports. Filled by
338 /// `GraphBuilder::build()`. Used by `propagate` to copy data.
339 downstream_input_ptrs: Vec<*mut Port<T, BUF_SIZE>>,
340 /// Unique downstream nodes (one per target, deduplicated at build time).
341 /// Filled by `GraphBuilder::build()`. Used by `propagate` to recurse
342 /// into downstream nodes — no runtime deduplication needed.
343 downstream_nodes: Vec<*mut crate::traits::NodeVariant<T, BUF_SIZE>>,
344 /// Direct pointer to upstream output buffer for zero-copy routing.
345 /// `Some` only for an **exclusive 1:1** input port (its upstream output
346 /// has a single consumer and this input has a single producer).
347 /// `None` for output ports, fan-in, fan-out branches, or unconnected —
348 /// those materialize an independent copy.
349 /// Valid for the engine's lifetime.
350 upstream_buffer: Option<*const FixedBuffer<T, BUF_SIZE>>,
351 /// Feedback edge targets from this output port (for serialization)
352 feedback_downstream: Vec<(usize, usize)>,
353
354 /// Direct pointers to `feedback_buffer` on downstream input ports.
355 ///
356 /// Set by `GraphBuilder::build()` for feedback edges.
357 /// `snapshot_feedback()` copies its buffer into each target.
358 feedback_ptrs: Vec<*mut Option<FixedBuffer<T, BUF_SIZE>>>,
359
360 /// Whether this input port has received new data in the current graph cycle.
361 ///
362 /// Set by `propagate` when a downstream input port receives a buffer copy.
363 /// Consumer nodes (esp. Sinks) check this flag to decide whether all
364 /// input channels are fresh before producing output.
365 data_received: bool,
366
367 /// Pull-model: pointer to the upstream node that feeds this input port.
368 /// Only set for same-chain edges (recording→recording or playback→playback).
369 /// Used by the pull traversal in `process_playback_chain`.
370 upstream_node: *mut crate::traits::NodeVariant<T, BUF_SIZE>,
371}
372
373impl<T: Transcendental, const BUF_SIZE: usize> fmt::Debug for Port<T, BUF_SIZE> {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 f.debug_struct("Port")
376 .field("id", &self.id)
377 .field("name", &self.name)
378 .field("direction", &self.direction)
379 .field("has_action", &self.action.is_some())
380 .field("has_feedback", &self.feedback_buffer.is_some())
381 .field("downstream_len", &self.downstream.len())
382 .finish()
383 }
384}
385
386impl<T: Transcendental, const BUF_SIZE: usize> Port<T, BUF_SIZE> {
387 /// Create a new signal output port
388 pub fn output(node_id: NodeId, index: u16, name: &str) -> Self {
389 Self {
390 id: PortId::signal_out(node_id, index),
391 name: name.to_string(),
392 direction: PortDirection::Output,
393 action: None,
394 pending_command: None,
395 buffer: FixedBuffer::new(),
396 feedback_buffer: None,
397 downstream: Vec::new(),
398 feedback_downstream: Vec::new(),
399 feedback_ptrs: Vec::new(),
400 downstream_input_ptrs: Vec::new(),
401 downstream_nodes: Vec::new(),
402 upstream_buffer: None,
403 upstream_node: std::ptr::null_mut(),
404 data_received: false,
405 }
406 }
407
408 /// Create a new signal input port
409 pub fn input(node_id: NodeId, index: u16, name: &str) -> Self {
410 Self {
411 id: PortId::signal_in(node_id, index),
412 name: name.to_string(),
413 direction: PortDirection::Input,
414 action: None,
415 pending_command: None,
416 buffer: FixedBuffer::new(),
417 feedback_buffer: None,
418 downstream: Vec::new(),
419 feedback_downstream: Vec::new(),
420 feedback_ptrs: Vec::new(),
421 downstream_input_ptrs: Vec::new(),
422 downstream_nodes: Vec::new(),
423 upstream_buffer: None,
424 upstream_node: std::ptr::null_mut(),
425 data_received: false,
426 }
427 }
428
429 /// Create a new control output port
430 pub fn control_output(node_id: NodeId, index: u16, name: &str) -> Self {
431 Self {
432 id: PortId::control_out(node_id, index),
433 name: name.to_string(),
434 direction: PortDirection::Output,
435 action: None,
436 pending_command: None,
437 buffer: FixedBuffer::new(),
438 feedback_buffer: None,
439 downstream: Vec::new(),
440 feedback_downstream: Vec::new(),
441 feedback_ptrs: Vec::new(),
442 downstream_input_ptrs: Vec::new(),
443 downstream_nodes: Vec::new(),
444 upstream_buffer: None,
445 upstream_node: std::ptr::null_mut(),
446 data_received: false,
447 }
448 }
449
450 /// Create a new control output port with an algorithm
451 pub fn control_output_with_action(
452 node_id: NodeId,
453 index: u16,
454 name: &str,
455 action: Box<dyn Algorithm<T>>,
456 ) -> Self {
457 Self {
458 id: PortId::control_out(node_id, index),
459 name: name.to_string(),
460 direction: PortDirection::Output,
461 action: Some(action),
462 pending_command: None,
463 buffer: FixedBuffer::new(),
464 feedback_buffer: None,
465 downstream: Vec::new(),
466 feedback_downstream: Vec::new(),
467 feedback_ptrs: Vec::new(),
468 downstream_input_ptrs: Vec::new(),
469 downstream_nodes: Vec::new(),
470 upstream_buffer: None,
471 upstream_node: std::ptr::null_mut(),
472 data_received: false,
473 }
474 }
475
476 /// Create a new control input port
477 pub fn control_input(node_id: NodeId, index: u16, name: &str) -> Self {
478 Self {
479 id: PortId::control_in(node_id, index),
480 name: name.to_string(),
481 direction: PortDirection::Input,
482 action: None,
483 pending_command: None,
484 buffer: FixedBuffer::new(),
485 feedback_buffer: None,
486 downstream: Vec::new(),
487 feedback_downstream: Vec::new(),
488 feedback_ptrs: Vec::new(),
489 downstream_input_ptrs: Vec::new(),
490 downstream_nodes: Vec::new(),
491 upstream_buffer: None,
492 upstream_node: std::ptr::null_mut(),
493 data_received: false,
494 }
495 }
496
497 /// Get the port ID
498 pub fn id(&self) -> PortId {
499 self.id
500 }
501
502 /// Get the port name
503 pub fn name(&self) -> &str {
504 &self.name
505 }
506
507 /// Check if port is an input
508 pub fn is_input(&self) -> bool {
509 self.direction.is_input()
510 }
511
512 /// Check if port is an output
513 pub fn is_output(&self) -> bool {
514 self.direction.is_output()
515 }
516
517 /// Low-level access to the port's own buffer (engine / I-O boundary).
518 ///
519 /// Nodes should prefer [`read`](Self::read): for a signal **input** this
520 /// returns the port's own buffer, which is *not* the effective input on a
521 /// zero-copy edge.
522 pub fn buffer(&self) -> &FixedBuffer<T, BUF_SIZE> {
523 &self.buffer
524 }
525
526 /// Low-level mutable access to the port's own buffer (crate-internal).
527 ///
528 /// Nodes should prefer [`write`](Self::write) / [`write_from`](Self::write_from).
529 pub(crate) fn buffer_mut(&mut self) -> &mut FixedBuffer<T, BUF_SIZE> {
530 &mut self.buffer
531 }
532
533 /// Read the effective input block for this port (zero-copy aware).
534 ///
535 /// On an exclusive 1:1 edge this is the upstream output buffer; otherwise
536 /// it is the port's own (materialized) buffer. This is the correct way for
537 /// a node to read a signal input.
538 #[inline]
539 pub fn read(&self) -> &[T; BUF_SIZE] {
540 self.signal_buffer().as_array()
541 }
542
543 /// Mutable access to this port's output block. The canonical way for a
544 /// node to write its output samples.
545 #[inline]
546 pub fn write(&mut self) -> &mut [T; BUF_SIZE] {
547 self.buffer_mut().as_mut_array()
548 }
549
550 /// Write this port's output block from a source array.
551 #[inline]
552 pub fn write_from(&mut self, src: &[T; BUF_SIZE]) {
553 self.write().copy_from_slice(src);
554 }
555
556 /// The delayed feedback block, if this port is on a feedback edge.
557 #[inline]
558 pub fn feedback(&self) -> Option<&[T; BUF_SIZE]> {
559 self.feedback_buffer.as_ref().map(|b| b.as_array())
560 }
561
562 // ========================================================================
563 // Sink-facing status
564 // ========================================================================
565
566 /// Whether this input port received fresh data in the current graph cycle.
567 #[inline]
568 pub fn data_received(&self) -> bool {
569 self.data_received
570 }
571
572 /// Set the `data_received` flag (sinks reset it after consuming).
573 #[inline]
574 pub fn set_data_received(&mut self, value: bool) {
575 self.data_received = value;
576 }
577
578 // ========================================================================
579 // Graph construction API (used by `GraphBuilder::build`)
580 //
581 // These wire the immutable topology once at build time. They are not
582 // intended for node authors — signal data flows through `read`/`write`.
583 // ========================================================================
584
585 /// Downstream signal connections `(target_node, target_port)` (serialization).
586 #[inline]
587 pub fn downstream(&self) -> &[(usize, usize)] {
588 &self.downstream
589 }
590
591 /// Feedback edge targets from this output port (serialization).
592 #[inline]
593 pub fn feedback_downstream(&self) -> &[(usize, usize)] {
594 &self.feedback_downstream
595 }
596
597 /// Direct pointers to downstream input ports (engine propagation).
598 #[inline]
599 pub fn downstream_input_ptrs(&self) -> &[*mut Port<T, BUF_SIZE>] {
600 &self.downstream_input_ptrs
601 }
602
603 /// Unique downstream nodes fed by this output port (engine propagation).
604 #[inline]
605 pub fn downstream_nodes(&self) -> &[*mut crate::traits::NodeVariant<T, BUF_SIZE>] {
606 &self.downstream_nodes
607 }
608
609 /// The upstream node feeding this input port (pull model), or null.
610 #[inline]
611 pub fn upstream_node(&self) -> *mut crate::traits::NodeVariant<T, BUF_SIZE> {
612 self.upstream_node
613 }
614
615 /// Whether this input port aliases an upstream buffer (exclusive 1:1 edge).
616 #[inline]
617 pub fn has_upstream_buffer(&self) -> bool {
618 self.upstream_buffer.is_some()
619 }
620
621 /// Record a downstream signal connection `(target_node, target_port)`.
622 #[inline]
623 pub fn add_downstream(&mut self, target_node: usize, target_port: usize) {
624 self.downstream.push((target_node, target_port));
625 }
626
627 /// Record a feedback edge target `(target_node, target_port)`.
628 #[inline]
629 pub fn add_feedback_downstream(&mut self, target_node: usize, target_port: usize) {
630 self.feedback_downstream.push((target_node, target_port));
631 }
632
633 /// Append a direct pointer to a downstream input port.
634 #[inline]
635 pub fn add_downstream_input_ptr(&mut self, ptr: *mut Port<T, BUF_SIZE>) {
636 self.downstream_input_ptrs.push(ptr);
637 }
638
639 /// Append a downstream node pointer, deduplicating on identity.
640 #[inline]
641 pub fn add_downstream_node(&mut self, node: *mut crate::traits::NodeVariant<T, BUF_SIZE>) {
642 let val = node as usize;
643 if !self.downstream_nodes.iter().any(|&p| p as usize == val) {
644 self.downstream_nodes.push(node);
645 }
646 }
647
648 /// Set the pull-model upstream node pointer.
649 #[inline]
650 pub fn set_upstream_node(&mut self, node: *mut crate::traits::NodeVariant<T, BUF_SIZE>) {
651 self.upstream_node = node;
652 }
653
654 /// Set the zero-copy upstream buffer alias (exclusive 1:1 edges only).
655 #[inline]
656 pub fn set_upstream_buffer(&mut self, buffer: Option<*const FixedBuffer<T, BUF_SIZE>>) {
657 self.upstream_buffer = buffer;
658 }
659
660 /// Allocate this port's feedback buffer (feedback edge endpoint).
661 #[inline]
662 pub fn init_feedback_buffer(&mut self) {
663 self.feedback_buffer = Some(FixedBuffer::new());
664 }
665
666 /// Raw pointer to this port's `feedback_buffer`, for wiring `feedback_ptrs`.
667 #[inline]
668 pub fn feedback_buffer_ptr(&self) -> *mut Option<FixedBuffer<T, BUF_SIZE>> {
669 &self.feedback_buffer as *const Option<FixedBuffer<T, BUF_SIZE>>
670 as *mut Option<FixedBuffer<T, BUF_SIZE>>
671 }
672
673 /// Append a pointer to a downstream input port's feedback buffer.
674 #[inline]
675 pub fn add_feedback_ptr(&mut self, ptr: *mut Option<FixedBuffer<T, BUF_SIZE>>) {
676 self.feedback_ptrs.push(ptr);
677 }
678
679 /// Whether this input port is a pure zero-copy passthrough.
680 ///
681 /// True when it aliases a single upstream output buffer
682 /// (`upstream_buffer` is set) and performs no per-port processing —
683 /// no `action`, no `pending_command`, and no feedback mixing. Such a
684 /// port is never materialized into its own `buffer`: the consumer reads
685 /// the upstream buffer directly through [`signal_buffer`](Self::signal_buffer).
686 #[inline]
687 pub fn is_zero_copy(&self) -> bool {
688 self.upstream_buffer.is_some()
689 && self.action.is_none()
690 && self.pending_command.is_none()
691 && self.feedback_buffer.is_none()
692 }
693
694 /// Get the effective signal buffer for this port.
695 ///
696 /// For a zero-copy input port returns the upstream output buffer directly.
697 /// Otherwise returns the local buffer (materialized by `run_action` /
698 /// feedback `pre_process`).
699 #[allow(unsafe_code)]
700 pub fn signal_buffer(&self) -> &FixedBuffer<T, BUF_SIZE> {
701 match self.upstream_buffer {
702 Some(ptr) if self.is_zero_copy() => unsafe { &*ptr },
703 _ => &self.buffer,
704 }
705 }
706
707 /// Pre-process this port before node DSP.
708 ///
709 /// For input ports on a feedback edge, mixes the delayed feedback
710 /// (from `feedback_buffer`) into the current `buffer`.
711 pub fn pre_process(&mut self) {
712 if let Some(ref fb) = self.feedback_buffer {
713 let arr = self.buffer.as_mut_array();
714 let fb_arr = fb.as_array();
715 let chunks = BUF_SIZE / 4;
716
717 for chunk in 0..chunks {
718 let o = chunk * 4;
719 let a = ScalarVector4::load(&arr[o..o + 4]);
720 let b = ScalarVector4::load(&fb_arr[o..o + 4]);
721 a.add(&b).store(&mut arr[o..o + 4]);
722 }
723
724 for i in chunks * 4..BUF_SIZE {
725 arr[i] += fb_arr[i];
726 }
727 }
728 }
729
730 /// Snapshot the buffer into `feedback_buffer` and propagate to
731 /// downstream input ports via `feedback_ptrs`.
732 ///
733 /// For output ports on a feedback edge, saves the current buffer
734 /// so it can be used as delayed feedback in the next block, then
735 /// copies it into each target input port's `feedback_buffer`.
736 /// No-op when `feedback_buffer` is `None`.
737 #[allow(unsafe_code)]
738 pub fn snapshot_feedback(&mut self) {
739 if let Some(ref mut fb) = self.feedback_buffer {
740 fb.copy_from(self.buffer.as_array());
741 for &ptr in &self.feedback_ptrs {
742 unsafe {
743 if let Some(ref mut target) = *ptr {
744 target.copy_from(fb.as_array());
745 }
746 }
747 }
748 }
749 }
750
751 /// Propagate this port's own buffer to all downstream input ports.
752 ///
753 /// For each downstream input port that is **not** a zero-copy passthrough
754 /// (fan-in, feedback, or a port with its own `action`/`pending_command`),
755 /// materialize the data into that port's buffer via `run_action`.
756 /// Zero-copy passthrough ports are left untouched — their consumer reads
757 /// this output buffer directly through [`signal_buffer`](Self::signal_buffer),
758 /// so no copy is performed. Then process each downstream node and recurse
759 /// through its output ports.
760 ///
761 /// No heap allocations — `downstream_nodes` is pre‑filled at build time.
762 #[allow(unsafe_code)]
763 pub fn propagate(
764 &self,
765 ctx: &RenderContext,
766 tick: &crate::time::ClockTick,
767 ) -> ProcessResult<()> {
768 let buffer = self.buffer.as_array();
769 for &ptr in &self.downstream_input_ptrs {
770 unsafe {
771 let p = &mut *ptr;
772 if !p.is_zero_copy() {
773 p.run_action(Some(buffer))?;
774 }
775 p.data_received = true;
776 }
777 }
778 for &parent in &self.downstream_nodes {
779 unsafe {
780 let nv = &mut *parent;
781 for pi in 0..nv.num_signal_inputs() {
782 if let Some(p) = nv.input_port_mut(pi) {
783 p.pre_process();
784 }
785 }
786 nv.process_block(ctx, tick)?;
787 for po in 0..nv.num_signal_outputs() {
788 if let Some(p) = nv.output_port_mut(po) {
789 p.snapshot_feedback();
790 }
791 }
792 for po in 0..nv.num_signal_outputs() {
793 if let Some(p) = nv.output_port(po) {
794 p.propagate(ctx, tick)?;
795 }
796 }
797 }
798 }
799 Ok(())
800 }
801
802 /// Run the port's algorithm.
803 ///
804 /// Delivers any pending command via `Algorithm::apply_command()`, then
805 /// calls `Algorithm::process()` with the input and output slices.
806 /// When no algorithm is attached, the pending command value (if any)
807 /// is written directly into the buffer; otherwise input is passed
808 /// through or zero-filled.
809 pub fn run_action(
810 &mut self,
811 input: Option<&[T; BUF_SIZE]>,
812 ) -> crate::traits::ProcessResult<()> {
813 match &mut self.action {
814 Some(action) => {
815 // Deliver any pending command to the algorithm
816 if let Some(cmd) = self.pending_command.take() {
817 action.apply_command(cmd);
818 }
819 let input_slice = input.map(|arr| arr.as_slice());
820 action.process(input_slice, self.buffer.as_mut_slice())
821 }
822 None => {
823 // No algorithm — use pending command value if set,
824 // otherwise pass through input or zero-fill.
825 if let Some(cmd) = self.pending_command.take() {
826 self.buffer.fill(cmd);
827 } else if let Some(input_data) = input {
828 self.buffer.copy_from(input_data);
829 } else {
830 self.buffer.fill(T::ZERO);
831 }
832 Ok(())
833 }
834 }
835 }
836
837 /// Set a command value for this port.
838 ///
839 /// The value is stored as a pending command and delivered to the
840 /// algorithm (or written directly to the buffer) on the next
841 /// `run_action()` call.
842 pub fn set_value(&mut self, value: T) {
843 self.pending_command = Some(value);
844 }
845}
846
847// ============================================================================
848// Send / Sync
849// ============================================================================
850
851// SAFETY: `upstream_buffer` is a raw pointer to a buffer owned by another
852// Port in the same static graph. The graph is immutable during processing
853// and runs single-threaded in topological order. The pointer target
854// outlives the pointer for the entire processing session.
855#[allow(unsafe_code)]
856unsafe impl<T: Transcendental + Send, const BUF_SIZE: usize> Send for Port<T, BUF_SIZE> {}
857#[allow(unsafe_code)]
858unsafe impl<T: Transcendental + Sync, const BUF_SIZE: usize> Sync for Port<T, BUF_SIZE> {}
859
860// ============================================================================
861// Tests
862// ============================================================================
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867
868 #[test]
869 fn test_port_id_creation() {
870 let node = NodeId(42);
871
872 let signal_in = PortId::signal_in(node, 0);
873 assert_eq!(signal_in.port_type(), PortType::Signal);
874 assert!(signal_in.is_input());
875
876 let clock_out = PortId::clock_out(node, 0);
877 assert_eq!(clock_out.port_type(), PortType::Clock);
878 assert!(clock_out.is_output());
879
880 let feedback_in = PortId::feedback_in(node, 0);
881 assert_eq!(feedback_in.port_type(), PortType::Feedback);
882 assert!(feedback_in.is_input());
883 }
884}