Dataset Hooks
Dataset hooks fire during the load and save operations inside Node::call(). Each hook receives the owning node and the dataset reference.
Methods
fn before_dataset_loaded(&self, n: &dyn StepInfo, ds: &DatasetRef)
-> Result<HookControl, HookAbort> { Ok(HookControl::Continue) }
fn after_dataset_loaded(&self, n: &dyn StepInfo, ds: &DatasetRef, value: &dyn Any)
-> Result<(), HookAbort> { Ok(()) }
fn before_dataset_saved(&self, n: &dyn StepInfo, ds: &DatasetRef, value: &dyn Any)
-> Result<HookControl, HookAbort> { Ok(HookControl::Continue) }
fn after_dataset_saved(&self, n: &dyn StepInfo, ds: &DatasetRef)
-> Result<(), HookAbort> { Ok(()) }
Arguments
nβ the node that is loading/saving the dataset. Usen.name()to get the node name.dsβ the dataset reference:ds.idβ unique pointer-based identifierds.nameβ resolved name from the catalog (e.g.Some("readings")), orNoneinno_stdds.meta.is_param()β whether this is a parameter datasetds.meta.type_string()β the Rust type name (e.g."pondrs::datasets::memory::MemoryDataset<f64>")ds.meta.html()β (stdonly) returns an optional HTML snippet for the datasetβs current contents. Datasets likePlotlyDatasetoverride this to produce rich visualizations; file-backed datasets render their contents as formatted text. Used by the viz dashboard.ds.meta.yaml()β (stdonly) returns the datasetβs configuration serialized as YAML.
value(onafter_dataset_loadedandbefore_dataset_saved) β the dataset value as&dyn Any. Usevalue.downcast_ref::<T>()to inspect a specific type. For ergonomic type-safe access, see Typed Hooks.
Firing order
For a node with two inputs and one output, hooks fire in this order:
before_dataset_loaded(n, ds0)after_dataset_loaded(n, ds0, &value0)before_dataset_loaded(n, ds1)after_dataset_loaded(n, ds1, &value1)- node function executes
before_dataset_saved(n, ds_out, &output_value)- if
Skip: save is skipped,after_dataset_saveddoes not fire - if
Continue: dataset is saved, then:
- if
after_dataset_saved(n, ds_out)
Dataset hooks fire inside the before_node_run / after_node_run window. The sequence is always: before_node_run β dataset loads β function call β dataset saves β after_node_run.
Note:
before_dataset_loadedreturnsHookControlfor API consistency, butSkipis not currently acted on β the load always proceeds. Onlybefore_dataset_savedSkip has an effect.
Example: tracking I/O time
use std::sync::Mutex;
use std::collections::HashMap;
use std::time::Instant;
struct IoTimingHook {
starts: Mutex<HashMap<usize, Instant>>,
}
impl Hook for IoTimingHook {
fn before_dataset_loaded(&self, _n: &dyn StepInfo, ds: &DatasetRef)
-> Result<HookControl, HookAbort>
{
self.starts.lock().unwrap().insert(ds.id, Instant::now());
Ok(HookControl::Continue)
}
fn after_dataset_loaded(&self, _n: &dyn StepInfo, ds: &DatasetRef, _value: &dyn Any)
-> Result<(), HookAbort>
{
if let Some(start) = self.starts.lock().unwrap().remove(&ds.id) {
let name = ds.name.unwrap_or("<unknown>");
println!(" loaded {} in {:.1}ms", name, start.elapsed().as_secs_f64() * 1000.0);
}
Ok(())
}
}
Inspecting values
The value parameter on after_dataset_loaded and before_dataset_saved is type-erased as &dyn Any. You can downcast it manually:
fn after_dataset_loaded(&self, n: &dyn StepInfo, ds: &DatasetRef, value: &dyn Any)
-> Result<(), HookAbort>
{
if let Some(v) = value.downcast_ref::<f64>() {
println!("Node {} loaded f64 value: {}", n.name(), v);
}
Ok(())
}
For hooks that only care about one type, Typed Hooks provide a cleaner approach β you implement TypedHook<T> with methods that receive &T directly, and non-matching types are silently ignored.
Name resolution
In std builds, the runner resolves dataset names from the catalog indexer before dispatching to hooks. This is why ds.name is Option<&str> β itβs Some("readings") when the catalog indexer can map the pointer to a field name, and None in no_std builds where the indexer isnβt available.