Chapter 10. Interfacing SpecTcl with data from other data acquisition systems.

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:

Figure 10-1. SpecTcl event decode process

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:

10.1. The classes involved in getting data from source to analyzer

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.