In this section we're going to look at the code that implements this program. This section is divided into subsections.
The process.cpp file describes the main program.
The CRingITemProcessor class
describes the code defining and implementing the
CRingItemProcessor class.
Let's start by lookcing at the changes we needed to make in the heading part of the main relative to the ring reader example:
#include <CDataSource.h> #include <CDataSourceFactory.h> #include <CRingItem.h> #include <DataFormat.h> #include <Exception.h> #include <CRingItemFactory.h>#include <CRingScalerItem.h> #include <CRingStateChangeItem.h> #include <CRingTextItem.h> #include <CPhysicsEventItem.h>
#include <CRingPhysicsEventCountItem.h> #include <CDataFormatItem.h> #include <CGlomParameters.h> #include <CDataFormatItem.h> #include "processor.h"
#include <iostream> #include <cstdlib> #include <memory> #include <vector> #include <cstdint> static void
processRingItem(CRingItemProcessor& procesor, CRingItem& item);

processRingItem
static function.

CRingItem. We'll see them
used in processRingItem below.

CRingItemProcessor
object by reference to the
processRingItem function.
The pass by reference allows the polymorphism of
a hypothetical class hierarchy based on a
CRingItemProcessor base
class to function.
The next change in the main program is just to
instantiate a CRingItemProcessor and
to pass it to the processRingItem
function:
...
CRingItem* pItem;
CRingItemProcessor processor;
while ((pItem = pDataSource->getItem() )) {
std::unique_ptr<CRingItem> item(pItem);
processRingItem(processor, *item);
}
...
The biggest change is in the implementatino of
processRingItem:
static void
processRingItem(CRingItemProcessor& processor, CRingItem& item)
{
// Create a dynamic ring item that can be dynamic cast to a specific one:
CRingItem* castableItem =
CRingItemFactory::createRingItem(item);
std::unique_ptr<CRingItem> autoDeletedItem(castableItem);
// Depending on the ring item type dynamic_cast the ring item to the
// appropriate final class and invoke the correct handler.
// the default case just invokes the unknown item type handler.
switch (castableItem->type()) {
case PERIODIC_SCALERS:
{
CRingScalerItem& scaler(
dynamic_cast<CRingScalerItem&>(*castableItem)
);
processor.processScalerItem(scaler);
break;
}
case BEGIN_RUN: // All of these are state changes:
case END_RUN:
case PAUSE_RUN:
case RESUME_RUN:
{
CRingStateChangeItem& statechange(dynamic_cast<CRingStateChangeItem&>(*castableItem));
processor.processStateChangeItem(statechange);
break;
}
case PACKET_TYPES: // Both are textual item types
case MONITORED_VARIABLES:
{
CRingTextItem& text(dynamic_cast<CRingTextItem&>(*castableItem));
processor.processTextItem(text);
break;
}
case PHYSICS_EVENT:
{
CPhysicsEventItem& event(dynamic_cast<CPhysicsEventItem&>(*castableItem));
processor.processEvent(event);
break;
}
case PHYSICS_EVENT_COUNT:
{
CRingPhysicsEventCountItem&
eventcount(dynamic_cast<CRingPhysicsEventCountItem&>(*castableItem));
processor.processEventCount(eventcount);
break;
}
case RING_FORMAT:
{
CDataFormatItem& format(dynamic_cast<CDataFormatItem&>(*castableItem));
processor.processFormat(format);
break;
}
case EVB_GLOM_INFO:
{
CGlomParameters& glomparams(dynamic_cast<CGlomParameters&>(*castableItem));
processor.processGlomParams(glomparams);
break;
}
default:
{
processor.processUnknownItemType(item);
break;
}
}
}
While this function is rather long, much of the processing has the same flavor. We will therefore only show detailed processing for scaler items.

CRingItem*.
In cases in the switch statement, we shall see
type safe up-casts done
once the actual item type is known.

std::unique_ptr to ensure
the dynamically allocated object returned by the
factory is destroyed regardless how the
processRingItem function
exits.



CRingScalerItem
object because we now know that this is the type of
object our castableItem actually
points at.
Using a dynamic cast rathe rthan the alternative
ensures that type checking is done by the cast at
run-time to ensure this is a legal type conversion.
If the underlying type is not actually a
CRingScalerItem object of from
a class that is a subclass of that class, the dynamic
cast would throw an exception (std::bad_cast).


CRingITemProcessor class
Let's look at the CRingProcessor class.
First the header, as it does point to an interesting program
design philosophy. processor.h contains:
#ifndef PROCESSOR_H#define PROCESSOR_H class CRingScalerItem; class CRingStateChangeItem; class CRingTextItem; class CPhysicsEventItem;
class CRingPhysicsEventCountItem; class CDataFormatItem; class CGlomParameters; class CRingItem; class CRingItemProcessor { public: virtual void processScalerItem(CRingScalerItem& item); virtual void processStateChangeItem(CRingStateChangeItem& item); virtual void processTextItem(CRingTextItem& item); virtual void processEvent(CPhysicsEventItem& item);
virtual void processEventCount(CRingPhysicsEventCountItem& item); virtual void processFormat(CDataFormatItem& item); virtual void processGlomParams(CGlomParameters& item); virtual void processUnknownItemType(CRingItem& item); }; #endif

This pattern is called an include guard. Using include guards is good programming practice. If you do use them, choose a convention for the name of the preprocessor symbol you will be defining as collisions with include guard names can be incredibly hard to sort out.
The include guard name shown is the convention followed in NSCLDAQ itself as well as several other open source projects.

This is a good practice because:
Headers are often included in more than one place and the recompilation of headers included in headers and so on, can slow down compilation times.
More importantly, including headers can lead to circular dependencies. That is file A includes B which includes A that can be tough for the compiler to untangle.
The rule I follow is that if the compiler does not need to know the shape of a class or other entity, or its methods, I usd a forward definition rather than an #include.

processRingItem.
Now lets look at the implementation file processor.cpp. Here's the head of that file:
#include "processor.h"#include <CRingItem.h> #include <CRingScalerItem.h> #include <CRingTextItem.h> #include <CRingStateChangeItem.h>
#include <CPhysicsEventItem.h> #include <CRingPhysicsEventCountItem.h> #include <CDataFormatItem.h> #include <CGlomParameters.h> #include <iostream> #include <map> #include <string> #include <ctime>
static std::map<CGlomParameters::TimestampPolicy, std::string> glomPolicyMap = { {CGlomParameters::first, "first"}, {CGlomParameters::last, "last"}, {CGlomParameters::average, "average"} };


CRingItem in the
class definition header. I do need to include them here
as I'm going to be invoking method functions on each of them.
If I was just going to pass these objects by reference or pointers to methods or functions implemented in a separate file I probably would not include the definitions in this file as it would not be necessary.

Let's look at the implementations of the type dependent processing methods one by one. For the most part, just exploit the methods available to the subclass ring item types to output some partial or complete dump of the items.
Example 4-1.
CRingItemProcessor::processScalerItem
void
CRingItemProcessor::processScalerItem(CRingScalerItem& item)
{
time_t ts = item.getTimestamp();
std::cout << "Scaler item recorded "
<< ctime(&ts) << std::endl;
for (int i = 0; i < item.getScalerCount(); i++) {
std::cout << "Channel " << i << " had "
<< item.getScaler(i) << " counts\n";
}
}

getTimestamp, where
defined, gets the unix timestamp at which a ring
item was created. Don't confuse this with
getEventTimestamp defined
in the CRingItem base class
which returns the high precision event timestamp
used in event building.

ctime is defined in the
ctime header and produces a
formatted string representation of the
pointer to a time_t it is passed.

getScalerCount returns
the number of scaler vlues in the item. This
controls the loop which uses
getScaler to
get the specified scaler value.
Example 4-2.
CRingItemProcessor::processStateChangeItem
void
CRingItemProcessor::processStateChangeItem(CRingStateChangeItem& item)
{
time_t tm = item.getTimestamp();
std::cout << item.typeName()
<< " item recorded for run "
<< item.getRunNumber() << std::endl;
std::cout << "Title: "
<< item.getTitle() << std::endl;
std::cout << "Occured at: " << std::ctime(&tm)
<< " " << item.getElapsedTime()
<< " sec. into the run\n";
}
State change items indicate a change in the run state. These differ only in the ring item type.

CRingItem class has
a method that return s a text string that describes
the item type. We use this to indicate which
transition this record reflects.

getRunNumber method
for CRingStateChangeItem returns
that run number.

getTitle returns
that as a std::string.

getElapsedTime returns
this value in seconds. Naturally,
BEGIN_RUN items have an
elapsed time of 0.
Example 4-3.
CRingItemProcessor::processTextItem
void
CRingItemProcessor::processTextItem(CRingTextItem& item)
{
time_t tm = item.getTimestamp();
std::cout << item.typeName() << " item recorded at "
<< std::ctime(&tm) << " " << item.getTimeOffset()
<< " seconds into the run\n";
std::cout << "Here are the recorded strings: \n";
std::vector<std::string> strings = item.getStrings();
for (int i =0; i < strings.size(); i++) {
std::cout << i << ": '" << strings[i] << "'\n";
}
}
CRingTextItem objects contain
a sequence of null terminated strings. These are used to
document aspects of the generating program. For example,
if you use documented packages in the SBS readout
framework, you will get a string for each of those
packets in a PACKET_TYPES item.
Similarly, if you create some run variables that are externally modified via a readout framework's TclServer component, these variables and their values will be periodically logged in a MONITORED_VARIABLES item. Note that each variable will be logged in a string that could be sourced into a Tcl interpreter to re-define that variable.
Regardles of the type of ring item,
getStrings returns a
std::vector<std::string>
that contains the strings in the item.
Example 4-4.
CRingItemProcessor::processEvent
void
CRingItemProcessor::processEvent(CPhysicsEventItem& item)
{
std::cout << "Event:\n";
std::cout << item.toString() << std::endl;
}
All items have a toString
method that is polymorphic in the base calss
CRingItem. This is used
by dumper and now by us to create a textual dump of the
event.
Typically this is where you'd put the real work of unpacking the physics event and doing something useful with it, like making a root tree.
Example 4-5.
CRingItemProcessor::processEventCount
void
CRingItemProcessor::processEventCount(CRingPhysicsEventCountItem& item)
{
time_t tm = item.getTimestamp();
std::cout << "Event count item";
if (item.hasBodyHeader()) {
std::cout << " from source id: " << item.getSourceId();
}
std::cout << std::endl;
std::cout << "Emitted at: " << std::ctime(&tm) << " "
<< item.getTimeOffset() << " seconds into the run \n";
std::cout << item.getEventCount() << " events since lastone\n";
}
CRingPhysicEventCountItem objects
are periodically emitted by readout frameworks during an active
run. They provide information about the number of triggers
processed since the last such item, or the beginning of the
run for the first one.
The intent is that these items can provide an idea of the trigger rate as well as information about how many items were skipped when the consumer is sampling physics items. Well that's the idea anyway. This all becomes a bit more complicated with event built data, and even more complicated with multilevel event building.
Example 4-6.
CRingItemProcessor::processFormat
void
CRingItemProcessor::processFormat(CDataFormatItem& item)
{
std::cout << " Data format is for: "
<< item.getMajor() << "." << item.getMinor() << std::endl;
}
Since NSCLDAQ-11.0, data producers have started to emit data format records so that software (and humans) know how to handle data files as the formats evolve. The format record contains only a major and minor format version. These match the version of NSCLDAQ in which the data format was introduced.
getMajor returns the major version number
and getMinor returns the minor version.
Example 4-7. CRingItemProcessor::processGlomParams
void
CRingItemProcessor::processGlomParams(CGlomParameters& item)
{
std::cout << "Event built data. Glom is: ";
if (item.isBuilding()) {
std::cout << "building with coincidece interval: "
<< item.coincidenceTicks()
<< std::endl;
std::cout << "Timestamp policy: "
<< glomPolicyMap[item.timestampPolicy()]
<< std::endl;
} else {
std::cout << "operating in passthrough (non-building) mode\n";
}
}
When experiments use the event builder, the last stage of the event builder, glom, emits a glom parameters item. This item describes how glom was told to function.
Glom can glue fragments together into events, given a coincidence interval (in timestamp units), or it can operate in non-building, passthrough mode, where the fragments are just passed on through to the output.

isBuilding
returns boolean true if glom has been asked to glue
fragments togehter. If not, none of the other
glom parameters are relevant.

coincidenceTicks
returns the number of timestamp ticks in the
event building coincidence window. If not building,
the return value from this method is meaningless.

timestampPolicy is an
enumerated value that describes this policy. This
is used as a key to lookup the textual version of
that policy.
Example 4-8.
CRingItemProcessor::processUnknownItemType
void
CRingItemProcessor::processUnknownItemType(CRingItem& item)
{
std::cout << item.toString() << std::endl;
}
If we have receiged a ring item type that we don't know
about, we just use the toString
method to dump it to stdout.