HeaderStorage
HeaderStorage is the base class for any object that contributes
fields to an experiment’s CSV header file. The header is the
human-readable, semicolon-delimited record of every parameter that
defined an acquisition: hardware settings, RF and chirp configuration,
digitizer setup, flow setpoints, validation thresholds, and so on. The
file uses six columns — object key, array key, array index, key, value,
unit — and HeaderStorage packs values into that schema on the way
out and unpacks them back when an experiment is loaded from disk.
Experiment is the root of a tree of HeaderStorage nodes, with
FtmwConfig, RfConfig, the digitizer, the pulse generator,
the LIF config, and the validator as children, each of which may add
its own grandchildren. The framework dispatches incoming rows to the
correct subtree by matching the object key in column 0, and on the
write side it walks the tree depth-first to produce the full set of
rows. The on-disk layout of an experiment, including the header file,
is described in Data Storage.
The two virtuals you must implement
Subclasses override two pure virtuals:
storeValues()runs just before the header is written. Inside it, callstore()once per scalar field andstoreArrayValue()once per cell of any array fields. Each row is buffered in this object’s cache.retrieveValues()runs after the header has been parsed and all matching rows have been routed to this object. Inside it, callretrieve()andretrieveArrayValue()to extract the cached values into your own members.
Each call to retrieve() or retrieveArrayValue() removes the
row from the cache. Asking for the same key twice yields the default
the second time. arrayStoreSize() reports how many entries an
array has before you start consuming it.
Composing a tree
A HeaderStorage may own children. Children are declared by
overriding prepareChildren() and calling addChild() once per
child:
void Experiment::prepareChildren()
{
addChild(ps_ftmwConfig.get());
addChild(ps_validator.get());
for(auto &p : d_hwCfgs)
addChild(p.get());
addChild(ps_lifCfg.get());
}
The framework calls prepareChildren() at the start of every read
or write pass, after wiping any prior child list, so the tree always
reflects the current shape of the data. Children themselves do not
call addChild on their parent — the parent owns the relationship
in prepareChildren(). The framework then recurses into each
child’s prepareChildren(), allowing arbitrary nesting.
removeChild() exists for the rare case of a parent that needs to
detach a child mid-flight (Experiment uses it when the user
disables an FTMW or LIF subsystem). It does not delete the child
object; ownership lives with whoever holds the smart pointer.
Write flow
A caller invokes
getStrings()on the root (Experimentdoes this insidesave()viaBlackchirpCSV::writeHeader).Each node’s
prepareToStore()runs, which callsprepareChildren()and recurses into each child.Each node’s
storeValues()runs, populating its cache viastore()andstoreArrayValue().The framework converts cached entries to the six-column form, merges in each child’s
getStrings()output, and clears the in-memory cache.
Therefore: never call store() outside storeValues(); the rows
would be cleared on the next write.
Read flow
The caller (
Experiment’s reading constructor) callsprepareToStore()on the root once, so the child tree is built.The caller reads the CSV file line by line and hands each row to
storeLine()on the root. Rows are dispatched to whichever node’sd_headerKeymatches column 0 (children searched first if the root does not match).After all lines have been routed, the caller invokes
readComplete()on the root, which callsretrieveValues()on every node depth-first.Each
retrieveValues()implementation pulls rows back out viaretrieve()/retrieveArrayValue()and assigns them to the object’s members.
Therefore: never call retrieve() outside retrieveValues() (or
after readComplete() has run) — the cache is empty by then.
Enum cells
store() / storeArrayValue() template overloads wrap their
argument with QVariant::fromValue, so a Q_ENUM-registered
enumerator is written by name rather than as an opaque integer. On
the read side retrieve() and retrieveArrayValue() dispatch
to BC::CSV::enumFromVariant whenever the requested type carries
Q_ENUM or Q_ENUM_NS, so historical fixtures whose cells held
the integer form continue to round-trip back to the typed value
without subclasses having to call the helper directly. The dual-form
contract that motivates this is described under
Enum cells: writing names, reading both.
Object-key conventions
Singleton-style objects (
Experiment,RfConfig,ChirpConfig, etc.) pass a constant key from theirBC::Store::*namespace.Per-instance objects (
PulseGenConfigand other hardware configs) pass the hardware key for the specific instance (e.g."PulseGenerator.main"). This guarantees that experiments with several instances of the same hardware type produce distinguishable header rows.
The chosen key is stored in d_headerKey and cannot be changed
afterward.
API Reference
-
class HeaderStorage
Base class for objects that contribute fields to an experiment’s CSV header file.
Each HeaderStorage is identified by an object key (column 0 of the six-column header CSV: object key, array key, array index, key, value, unit). Rows are dispatched between objects by matching column 0, so the same hardware-instance key (e.g.
"PulseGenerator.main") that identifies a HardwareObject is also used as its HeaderStorage object key. The chosen key is stored ind_headerKeyand cannot be changed afterward.Cache invariants:
store()/storeArrayValue()may only be called insidestoreValues(), andretrieve()/retrieveArrayValue()may only be called insideretrieveValues(). The cache is cleared at the start of each write pass, and each retrieve removes its row, so calls outside those hooks yield silently wrong results. Tree composition is declared by overridingprepareChildren()and callingaddChild()once per child; the framework rebuilds the child list at the start of every read or write pass and recurses automatically.Subclassed by ChirpConfig, DigitizerConfig, Experiment, ExperimentValidator, FlowConfig, FtmwConfig, LifConfig, PressureControllerConfig, PulseGenConfig, RfConfig, TemperatureControllerConfig
Public Types
-
using ValueUnit = std::pair<QVariant, QString>
Alias for storing a value and unit
-
using HeaderMap = std::map<QString, ValueUnit, std::less<>>
Alias for a map of key-value+unit pairs
-
using HeaderStrings = std::multimap<QString, std::tuple<QString, QString, QString, QString, QString>>
Alias for a set of strings representing header data, together with the object key
Public Functions
-
HeaderStorage(QAnyStringView objKey)
Constructor. Sets the object key, which should be unique to the most derived class.
- Parameters:
objKey – The object key written into the first column of every header line produced by this object. Used by storeLine() to dispatch incoming lines to the correct HeaderStorage in the parent/child tree.
-
inline virtual ~HeaderStorage()
Destructor. Does nothing.
-
inline QString headerKey() const
This node’s object key (column 0 of the CSV).
- Returns:
The key set in the constructor.
-
HeaderStrings getStrings()
Drive the write pass: build the child tree, populate caches, collect every node’s rows, and clear the caches.
Called by the experiment writer (BlackchirpCSV::writeHeader) on the root HeaderStorage. Internally:
calls prepareToStore() to (re)build the child tree;
calls this object’s storeValues() to fill its cache;
packs every cached row into the six-column form (object key, array key, array index, key, value, unit);
recursively merges every child’s getStrings() result;
clears this object’s cache so a subsequent write starts clean.
Subclasses normally never call this directly; only the writer does. They participate by overriding storeValues() and prepareChildren().
- Returns:
Multimap of cached rows keyed by the producing object’s d_headerKey. Each value is a five-string tuple (array key, array index, key, value, unit), any of which may be empty.
-
void prepareToStore()
Build the child tree (recursive).
Clears this node’s child list, then calls prepareChildren() so the subclass can repopulate it via addChild(), then recurses into every newly added child. Called automatically at the start of a read pass and from getStrings() at the start of a write pass.
-
bool storeLine(const QVariantList l)
Route one parsed CSV row into the appropriate node’s cache.
Called by the experiment reader once per CSV line, after the line has been parsed into six QVariants. If l ‘s first entry matches d_headerKey, the row is added to this object’s cache; otherwise children are searched depth-first. Empty key or value cells cause the row to be dropped silently.
The caller (Experiment’s reading constructor) is responsible for verifying l has exactly six entries before invoking this function — there is no length check inside.
- Parameters:
l – Six-element list parsed from a CSV row:
Object Key
Array Key (empty for scalar rows)
Array Index (empty for scalar rows)
Key
Value
Unit (may be empty)
- Returns:
True if the row was claimed by this node or any of its descendants; false if no matching object key was found.
-
void readComplete()
Drive the read pass: invoke retrieveValues() depth-first.
Called by the experiment reader on the root HeaderStorage after every line has been routed via storeLine(). This object’s retrieveValues() runs first, then every child’s readComplete() is called in turn. By the time control returns to the reader, each subclass has consumed its cached rows back into its own members.
-
void addChild(HeaderStorage *other)
Register a child for this node. Call from prepareChildren().
Children are stored as raw pointers; ownership is not transferred. The caller (typically the parent’s prepareChildren()) must guarantee that the child outlives the read or write pass currently in progress. Passing nullptr is safe and ignored.
- Parameters:
other – Pointer to the child HeaderStorage.
-
HeaderStorage *removeChild(HeaderStorage *child)
Detach a child without deleting it.
Used by parents that disable a subsystem during the lifetime of the parent (Experiment uses it when an FTMW or LIF subsystem is removed mid-flight). After removal the child no longer participates in subsequent read/write passes; ownership of the child object is unaffected.
- Parameters:
child – Child to detach.
- Returns:
The detached child pointer, or nullptr if not found.
-
inline virtual void prepareChildren()
Declare this node’s children. Override in subclasses with children.
Called every time prepareToStore() runs, after the prior child list has been wiped. Inside, call addChild() once per child. The default implementation declares no children.
Repopulating the list every pass (rather than once at construction) means children that come and go with user choices — a freshly disabled hardware subsystem, an optional validator — are reflected automatically.
Protected Functions
-
virtual void storeValues() = 0
Populate this object’s cache with rows to be written.
Subclasses must implement. Inside, call store() once per scalar field and storeArrayValue() once per cell of any array fields. Do not call store() / storeArrayValue() from anywhere else; rows added outside this function are cleared at the next getStrings() call.
-
virtual void retrieveValues() = 0
Consume cached rows back into this object’s members.
Subclasses must implement. Called once per node, depth-first, after every header row has been routed by storeLine(). Inside, call retrieve() / retrieveArrayValue() (each call removes the row from the cache) and assign the values back to your own data members. Use arrayStoreSize() to learn how many entries an array contains before iterating it.
-
template<typename T>
inline void store(QAnyStringView key, const T &val, QAnyStringView unit = {}) Cache one scalar row for writing. Templated overload.
Call from storeValues(). Equivalent to the QVariant overload but accepts any type that QVariant can wrap.
- Parameters:
key – Row key (column 4 of the CSV).
val – Value (column 5).
unit – Optional unit (column 6).
-
void store(QAnyStringView key, const QVariant val, QAnyStringView unit = {})
Cache one scalar row for writing.
Call from storeValues(). The row is added to this object’s cache and emitted by the next getStrings() call. Repeated calls with the same key overwrite.
- Parameters:
key – Row key (column 4 of the CSV).
val – Value (column 5).
unit – Optional unit (column 6).
-
template<typename T>
inline void storeArrayValue(QAnyStringView arrayKey, std::size_t index, QAnyStringView key, const T &val, QAnyStringView unit = {}) Cache one cell of an array for writing. Templated overload.
Equivalent to the QVariant overload but accepts any type that QVariant can wrap.
- Parameters:
arrayKey – Array name (column 2 of the CSV).
index – Row index within the array (column 3).
key – Cell key (column 4).
val – Cell value (column 5).
unit – Optional unit (column 6).
-
void storeArrayValue(QAnyStringView arrayKey, std::size_t index, QAnyStringView key, const QVariant val, QAnyStringView unit = {})
Cache one cell of an array for writing.
Call from storeValues() once per cell. Use array storage for any list-like collection (e.g. one row per pulse-generator channel). If arrayKey is new, the array is created; if index exceeds the current size, the array grows to fit. A typical loop:
for(std::size_t i = 0; i < d_channels.size(); ++i) { const auto &c = d_channels[i]; storeArrayValue(channelArr, i, name, c.name); storeArrayValue(channelArr, i, delay, c.delay, "us"_L1); storeArrayValue(channelArr, i, enabled, c.enabled); }
- Parameters:
arrayKey – Array name (column 2 of the CSV).
index – Row index within the array (column 3).
key – Cell key (column 4).
val – Cell value (column 5).
unit – Optional unit (column 6).
-
template<typename T>
inline T retrieve(QAnyStringView key, const T &defaultValue = QVariant().value<T>()) Pull one scalar row out of the cache.
Call from retrieveValues(). Removes the row from the cache; a second retrieve with the same key returns defaultValue. Convert from QVariant via QVariant::value, so any default value type that round-trips through QVariant is allowed.
- Parameters:
key – The key matched against column 4 of the cached row.
defaultValue – Returned if the key is missing or has already been retrieved. Defaults to a default-constructed T.
- Returns:
The retrieved value, or defaultValue.
-
QString peekUnit(QAnyStringView key) const
Read the unit cell of a cached scalar row without consuming it.
Returns column 6 of the row matching key. Does not erase the cache entry, so a subsequent retrieve() with the same key still returns the value as usual. Returns an empty QString if the key is absent or its unit cell was empty on disk.
-
QString peekValueString(QAnyStringView key) const
Read the literal value-cell string of a cached scalar row without consuming it.
Returns column 5 of the row matching key as the QString form the CSV reader stuffed into the QVariant. Useful for inspecting the on-disk numeric formatting (e.g., counting fractional digits) before letting retrieve() convert to a typed value. Returns an empty QString if the key is absent or the value cell was empty.
-
std::size_t arrayStoreSize(QAnyStringView key) const
Number of entries in a cached array.
Use this from retrieveValues() to size your loop before pulling values out with retrieveArrayValue().
- Parameters:
key – Array name to look up.
- Returns:
Number of entries, or 0 if the array was not stored.
-
template<typename T>
inline T retrieveArrayValue(QAnyStringView arrayKey, std::size_t index, QAnyStringView key, const T &defaultValue = QVariant().value<T>()) Pull one cell out of a cached array.
Call from retrieveValues(). Removes the cell from the cache; the containing entry’s outer slot is preserved (so arrayStoreSize() does not change), but a second retrieve of the same cell returns defaultValue.
- Parameters:
arrayKey – Array name.
index – Row index within the array.
key – Cell key.
defaultValue – Returned if the array is missing, the index is out of range, or the cell has already been retrieved.
- Returns:
The retrieved value, or defaultValue.
Protected Attributes
-
QString d_headerKey
Object key (column 0 of the CSV); set in the constructor and treated as immutable.
Private Members
-
std::map<QString, HeaderArray, std::less<>> d_arrayValues
Map containing lists of key-value pairs
-
std::vector<HeaderStorage*> d_children
List containing pointers to children
-
using ValueUnit = std::pair<QVariant, QString>