Skip to main content

polydat_core/kernel/subcontext/
pull.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! [`PullConsumer`] — wrapper / dispenser registration shape.
5//!
6//! ## Cross-crate boundary
7//!
8//! The runtime fixture / pull-plan machinery (SRD-32) lives in
9//! `nbrs-runtime`. This crate (`polydat`) cannot depend on
10//! `nbrs-runtime` — the dependency runs the other way. The
11//! [`PullConsumer`] trait below carries only the *intent*: a list
12//! of names the consumer wants to pull at cycle time. The
13//! activity-side `ScopeFixture::register_consumer` adapter walks
14//! these names and seals them into a `PullPlan` against the
15//! spawned kernel's program. SRD-32's pull-plan format and the
16//! per-consumer contract stay as 32 specifies; only the
17//! accumulator surface unifies under
18//! [`crate::kernel::subcontext::SubcontextBuilder::register_pull`].
19//!
20//! The host reads the registrations back through
21//! [`crate::kernel::subcontext::ScopeKernel::consumers`] on the
22//! spawned kernel (design doc §2.4).
23
24use std::sync::Arc;
25
26/// Anything that wants to pull Polydat values at cycle time.
27///
28/// Implementors expose the names they intend to read; the spawn
29/// path makes those names available on the spawned kernel for
30/// the activity-side fixture to seal into a `PullPlan`.
31///
32/// This trait is `Send + Sync` because consumers are stored on
33/// the artifact and may flow across threads with the spawned
34/// kernel.
35pub trait PullConsumer: Send + Sync {
36    /// The Polydat names this consumer will pull at cycle time.
37    /// Returned in registration order so handle indices match the
38    /// caller's expected layout.
39    fn names(&self) -> &[String];
40
41    /// Diagnostic label — appears in error messages when a
42    /// registered name fails to resolve against the kernel
43    /// program at seal time. Default: `"<unnamed-consumer>"`.
44    fn label(&self) -> &str {
45        "<unnamed-consumer>"
46    }
47}
48
49/// A consumer registration that has been recorded in a
50/// [`crate::kernel::subcontext::SubcontextBuilder`].
51///
52/// Wraps an `Arc<dyn PullConsumer>` so consumers can be cheaply
53/// shared between the artifact and the spawned kernel without
54/// trait-object cloning. The host's fixture adapter owns the seal
55/// step; this type only records the consumer for later
56/// inspection.
57#[derive(Clone)]
58pub struct RegisteredPullConsumer {
59    inner: Arc<dyn PullConsumer>,
60}
61
62impl RegisteredPullConsumer {
63    /// A registered consumer wrapping `consumer`.
64    pub fn new(consumer: Arc<dyn PullConsumer>) -> Self {
65        Self { inner: consumer }
66    }
67
68    /// Inspect the names this consumer requested. Used by the
69    /// activity-side adapter at seal time and by Phase 1 tests
70    /// to verify the registration round-trip.
71    pub fn names(&self) -> &[String] {
72        self.inner.names()
73    }
74
75    /// The consumer's diagnostic label.
76    pub fn label(&self) -> &str {
77        self.inner.label()
78    }
79
80    /// Borrow the underlying trait object — the activity-side
81    /// adapter uses this to dispatch to consumer-specific
82    /// configuration once it has the kernel program in hand.
83    pub fn as_dyn(&self) -> &dyn PullConsumer {
84        self.inner.as_ref()
85    }
86}
87
88impl std::fmt::Debug for RegisteredPullConsumer {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.debug_struct("RegisteredPullConsumer")
91            .field("label", &self.label())
92            .field("names", &self.names())
93            .finish()
94    }
95}
96
97/// Test-only / scratch consumer: holds a fixed list of names.
98/// Production consumers (validation / conditional / throttle /
99/// …) implement [`PullConsumer`] themselves on the activity
100/// side. This shape exists so Phase 1 tests can exercise the
101/// builder + spawn path without importing the activity crate.
102#[derive(Debug)]
103pub struct NamedPullConsumer {
104    label: String,
105    names: Vec<String>,
106}
107
108impl NamedPullConsumer {
109    /// A consumer with a label and the output names it pulls.
110    pub fn new(label: impl Into<String>, names: impl IntoIterator<Item = String>) -> Self {
111        Self {
112            label: label.into(),
113            names: names.into_iter().collect(),
114        }
115    }
116}
117
118impl PullConsumer for NamedPullConsumer {
119    fn names(&self) -> &[String] {
120        &self.names
121    }
122
123    fn label(&self) -> &str {
124        &self.label
125    }
126}