SpecTcl tries hard to be data acquisition system agnostic. This begins with not having any direct connection to specific online systems and extens to the way in which events received from a data source are parsed into chucks of events which are then passed to the analyzer which forwards them on to the event processing pipeline.
Let's have a look at how this all hangs together:
SpecTcl's data comes from data sources. Each data source type has a I/O class. This class passes fixed sized blocks of data to a buffer decoder object. The buffer decoder object.
The buffer decoder is supposed to understand how data are packaged by the source data acquisition system. It is supposed to pass blocks of events to the analyzer in a way that allows that object to call the event processing pipeline. Secondarily, the decoder is supposed to create and make avaialble information about the source system byte ordering so that clients of the data can construct appropriate data translation objects.
The buffer decoder is selected by the attach command.
The class that implements that provides a method for extending the
set of values for its -format
switch.
Note that this has actually been done for two non NSCL data acquisition systems over the life of SpecTcl (that I know about). Specifically, SpecTcl has analyzed data from IUCF XSYS (at Kansas State University's Macdonald Laboratory), and from the Lucid data acquisition system (at the Saskatchewan Accelerator Laboratory, decommissioned in 1999). )
In the remainder of this chapter we will:
Describe in detail the key classes involved in the flow of data from data source to analyzer.
Describe the steps needed to support data from another data acquisition systsem.
Give a simple example supporting data from some fictitious data acquisition system. The data format will be chosen so that the actual programming is minimal.
SpecTcl uses classes derived from the following base classes to get data from data source to analyzer:
CFile
Represents a data source from a file descriptor. This
is not an abstract base class, as it's used to accept
data from file data sources. It is also the base class
for CPipeFile
which gets data
from a program helper along a pipe.
CBufferDecoder
Understands how items are packaged by the data
acquisition system. Once the CFile
object has been asked to read a block of data from its
encapsulated file descriptor, that block of data is passed
to the CBufferDecoder
. That
object unpackes items from the data block and
calls appropriate methods in the
CAnalyzer
object.
CAnalyzer
Dispatches items from the buffer decoder to the appropriate methods of the event processors in the event processing pipeline.
TranslatorPointer
A templated class that has the semantics of a pointer
but transparently performs byte reordering if needed.
This operates by encapsulating a
BufferTranslator
object which
may or my not swap items as needed.
The BufferFactory
knows how to
instantiate the right type of BufferTranslator
by being given an appropriate set of signature bytes which
define the byte order of the originating system.
These are compared against signature bytes generated
by the running system to determine the required byte ordering.
Let's loopk at the CFile
, CBufferDecoder
,
and CAnalyzer
classes individually. The
TranslatorPointer
, BufferTranslator
and BufferFactory
are quite tightly coupled
so we'll look at those together.
CFile
data source base class.
This class and its derivations supply I/O support for SpecTcl.
Early versions of SpecTcl supported ANSI labeled tapes as a
data source in addition to disk files and pipes. This support
was removed since very few if any people want to analyze data
directly from tape.
The base class for I/O support is CFile
and it is not an abstract class but supports disk file I/O.
This class is defined in File.h
Technically it could also be used to read data from tapes that
have been prepositioned to the appropriate data file.
Here is the important part of the definition of this class:
Example 10-1. CFile
class definition
class CFile { CFile (); CFile ( UInt_t am_nFd, bool fixedLength=true); UInt_t getFd() const FileState_t getState() const virtual Int_t Read (Address_t pBuffer, UInt_t nSize) ; virtual Int_t Write (const Address_t pBuffer, UInt_t nBytes) ; virtual void Open (const std::string& rsFilename, UInt_t nAccess, bool fullBlocks = true) ; virtual void Open(UInt_t nFd); virtual void Close () ; virtual Bool_t IsReadable(UInt_t nMs) const; void setVariableRecord(); void setFixedRecord(); };
CFile();
This is the normal constructor. The state of the file
is kfsClosed, indicating the
object is not yet connected to a file. The
Open
method is normally
then used to open a file.
CFile
( UInt_t nFd, bool fixedLength = =true);
Constructs the object in an
kfsOpen state. The
nFd
parameter is an already
file descriptor openon whatever data source the object
will read from or write to.
fixedLength
means that the client will
perform reads of fixed sized blocks of data. This
means, specifically, that the read operations will
block on a read until all requested bytes have been
accepted or until an error or endfile condition
is signaled.
If fixedLength
is false, read
will be done in non-blocking mode. This allows for
partial transfers which can be useful in some
applications.
const UInt_t getFd();
Gets the file descriptor that's bound to the object. This only has meaning if the object's state iss kfsOpen.
const FileState_t getState();
Returns the object state. This can be on of kfsOpen in which case the file can be considered open. It can also be kfsClosed un which case the file should be considered closed.
virtual Int_t Read( Address_t pBuffer, UInt_t nSize);
Attempts to read nSize
bytes
from the file descriptor associated with the
object into pBuffer
.
This throws an error if the object is in the
kfsClosed state.
The number of bytes actually read is returned. Note that the behavior of this method depends on whether or not the object has been told to run in fixed block size mode. In Linux reads may terminate transferring some, all or none of the data.
IF fixed size block mode is enabled and the underlying read returns fewer than the requested number of bytes, additional reads are performed until the requested number of bytes have been received. If fixed sized mode is off, only one read will ever be performed.
All errors are signalled by throwing a
CErrnoException
.
If an end file is reached, the method returns
0.
virtual Int_t Write(const Address_t pBuffer, UInt_t nBytes);
Writes nBytes
of data to the
file descriptor that is associated with this object.
The data comes from the storage pointed to by
pBuffer
. An exception is thrown
if the object is not in the kfsOpen
state. An exception is also thrown if the write fails.
It is possible that the write will transfer fewer bytes
of data than requested. The actual number of bytes
transfered is the method's return value. Unlike
Read
, only one write is ever
performed and the
caller will need to perform additional
transfers, if necessary, until the entire block of
data is written.
virtual void Open (const std::string& rsFilename, UInt_t nAccess, bool fullBlocks = true);
Opens a file, binding its file descriptor into the object. If the state of the object is kfsOpen, the existing file is closed prior to opening the new file.
The file opened is rsFilename
.
The nAccess
parameter is a bit
mask of kacRead (file is readable),
kacWrite (file is writable) and
kacCreate (file can be created if
needed).
If fullBlocks
is true,
reads will be done in blocking mode and however many
reads needed to fill the user buffer will be performed.
If, on the other hand, fullBlocks
is false, reads are done with blocking turned off and
only one read is done to attempt to satisfy a
Read
requests.
On success, the state of the object will be kfsOpen. On failure, an exception will be thrown.
virtual void Open( UInt_t nFd);
Sets the state of the object into
kfsOpen and binds the
nFd
parameter as the object's
file descriptor. If there's already an open file descriptor
it is first closed.
The intent of this method is to allow a file descriptor
opened outside of CFile
to
be used with CFile
objects.
virtual void Close ();
Closes the file descriptor bound to the object and sets the state to kfsClosed. If there is no open file descriptor bound to the object, an exception is thrown..
virtual const Bool_t IsReadable( UInt_t nMs);
Returns kfTRUE if the file descriptor
bound to the object becomes readable within
nMs
milliseconds. If the
object state is kfsClosed,
kfFALSE is returned.
Note that the base class implementation, simply returns
true if the file is open and false if not. This is suitable
for disk files that are always readable. Derived classes
may want to override this, implementing it in terms of
select(2)
.
The CAnalyzer
class.
This class invokes the buffer decoder's
operator()
method and provides
methods the buffer decoder calls back as it decodes
the outer structures of the data.
The sequence, in fact, is that SpecTcl, on determining a data
source can be read reads a block of data from that event
source and passes it to CAnalyzer::OnBuffer
.
The analyzer, in turn passes the buffer to the current
CBufferDecoder
object
which does a high level parse of the buffer calling one or more
CAnalyzer
methods to process each run.
The CBufferDecoder
provides several methods
that can be used by both the analyzer and the event processing
pipeline.
The CAnalyzer
base class is defined in
Analyzer.h. The important parts of this
class definition are shown below:
Example 10-2. CAnalyzer
base class definition
class CAnalyzer { public: CAnalyzer(); CAnalyzer(UInt_t am_nParametersInEvent, UInt_t nThreshold = CAnalyzer::m_nDefaultEventThreshold); public: UInt_t getEventThreshold() const; UInt_t getParametersInEvent() const; CEventList& getEventList(); CBufferDecoder* getDecoder(); CEventSink* getSink(); public: virtual void OnBuffer(UInt_t nBytes, Address_t pData); virtual void OnStateChange(UInt_t nType, CBufferDecoder& rDecoder); virtual void OnPhysics(CBufferDecoder& rDecoder); virtual void OnScaler(CBufferDecoder& rDecoder); virtual void OnOther(UInt_t nType, CBufferDecoder& rDecoder); virtual void OnEndFile(); virtual void OnInitialize(); CBufferDecoder* AttachDecoder(CBufferDecoder& rDecoder); CBufferDecoder* DetachDecoder(); CEventSink* AttachSink(CEventSink& rSink); CEventSink* DetachSink(); void entityNotDone(); };
CAnalyzer();
,
CAnalyzer( UInt_t nParametersInEvent, UInt_t nThreshold = CAnalyzer::m_nDefaultEventThreshold);
These constructors create the base class. Two parameters control when the event sink is run and how events are allocated.
nParametersInEvent
is the number
of parameters that are initially in the
event array like object that is passed to the
event processig pipeline. This gets expanded as needed.
Furthermore, these objects are now recycled, so the
impact of getting this wrong is not nearly what it
used to be.
nThreshold
is the maximum
number of events that will be accumulated from
successive runs of the evet analysis pipeline before
the event sink pipeline is called.
const UInt_t getEventThreshold();
Returns the number of events that need to be accumulated before they are passed through the event sink pipeline.
const UInt_t getParametersInEvent();
Returns the initial size for event objects. Events are dynamically expanded. Furthermore, once used and analyzed, they are invalidated, rather than destroyed and re-used later.
The effect of this is that eventually events grow to the fit the largest numbered parameter used by the analysis case.
CEventList& getEventList();
The analyzer collects all events into an event list (a wrapped vector of events). This event list is then iterated over to send events to the analysis pipeline. This method returns a reference to the event list.
The event list should not be confused with the event pool,
also maintained by the analyzer but not available externally.
The event pool is a CEventList
that contains
invalidated events that may be handed to runs of the
event analysis pipeline. The Analyzer passes events from
the event pool to the analysis pipeline, whereupon they
are put in the event list. The event list is passed,
event by event, to the event sink pipeline whereupon
each event is invalidated and putback in the event pool.
CBufferDecoder* getDecoder();
Returns a pointer to the current buffer decoder. The
buffer decoder is selected by the -format
option of the attach command.
CEventSink* getSink();
Returns a pointer to the analyzer's event sink. In
practice this is actually a pointer to an
CEventSinkPipeline
which, itself,
contains an ordered list of CEventSink
objects including a CHistogrammer
and zero or more CEventFilter
objects.
virtual void OnBuffer( UInt_t nBytes, Address_t pData);
This method is invoked by SpecTcl when a block of data
has been read from the data source.
pData
points to that block and
nBytes
is the number of bytes
actually read.
This base class simply invokes the buffer decoder's
operator()
method which
is expected to break apart the buffer into bits the
analyzer cares about and make callbacks to the analyzer.
Many of the remaining methods are normally called by
the buffer decoder from within its
operator()
virtual void OnStateChange( UInt_t nType, CBufferDecoder& rDecoder);
Called by the buffer decoder to process state change items. State change items are supposed to indicate a change in run state such as begin run. Note that not all data acquisition systems emit state change items. Not all analysis cases need them.
The nType
parameter identifies the
type of state change. For NSCLDAQ, these are
defined in buftypes.h. The
ones that could be passed in to this method in NSCLDAQ
are BEGRUNBF (begin run),
ENDRUNBF (end run),
PAUSBF (pause run),
and RESUMEBF (resume run).
Note to the astute reader that the ring buffer decoder maps the ring item types for state changes into these values in order to retain compatibility with older analysis cases.
rDecoder
references the decoder.
The decoder can be used to fetch key bits of information
about the item the decoder has plucked from the item.
Typically the decoder can now respond to
calls back to
getRun
, getTitle
with the run number and title of this run respectively
(if available).
virtual void OnPhysics( CBufferDecoder& rDecoder);
This method is invoked by the buffer decoder when a
run of physics events has been detected. The
buffer decoder must be prepared to reply to
method calls to
getBody
, getBodySize
and getEntityCount
to allow
OnPhysics
to know the location,
extent
of the data and how many events to expect.
If the event processing pipeline attempts to run the analyzer
off the end of the extent of the data
(indicated by getBodySize
, the analyzer
will throw an exception and abort processing of that chunk.
If the event processing pipeline does not consume all of
the data after analyzing the number of events indicated
by getEntityCount
an error
message is emitted as well.
virtual void OnScaler( CBufferDecoder& rDecoder);
Called by the buffer decoder when a scaler item has been
encountered in the input data. Scaler items are expected
to have a set of counters. The buffer decoder must be
ready to respond to invocations of the
buffer decoder's
getBody
, getBodySize
and getEntityCount
from the
event analysis pipeline as the analyzer makes no assumptions
about the format of the scaler item.
Convention holds that
getBodySize
will give the size
of the scaler item pointed to by getBody
and getEntityCount
returns the
number of scalers in the item.
virtual void OnOther( UInt_t nType, CBufferDecoder& rDecoder);
Called by the buffer decoder for other types of
items. nType
is the type of
data item encountered. This value depends on the
data acquisition system contributing data. Note that
NSCLDAQ's ring buffer decoders map ring items to
values in bftypes.h, buffer types
for the NSCLDAQ-8.x system (prior to ring buffers).
virtual void OnEndFile();
Invoked by the data source, I/O subsystem when an endfile was detected on the data source. For pipe files, this implies the program on the other end of the pipe either exited or closed its stdout. For actual disk file data sources this is invoked whenthe entire file has been read.
virtual void OnInitialize();
Called by the SpecTcl main application object when
SpecTcl initialization is completed. One of the
expectations of this method is that it will invoke
OnInitialize
for all elements
of the event analysis pipeline.
CBufferDecoder* AttachDecoder( CBufferDecoder& rDecoder);
Called by the SpecTcl framework to attach a new buffer
decoder, rDecoder
. A pointer
to the prior decoder is returned. If there is no
prior decoder, a null pointer should be returned.
CBufferDecoder* DetachDecoder();
Detaches the current decoder from the the analyzer. This returns a pointer to the previous decoder. Note that attempting to use the analyzer when no decoder is attached will most likely cause program failure.
CEventSink* AttachSink( CEventSink& rSink);
Attaches a new event sink to the analyzer. Normally,
the event sink is an CEventSinkPipeline
(defined in EventSinkPipeline.h).
CEventSinkPipeline
is a container
for an ordered set of CEventSink
objects (including a CHistogrammer
).
It is rare that you'll need to invoke this method. The return value of this method is a pointer to the prior event sink object or nullptr if there is no prior event sink.
CEventSink* DetachSink();
Detaches the event sink from the analyzer. The result of this method is a pointer to the previously attached event sink. It is likely that using the analyzer when no event sink is attached will cause program failure.
void entityNotDone();
This method can be called from within the event processing
pipeline's operator()
method.
It is called to indicate that when the event processing
pipeline is
complete, while the size of the event should be
used to advance the event pointer, the number of entities
should not be decremented.
The purpose of this method is to simplify event processing for data acquisition systems that have the capability to emit super events. A super event is an event like chunk of data that contains several events. An example of a use of super events would be processing hardware containing multi-event buffers. The devices might be drained responding to a single trigger leaving a structure that contains several physics events.
In cases like that, the event processing pipeline needs to be invoked for each event contained in the super event and therefore needs control over when to report that it has completed processing of the outer entity.
Another alternative is to produce a buffer decoder that treats each super event as a run of events, however that would require that the buffer decoder know enough about the internal structure of the super event to be able to determine the number of events each super event contains.
The CBufferDecoder
class.
Objects of this class are attached to the analyzer. The analyzer,
on receiving a block of data, delegates the outer decode
of that block of data to the buffer decoder. In turn, the
buffer decoder will call back methods of the analyzer
we described above.
Buffer decoders are derived from the abstract base class
CBufferDecoder
. This class is defined in
BufferDecoder.h. Key parts of that header are
shown below.
Example 10-3. The CBufferDecoder
abstract base class
class CBufferDecoder { public: virtual BufferTranslator* getBufferTranslator() virtual const Address_t getBody() = 0; virtual UInt_t getBodySize() = 0; virtual UInt_t getRun() = 0; virtual UInt_t getEntityCount() = 0; virtual UInt_t getSequenceNo() = 0; virtual UInt_t getLamCount() = 0; virtual UInt_t getPatternCount() = 0; virtual UInt_t getBufferType() = 0; virtual void getByteOrder(Short_t& Signature16, Int_t& Signature32) = 0; virtual std::string getTitle() = 0; virtual void operator() (UInt_t nBytes, Address_t pBuffer, CAnalyzer& rAnalyzer); virtual void OnAttach(CAnalyzer& rAnalyzer); virtual void OnDetach(CAnalyzer& rAnalyzer); virtual bool blockMode(); // True if data source must deliver fixed sized blocks. virtual void OnSourceAttach(); virtual void OnSourceDetach(); virtual void OnEndFile(); };
virtual BufferTranslator* getBufferTranslator();
This is expected to return a BufferTranslator
that, in turn, can be used to instantiate
TranslatorPointers
. These
are part of SpecTcl's scheme to insulate programmers
from byte order differences that might exist between
the data source and SpecTcl.
NSCL data acquisition systems provide byte order signatures that allow consumers to determine if these differences exist (though the mechanism differs between NSCLDAQ 8 and earlier and NSCLDAQ 10 and later [there is no NSCLDAQ 9).
If you are processing data from a system that does not
provide these hints, you'll need to use your knoweledge
of the byte order of the source system and probe the
system SpecTcl is running on to produce the proper
BufferTranslator
object.
virtual const = 0 Address_t getBody();
This pure virtual method is expected to return the
body of the item currently being processed. In many
cases the item has a single logical entity (e.g.
information about a state change), however in others,
such as physics event items, the item may contain
more than one entity in which case
getBody
returns a pointer to the
first of these entities.
virtual = 0 UInt_t getBodySize();
Returns the size of the block of data pointed to by the
return value of getBody
. The
analyzer and event processing pipeline should ensure that
processing is confined to the storage defined by
getBody
and
getBodySize
or else the
results are not well defined.
virtual UInt_t getRun();
If the underlying data acquisition system has run numbers that can be gotten from the data (presumably in data towards the start of an event file), this is expected to return the run number of the data set currently being analyzed.
Data acquisition systems may not make it possible to determine the run number from the data. In that case this method should just return some value such as UINT_MAX.
virtual = 0 UInt_t getEntityCount();
Returns the number of entities present in the current item.
For scaler data this is, by convention, the number of
scalers present. For event data this is the number of
events in the block of data defined by
getBody
and
getBodySize
.
virtual UInt_t getSequenceNo();
For data acquisition systems in which it's possible to extract an event number, or buffer number, this is expected to return that number. SpecTcl's GUI's use this information to attempt to provide information about the fraction of events analyzed by the online system.
If the underlying DAQ system does not provide this we recommend returning the number of physics triggers seen. This will result in an analysis fraction of 1.0 which while wrong, at least does not result in program failure (e.g. divide by zero).
NSCLDAQ 8 and earlier provide a buffer sequence number and that is used. NSCLDAQ 10 and later emit periodic trigger count records. The number of triggers in the most recent trigger count record is returne by those buffer decoders.
virtual = 0 UInt_t getLamCount ();
In CAMAC based system that provide masks of the LAMS seen in the crates involved in an event, this provides the number of LAM masks present. This is supplied by NSCLDAQ 8 and earlier but, as non CAMAC systems became the rule rapidly became obsolete.
If your DAQ system is not able to provide this just return 0.
virtual = 0 UInt_t getPatternCount();
In some event formats, Readout is driven by some trigger pattern register. In those systems, this is supposed to return the number of pattern register items each event will have (as the decoding of the event may depend on knowing this).
This value is also increasingly obsolete and, you may retrun 0 if the DAQ system you are using (NSCLDAQ 10.0 and later e.g.) cannot supply this information.
virtual = 0 UInt_t getBufferType();
Returns the type of entity being processed. This should be a value from buftypes.h
If your DAQ system has packed heterogenous data into 'data units', the main job of the buffer decoder is to break each data unit up into one or more homogenously typed item.
virtual = 0 void getByteOrder ( Short_t& Signature16, Int_t& Signature32);
Returns the 16 and 32 bit SpecTcl byte order signatures for the system that sourced the data. SpecTcl uses host and source byte order signatures to not only determine if there is a byte order difference between the two, but to map the transformation required to take data in source format to data in host format.
The 16 bit byte order signature,
Signature16
will be writtenwith the value
0x0102 in the byte order of
the source system. The Signature32
32 bit signature, on the other hand, will be written
with the value 0x01020304 in the
byte order of the source system.
The values provided allow SpecTcl to determine how to transform arbitrary byte order differences between 16 and 32 bit values. In practice, there are two byte orderings in use in computer systems throughout the world. Little endian (predominates because it is what Intel uses), and Big endian.
If we view memory as an array of 8 bit bytes, for multi-byte values, little endian systems store the low order bytes in lower addresses while big endian systems store the high order bytes in lower addresses.
Thus the byte-wise representation of 0x0102034, the 32 bit byte order signature for little endian systems is: 0x04, 0x03, 0x02, 0x01 while on a bit endian system, the same signature's byte-wise values are: 0x01, 0x02, 0x03, 0x04.
If the source system provides some indicatiuon of its byte ordering in the data, this method should transform that indicator into the byte order signatures expected by SpecTcl. If not, you have to use your knowledge of the byte ordering of the source system to construct these signatures out of thin air.
virtual = 0 std::string getTitle();
For data acquisition systems where runs have a textual title that can be extracted from the event data, this method returns this title. If the data acquisition system has no title or it cannot be extracted from the data we recommend returning an empty string.
virtual = 0 void operator()( UInt_t nBytes, Address_t pBuffer, CAnalyzer& rAnalyzer);
Called when a block of data has been read from the
data source. nBytes
is the
number of bytes of data and pBuffer
points to the data block. rAnalyzer
is the analyzer that's orchestrating the data analysis.
This method will typically some methods of this analyzer.
The block of data must be parsed into individual items of specific data types containing individual items as well as blocks of event data. Depending on the data type, the approprite analyzer method(s) must be called.
virtual void OnAttach( CAnalyzer& rAnalyzer);
Called when the buffer decoder is attached to the analyzer.
Potentially this is done each time a data source is
attached. rAnalyzer
is a reference
to the analyzer this buffer decoder is being attached to.
virtual void OnDetach( CAnalyzer& rAnalyzer);
Called to detach the buffer decoder from the analyzer.
virtual bool blockMode();
This should return true if the decoder expects fixed sized blocks and false oterhwsis.
virtual void OnSourceAttach ();
Called when a new event soruce is attached.
virtual void OnSourceDetach();
Called when a data source is deteached from SpecTcl.
virtual void OnEndFile();
Called when an end file is detected on a data source. For file data sources this indicates the reads of the source have run into the end of the file. For pipe data sources this means either that the pipe program has exited or that it has closed its stdout.
Byte order transformations.
SpecTcl provides a set of classes that allow programmers to
instantiate pointer-like objects that can transparenty perform
any required byte order transformations between the source system
and the SpecTcl host system. The following sets of classes are required:
BufferTranslator
encapsulates a buffer of
data and allows fetches to be done from that bufer, returning
data in the byte order of the executing host. The
BufferFactory
class can provide you with
a BufferTranslator
given the 32 bit
byte order signature of the source system.
TranslatorPointer
provides the actual pointer
like object. It encapsulates a BufferTranslator
and uses it to provide pointer like semantics to the caller.
Let's look first at the BufferTranslator
class and its factory before turning our attention to the
pointer-like Translator pointer
.
The BufferTranslator
family of classes
and BufferFactory
class are defined in
BufferTranslator.h. Here are the important
parts of that header:
Example 10-4. BufferTranslator.h important bits:
class BufferTranslator { public: UChar_t GetByte( UInt_t ); Short_t GetWord( UInt_t ); Long_t GetLongword( UInt_t ); Address_t getBuffer(); void newBuffer(Address_t pBuffer); virtual void GetBlock( const Address_t, Int_t, Int_t ) = 0; virtual Long_t TranslateLong(ULong_t value); virtual uint64_t getQuad(uint64_t value) = 0; }; class BufferFactory { public: enum Endian {little, big}; static BufferTranslator* CreateBuffer(Address_t pBuffer, Int_t Signature32); }; BufferFactory::Endian MyEndianess();
BufferTranslator
is a base class for
SwappingBufferTranslator
which must perfrom byte translations, and
NonSwappingBufferTranslator
which
does not need to perform any byte order transformations.
Note that both of those classes construct with a
(protected) buffer which is
managed by this base class.
UChar_t GetByte( UInt_t offset);
Returns the raw byte at the byte offset specified by
offset
into the buffer on which
the object was constructed.
Short_t GetWord( UInt_t offset);
Returns the translated 16 bit item at the
byte offset specified by
offset
. The method
invokes the virtual method
GetBlock
which is implemented
in concrete classes and does the actual byte re-ordering
if needed.
Long_t GetLongword( UInt_t offset);
Returns the translated 32 bit item at the
byte offset specified
by offset
.
The method invokes the virtual method
GetBlock
which is
implemented in concrete classes to do the actual
byte-reordering.
Address_t getBuffer(();
The base class contains a pointer to the storage for the buffer being operated on by concrete classes. This returns a pointer to that buffer.
void newBuffer( Address_t pBuffer);
Replaces the pointer to the buffer operated on by
the object with pBuffer
.
virtual = 0 void GetBlock(const Address_t pItem, Int_t size, Int_t offset);
Gets a block of data at
the byte offset offset from the start of the buffer
into.
pItem
. The
block gotten is size
bytes long.
For swapping translators, the bytes from the buffer are
reversed. This makes this method suitable for
fetching multi-byte objects of 'normal' size like
16 bit, 32, or 64 bit integers.
virtual = 0 Long_t TranslateLong( ULong_t value);
Given an arbitrary 32 bit item, value
,
known to be in the source system byte ordering, returns
it byte translated to the calling host.
virtual = 0 uint64_t getQuad( uint64_t value);
Given the 64 bit value
known to
be in the source byte order representation, returns it
translated into the calling host representation.
The buffer factory has a single method:
static BufferTranslator* CreateBuffer( Address_t pBuffer, Int_t Signature32);
This returns a pointer to a dynamically allocated,
BufferTranslator
for the buffer
pBuffer
from a source system with the
32 bit byte order signature Signature32
.
There is also a utility function:
BufferFactory::Endian MyEndianess();
This returns the endianness of the host. This will be one of the values: BufferFactory::little, or BufferFactory::big.
In addition to creating a buffer decoder we also need to tell the
SpecTcl attach command that the buffer decoder we created shouild
be used for a specific format of data. That is, we need to associate
instances of our buffer decoder class with a specific value of the
-format
option of the attach
comand.
The attach command processor, defined in AttachCommand.h has a static method:
void addDecoderType( std::string type, CDecoderCreator* creator);
This fits the well known pattern of extensible factories that
SpecTcl employs in many other parts of its implementatiuon.
We can register an object that knows how to create out buffer decoder
and associate it with a keyword passed to the
-format
option.
The CDecoderCreator
class is defined as a typedef
in the AttachCommand.h header as well:
typedef CCreator<CBufferDecoder> CDecoderCreator;
CCreator
is a template class defined in
CCreator.h. It is part of the extensible
factory templated subsystem that was developed later in SpecTcl's life.
CCreator
looks like this:
Example 10-5. CCreator
definition
template <class T> class CCreator { public: virtual T* operator()() = 0; virtual std::string describe() const = 0; };
What all htis means is that when operator()
is called for your creator, you must return a pointrer to a new
instance of your decoder class. When describe
is called, you must provide a textual description of your decoder
class.