Skip to main content

rustdv_methodology/
factory.rs

1//! The factory (design-doc §15, D69–D75).
2//!
3//! rustdv has a factory, and it works as the UVM factory works (D74). It is
4//! not a separate subsystem: a "maker" is an ordinary Rust value, so the
5//! override table is the [`ConfigDb`](crate::config) and registration uses a
6//! `linkme` distributed slice like the test registry. From the user's chair there are
7//! two constructors — `Foo::new_comp()` (fixed) and `Foo::create_comp()`
8//! (overridable) — and `Factory::…` to install and inspect overrides.
9//!
10//! **How a `create_comp()` slot is overridden.** It is not resolved at the
11//! call — a `create_comp()` builds the default type immediately and flags the
12//! [`RustdvComp`] as factory-owned (D75). During the build walk, where the field
13//! name and so the path are finally known, the framework asks each flagged
14//! slot for its override (instance first, then type, by ConfigDb specificity,
15//! D13) and swaps it in before descending.
16
17use std::cell::RefCell;
18use std::collections::HashMap;
19use std::fmt;
20
21use crate::component::{Component, ComponentNode, RustdvCtx};
22use crate::config::ConfigDb;
23use crate::port::PortOwner;
24use linkme::distributed_slice;
25
26/// A maker: builds a component with no arguments (its name and parent come
27/// from the tree, D7). Non-capturing, so it is an ordinary `fn` pointer.
28pub type Maker = fn() -> Box<dyn ComponentNode>;
29
30// ===========================================================================
31// RustdvComp — the child slot (D75)
32// ===========================================================================
33
34/// A slot that holds any component. It says nothing about position in the
35/// tree and nothing about overridability; the *build line* decides that
36/// (`new_comp()` fixed, `create_comp()` overridable). Every child field a
37/// block may want to override is an `RustdvComp`.
38#[derive(Default)]
39pub struct RustdvComp {
40    inner: Option<Box<dyn ComponentNode>>,
41    /// Set by `create_comp()`; the walk checks flagged slots for an override.
42    overridable: bool,
43    /// The requested type's registered name, for the override lookup.
44    requested: Option<&'static str>,
45}
46
47impl RustdvComp {
48    /// A fixed slot: `new_comp()`. Never overridden.
49    pub fn fixed(node: Box<dyn ComponentNode>) -> RustdvComp {
50        RustdvComp {
51            inner: Some(node),
52            overridable: false,
53            requested: None,
54        }
55    }
56
57    /// A factory slot: `create_comp()`. The default is built now and may be
58    /// swapped for an override during the walk.
59    pub fn overridable(node: Box<dyn ComponentNode>, requested: &'static str) -> RustdvComp {
60        RustdvComp {
61            inner: Some(node),
62            overridable: true,
63            requested: Some(requested),
64        }
65    }
66
67    /// The held component, shared, for asking it things — chiefly for one of
68    /// its ports during `connect`. `None` before the slot is built.
69    pub fn as_node(&self) -> Option<&(dyn ComponentNode + 'static)> {
70        self.inner.as_deref()
71    }
72
73    /// The held component, for the traversal. `None` before it is filled.
74    /// The object lifetime is `'static` (a boxed component always is), which
75    /// matches [`ComponentNode::children_mut`]'s element type.
76    pub fn as_node_mut(&mut self) -> Option<&mut (dyn ComponentNode + 'static)> {
77        self.inner.as_deref_mut()
78    }
79
80    /// Move the held component **out** of the slot, leaving it empty (D82b).
81    ///
82    /// This is what lets a parent's `run` be concurrent with its children's.
83    /// While the box sits in the slot it is part of the parent, so `&mut
84    /// parent` and `&mut child` overlap and cannot both exist. Once moved out
85    /// it is an independent value with no borrow relationship to the parent,
86    /// so both futures can be driven together.
87    ///
88    /// The slot is empty only for the duration of the run phase;
89    /// [`RustdvComp::put_node`] restores it before the post-run phases walk the tree.
90    pub fn take_node(&mut self) -> Option<Box<dyn ComponentNode>> {
91        self.inner.take()
92    }
93
94    /// Put a component taken by [`RustdvComp::take_node`] back into the slot.
95    pub fn put_node(&mut self, node: Box<dyn ComponentNode>) {
96        self.inner = Some(node);
97    }
98
99    /// Called by the derive-generated resolver during the build walk, with
100    /// this slot's field name. If flagged and an override applies at the
101    /// slot's path, swap it in. The discarded default's phases never ran —
102    /// resolution happens before the walk descends into the child.
103    pub fn resolve(&mut self, ctx: &RustdvCtx, name: &str) {
104        if !self.overridable {
105            return;
106        }
107        let Some(req) = self.requested else { return };
108        let path = if ctx.path().is_empty() {
109            name.to_string()
110        } else {
111            format!("{}.{}", ctx.path(), name)
112        };
113        if let Some(ov) = Factory::lookup_override(req, &path) {
114            self.inner = Some((ov.make)());
115        }
116        self.overridable = false;
117    }
118}
119
120/// A slot is a [`PortOwner`], so `connect(&self.producer, ..)` works on an
121/// erased child exactly as `connect(self, ..)` works on the connecting
122/// component. Both questions are answered by a `ComponentNode` method, which
123/// is reachable through `dyn` — no cast to the child's concrete type, which
124/// Rust would not allow anyway.
125impl PortOwner for RustdvComp {
126    fn owner_port_slot(&self, name: &str) -> Option<std::rc::Rc<dyn std::any::Any>> {
127        self.as_node()?.port_slot(name)
128    }
129    fn owner_label(&self) -> &'static str {
130        match self.as_node() {
131            Some(n) => n.node_name(),
132            // An empty slot: the build phase never created this child. Say so
133            // rather than reporting a missing port on a nameless component.
134            None => "an unbuilt child slot",
135        }
136    }
137}
138
139// ===========================================================================
140// Overrides
141// ===========================================================================
142
143/// An installed override: what to build, and the target's name for the dump.
144#[derive(Clone, Copy)]
145pub struct Override {
146    make: Maker,
147    to: &'static str,
148}
149
150impl fmt::Debug for Override {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(f, "-> {}", self.to)
153    }
154}
155
156fn override_key(requested_name: &str) -> String {
157    format!("__factory_override__{requested_name}")
158}
159
160// ===========================================================================
161// Universal registration (D73) — a linkme distributed slice, like the test
162// registry. linkme owns the platform-specific linker section implementation.
163// ===========================================================================
164
165/// One registered component: its name and its maker. Emitted by
166/// `#[derive(Component)]` for every component, universally (D73).
167pub struct ComponentReg {
168    /// Accessor rather than a const string, so the derive can compute it
169    /// from the type without `const` gymnastics.
170    pub name: fn() -> &'static str,
171    pub make: Maker,
172}
173
174/// All component registrations contributed by `#[derive(Component)]`.
175///
176/// Public only so the derive macro can name it from a downstream crate.
177#[doc(hidden)]
178#[distributed_slice]
179pub static COMPONENT_REGISTRATIONS: [ComponentReg];
180
181fn collect_registry() -> HashMap<&'static str, Maker> {
182    let mut map = HashMap::new();
183    for reg in COMPONENT_REGISTRATIONS {
184        map.insert((reg.name)(), reg.make);
185    }
186    map
187}
188
189thread_local! {
190    /// Built once from the link-time section (the set of types does not
191    /// change per test, unlike the override table).
192    static REGISTRY: RefCell<Option<HashMap<&'static str, Maker>>> = const { RefCell::new(None) };
193}
194
195fn with_registry<R>(f: impl FnOnce(&HashMap<&'static str, Maker>) -> R) -> R {
196    REGISTRY.with(|r| {
197        let mut slot = r.borrow_mut();
198        if slot.is_none() {
199            *slot = Some(collect_registry());
200        }
201        f(slot.as_ref().unwrap())
202    })
203}
204
205// ===========================================================================
206// The facade
207// ===========================================================================
208
209/// The factory. Ambient, like the ConfigDb it is built on; every method is
210/// an associated function.
211pub struct Factory;
212
213impl Factory {
214    /// Build a component from its registered string name (D71). Overridable,
215    /// like anything from the factory. A name that is not registered is a
216    /// testbench bug and panics; the file-driven form (ch39) will return a
217    /// `Result` instead.
218    pub fn create_by_name(name: &str) -> RustdvComp {
219        let make = with_registry(|reg| reg.get(name).copied());
220        match make {
221            Some(make) => {
222                // `requested` needs a 'static name; recover the registry's
223                // key so a by-name-created component can also be overridden.
224                let stored = with_registry(|reg| reg.keys().find(|k| **k == name).copied());
225                RustdvComp::overridable(make(), stored.expect("just found it"))
226            }
227            None => panic!("Factory::create_by_name: no component registered as \"{name}\""),
228        }
229    }
230
231    /// Override every `From::create_comp()` with a `To`, testbench-wide
232    /// (UVM `set_type_override_by_type`). Compile-checked: `To` must be a
233    /// component.
234    pub fn set_type_override<From, To>()
235    where
236        From: Component + ComponentNode + Default + 'static,
237        To: Component + ComponentNode + Default + 'static,
238    {
239        Self::store_override(None, "*", From::comp_name(), To::comp_name(), || {
240            Box::new(To::default())
241        });
242    }
243
244    /// The same, by string name (UVM `set_type_override_by_name`). Not
245    /// compile-checked; an unregistered `to` panics at this call.
246    pub fn set_type_override_by_name(from: &str, to: &str) {
247        let make = with_registry(|reg| reg.get(to).copied()).unwrap_or_else(|| {
248            panic!("Factory::set_type_override_by_name: \"{to}\" is not registered")
249        });
250        let to_static =
251            with_registry(|reg| reg.keys().find(|k| **k == to).copied()).expect("just found it");
252        Self::store_override(None, "*", from, to_static, make);
253    }
254
255    /// Override a single instance, addressed by its path relative to `ctx`
256    /// (UVM `set_inst_override_by_type`). The path is a string because it
257    /// names a component elsewhere in the tree — not a duplicate of a field
258    /// name (D75).
259    pub fn set_inst_override<From, To>(ctx: &RustdvCtx, path: &str)
260    where
261        From: Component + ComponentNode + Default + 'static,
262        To: Component + ComponentNode + Default + 'static,
263    {
264        Self::store_override(Some(ctx), path, From::comp_name(), To::comp_name(), || {
265            Box::new(To::default())
266        });
267    }
268
269    fn store_override(
270        ctx: Option<&RustdvCtx>,
271        offset: &str,
272        from_name: &str,
273        to_name: &'static str,
274        make: Maker,
275    ) {
276        ConfigDb::set(
277            ctx,
278            offset,
279            &override_key(from_name),
280            Override { make, to: to_name },
281        );
282    }
283
284    /// The override in force for `requested_name` at `abs_path`, if any.
285    pub(crate) fn lookup_override(requested_name: &str, abs_path: &str) -> Option<Override> {
286        ConfigDb::get::<Override>(None, abs_path, &override_key(requested_name)).ok()
287    }
288
289    /// Print the overrides in force (UVM `uvm_factory().print()`). It is the
290    /// ConfigDb store, shown through the factory's window (D68).
291    pub fn print() {
292        rustdv_sim::log::info("Factory overrides:");
293        for (path, from, to) in ConfigDb::factory_overrides() {
294            rustdv_sim::log::info(&format!("  {path:<28}: {from} -> {to}"));
295        }
296    }
297}
298
299// ===========================================================================
300// Tests — no simulator.
301// ===========================================================================
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306    use crate::component::{Component, RustdvCtx};
307    use crate::config::ConfigDb;
308    use std::cell::RefCell;
309    use std::rc::Rc;
310
311    thread_local! {
312        static BUILT: RefCell<Vec<&'static str>> = const { RefCell::new(Vec::new()) };
313    }
314
315    fn record(name: &'static str) {
316        BUILT.with(|b| b.borrow_mut().push(name));
317    }
318
319    #[derive(Default)]
320    struct Base;
321    impl Component for Base {
322        fn build(&mut self, _ctx: &mut RustdvCtx) {
323            record("Base");
324        }
325    }
326    impl ComponentNode for Base {
327        fn node_name(&self) -> &'static str {
328            "Base"
329        }
330        fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
331            Vec::new()
332        }
333    }
334
335    #[derive(Default)]
336    struct Derived;
337    impl Component for Derived {
338        fn build(&mut self, _ctx: &mut RustdvCtx) {
339            record("Derived");
340        }
341    }
342    impl ComponentNode for Derived {
343        fn node_name(&self) -> &'static str {
344            "Derived"
345        }
346        fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
347            Vec::new()
348        }
349    }
350
351    fn fresh() {
352        ConfigDb::clear();
353        BUILT.with(|b| b.borrow_mut().clear());
354    }
355
356    #[test]
357    fn a_fixed_slot_holds_what_it_was_given() {
358        fresh();
359        let slot = RustdvComp::fixed(Box::new(Base));
360        assert_eq!(slot.as_node().unwrap().node_name(), "Base");
361    }
362
363    #[test]
364    fn an_empty_slot_reports_itself_by_name() {
365        fresh();
366        let slot = RustdvComp::default();
367        assert!(slot.as_node().is_none());
368        assert_eq!(slot.owner_label(), "an unbuilt child slot");
369    }
370
371    /// D75: `new_comp()` is fixed and `create_comp()` is overridable, and the
372    /// *build line* carries that choice — not the field type.
373    #[test]
374    fn a_type_override_swaps_a_create_slot_and_not_a_new_slot() {
375        fresh();
376        Factory::set_type_override::<Base, Derived>();
377        let ctx = RustdvCtx::for_test("env");
378
379        let mut overridable = RustdvComp::overridable(Box::new(Base), "Base");
380        overridable.resolve(&ctx, "tester");
381        assert_eq!(overridable.as_node().unwrap().node_name(), "Derived");
382
383        let mut fixed = RustdvComp::fixed(Box::new(Base));
384        fixed.resolve(&ctx, "scoreboard");
385        assert_eq!(
386            fixed.as_node().unwrap().node_name(),
387            "Base",
388            "new_comp is never swapped"
389        );
390    }
391
392    #[test]
393    fn no_override_leaves_the_requested_type() {
394        fresh();
395        let ctx = RustdvCtx::for_test("env");
396        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
397        slot.resolve(&ctx, "tester");
398        assert_eq!(slot.as_node().unwrap().node_name(), "Base");
399    }
400
401    /// D13's precedence, applied to the factory: an instance override beats a
402    /// type override at the same path.
403    #[test]
404    fn an_instance_override_beats_a_type_override() {
405        fresh();
406        let ctx = RustdvCtx::for_test("env");
407        Factory::set_type_override::<Base, Base>();
408        Factory::set_inst_override::<Base, Derived>(&ctx, "tester");
409        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
410        slot.resolve(&ctx, "tester");
411        assert_eq!(slot.as_node().unwrap().node_name(), "Derived");
412    }
413
414    #[test]
415    fn an_instance_override_applies_only_at_its_path() {
416        fresh();
417        let ctx = RustdvCtx::for_test("env");
418        Factory::set_inst_override::<Base, Derived>(&ctx, "tester");
419
420        let mut here = RustdvComp::overridable(Box::new(Base), "Base");
421        here.resolve(&ctx, "tester");
422        assert_eq!(here.as_node().unwrap().node_name(), "Derived");
423
424        let mut elsewhere = RustdvComp::overridable(Box::new(Base), "Base");
425        elsewhere.resolve(&ctx, "other");
426        assert_eq!(elsewhere.as_node().unwrap().node_name(), "Base");
427    }
428
429    /// D75's guarantee: resolution happens before the walk descends, so the
430    /// discarded default's own phases never run.
431    #[test]
432    fn the_discarded_default_never_built() {
433        fresh();
434        Factory::set_type_override::<Base, Derived>();
435        let ctx = RustdvCtx::for_test("env");
436        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
437        slot.resolve(&ctx, "tester");
438        // Neither has been built yet — but the point is that the *Base* we
439        // threw away is gone before any walk could reach it.
440        assert_eq!(BUILT.with(|b| b.borrow().len()), 0);
441        assert_eq!(slot.as_node().unwrap().node_name(), "Derived");
442    }
443
444    /// Resolving twice must not re-apply: the slot is no longer overridable
445    /// once the walk has passed it.
446    #[test]
447    fn a_slot_resolves_once() {
448        fresh();
449        let ctx = RustdvCtx::for_test("env");
450        let mut slot = RustdvComp::overridable(Box::new(Base), "Base");
451        slot.resolve(&ctx, "tester");
452        Factory::set_type_override::<Base, Derived>(); // installed too late
453        slot.resolve(&ctx, "tester");
454        assert_eq!(
455            slot.as_node().unwrap().node_name(),
456            "Base",
457            "an override installed after the walk passed does not apply"
458        );
459    }
460
461    #[test]
462    fn take_and_put_move_the_box_out_and_back() {
463        fresh();
464        let mut slot = RustdvComp::fixed(Box::new(Base));
465        let node = slot.take_node().expect("something to take");
466        assert!(
467            slot.as_node().is_none(),
468            "the slot is empty during the run phase"
469        );
470        slot.put_node(node);
471        assert_eq!(
472            slot.as_node().unwrap().node_name(),
473            "Base",
474            "and restored after"
475        );
476    }
477
478    // --- the sequence half of the factory (D80/D96) ----------------------
479
480    use crate::sequence::{
481        SeqCtx, SeqError, Sequence, clear_seq_overrides, create_seq, set_seq_override,
482    };
483
484    #[derive(Default)]
485    struct BaseSeq;
486    #[derive(Default)]
487    struct RandomSeq;
488
489    impl Sequence for BaseSeq {
490        type Req = u8;
491        type Rsp = u8;
492        async fn body(&mut self, _c: &mut SeqCtx<u8, u8>) -> Result<(), SeqError> {
493            Ok(())
494        }
495        fn seq_name(&self) -> &'static str {
496            "BaseSeq"
497        }
498    }
499    impl Sequence for RandomSeq {
500        type Req = u8;
501        type Rsp = u8;
502        async fn body(&mut self, _c: &mut SeqCtx<u8, u8>) -> Result<(), SeqError> {
503            Ok(())
504        }
505        fn seq_name(&self) -> &'static str {
506            "RandomSeq"
507        }
508    }
509
510    #[test]
511    fn create_seq_builds_the_requested_type_by_default() {
512        clear_seq_overrides();
513        let seq = create_seq::<BaseSeq>();
514        assert_eq!(seq.name(), "BaseSeq");
515    }
516
517    #[test]
518    fn a_sequence_override_swaps_the_type() {
519        clear_seq_overrides();
520        set_seq_override::<BaseSeq, RandomSeq>();
521        let seq = create_seq::<BaseSeq>();
522        assert_eq!(
523            seq.name(),
524            "RandomSeq",
525            "the test asked for Base and got Random"
526        );
527    }
528
529    #[test]
530    fn clearing_sequence_overrides_restores_the_default() {
531        clear_seq_overrides();
532        set_seq_override::<BaseSeq, RandomSeq>();
533        clear_seq_overrides();
534        assert_eq!(create_seq::<BaseSeq>().name(), "BaseSeq");
535    }
536
537    #[test]
538    fn an_override_on_one_sequence_leaves_others_alone() {
539        clear_seq_overrides();
540        set_seq_override::<BaseSeq, RandomSeq>();
541        assert_eq!(create_seq::<RandomSeq>().name(), "RandomSeq");
542    }
543
544    // Unused-import guard: `Rc` is here for future handle tests.
545    #[allow(dead_code)]
546    fn _rc_in_scope(_: Rc<u8>) {}
547}