Accessing TCL Variables from Event Processors

This application note describes how to access TCL variables from within SpecTcl event processors. Accessing TCL variables within event processors opens the door on a whole set of powerful applications for SpecTcl event processors. For example, variables can be used to gather statistics about calculations event processors are performing. Even more interesting, TCL Variables can be used to steer the calculations event processors perform. Boolean variables can switch on and off alternative sections of code. Numeric variables can parameterize other sections of code such as calibrations. Once variables have been established to communicate with event processors, Tk can be used to build application specific graphical user interfaces that control these variables.

TCL Variables can be accessed either by invoking API functions, or by linking C/C++ variables to the value of the TCL variable. If you will only be infrequently accessing a TCL variable value, the API approach is sufficient. If, however a variable will be accessed very frequently (for every event in an event processor for example), linking provides a zero overhead access to that variable.

This application note takes the form of a worked out example. The example creates and users four TCL Variables:

Background Information

Background Information

To extend this example, you will need to become familiar with a few SpecTcl header files and the global storage that links the SpecTcl library together into the SpecTcl program. This section:

Global Storage

The global storage area is defined in the header Globals.h. The relevant storage for this discussion is a pointer to SpecTcl's histogramming engine. We will need to create a spectrum and make it known to the histogramming engine, as the histogramming engine also:

SpecTcl's event processing model is a pipeline. Each stage of the pipeline may contain other internal pipelines as well. The overall pipeline consists of the following elements:

The global storage area contains the following:

   
Name Type Meaning
gpUnpacker CEventUnpacker* The head of the event processing pipeline.
gpBufferDecoder CBufferDecoder* The object that is used to do the top level buffer decode
gpEventSinkPipeline CEventSinkPipeline* Head of the event sink pipeline
gpEventSink CEvntSink* Actually a pointer to the histogram engine for historical reasons
gpInterpreter TCLInterpreter* Object wrapping of the TCL interpreter.
gpEventSource CFile* The event source.

there are other variables defined in Globals.h however these are either not yet used or not useful to the application.

For this application, we will need gpInterpreter, a pointer to an object encapsulation of the TCL Interpreter.

Relevant Headers

The following headers are useful for people who are extending the TCL functionality of SpecTcl:

The Event Processor

We will describe the following:

Our strategy will be as follows:

The Header file

The key parts of the SampleProcessor Header file (SampleProcessor.h) are shown below:

 

 #ifndef __SAMPLEPROCESSOR_H
 #define __SAMPLEPROCESSOR_H

 // Headers I really need.

 #ifndef __EVENTPROCESSOR_H
 #include <EventProcessor.h>
 #endif

 #ifndef __HISTOTYPES_H
 #include <histotypes.h>
 #endif

 // Everything else I make a forward definition to minimize dependencies:
 // at compile time:

 class CAnalyzer;
 class CBufferDecoder;
 class CEvent;

 class SampleProcessor : public CEventProcessor
 {
 private:
  int    m_BeginsSeen;		//!< number of begin runs we've seen.
  int    m_nBytesProcessed;	//!< Number bytes of event data processed.
  int    m_nKbytesProcessed;	//!< Kbytes processed.
  double m_fSlope;		//!< Calibration slope.
  double m_fOffset;		//!< Calibration offset.

 public:
  SampleProcessor();
  Bool_t OnAttach(CAnalyzer&     rAnalyzer);
  Bool_t OnBegin(CAnalyzer&      rAnalyzer,
		 CBufferDecoder& rDecoder);

  Bool_t operator()(const Address_t pEvent,
		    CEvent& rEvent,
		    CAnalyzer& rAnalyzer,
		    CBufferDecoder& rDecoder); 
 private:
  void UpdateKbytes();
 };

 #endif

Notes:

The constructor and OnAttach

The constructor for SampleProcessor is shown below.

 // Header files:

 #include <config.h>

 #include "SampleProcessor.h"
 #include <Analyzer.h>
 #include <TCLAnalyzer.h>
 #include <BufferDecoder.h>
 #include <Event.h>

 #include <tcl.h>

 #include <TCLInterpreter.h>
 #include <TCLVariable.h>
 #include <Globals.h>

 #include <stdio.h>
 /*!
   The constructor will just initialize the member variables:
 */

 SampleProcessor::SampleProcessor() :
  m_BeginsSeen(0),
  m_nBytesProcessed(0),
  m_nKbytesProcessed(0),
  m_fSlope(1.0),
  m_fOffset(0.0)
 {
 }

Constructor notes:

OnAttach is run once, and at that time, we know that TCL is all set up and can be manipulated. Here we ensure all variables are created and have proper initial values. We link the linked variables to the TCL Variables as follows:

Note that all TCL Variables are strings... except for linked ones. Note as well that destroying a CTCLVariable object does not unset the undelying variable.

 Bool_t
 SampleProcessor::OnAttach(CAnalyzer& rAnalyzer)
 {
  // This next step of finding the interpreter will be deprecated when
  // SpecTcl 3.0 is comissioned.

  CTCLInterpreter* pInterp = gpInterpreter;

  // Now create object encapsulations of the variables we care about
  // and set them to initial values:

  CTCLVariable BeginsSeen(pInterp, "BeginsSeen", kfFALSE);
  BeginsSeen.Set("0");
  
  CTCLVariable slope(pInterp, "slope", kfFALSE);
  slope.Set("1.0");

  CTCLVariable offset(pInterp, "offset", kfFALSE);
  offset.Set("0.0");

  CTCLVariable KbytesProcessed(pInterp, "KbytesProcessed", kfFALSE);
  KbytesProcessed.Set(0);

  // Link the variables to member data:

  slope.Link(&m_fSlope, TCL_LINK_DOUBLE);
  offset.Link(&m_fOffset, TCL_LINK_DOUBLE);

  KbytesProcessed.Link(&m_nKbytesProcessed, TCL_LINK_INT | TCL_LINK_READ_ONLY);

  return kfTRUE;
 }

Notes:

OnBegin and utility functions.

On Begin is going to get the value of BeginsSeen, parse it as an integer, increment it and Set it. We could just set the value of m_BeginsSeen, but

Error cases:

 Bool_t 
 SampleProcessor::OnBegin(CAnalyzer& rAnalyzer, CBufferDecoder& rDecoder)
 {
  CTCLInterpreter* pInterp = gpInterpreter;

  CTCLVariable BeginsSeen(pInterp, "BeginsSeen", kfFALSE);
  const char*       pValue = BeginsSeen.Get();
  if(pValue) {			// The variable exists...
    int nCurrent;
    if(sscanf(pValue, "%d", &nCurrent)) { // It's an int.. override.
      m_BeginsSeen = nCurrent;
    }
    
  }
  // If we get to here we need to increment the value of m_BeginsSeen and
  // Set a new variable value.

  m_BeginsSeen++;
  char textVersion[100];
  sprintf(textVersion, "%d", m_BeginsSeen);
  BeginsSeen.Set(textVersion);

  // While we're at it we need to reset m_nKbytesProcessed and
  // trigger an update on it

  m_nBytesProcessed = 0;
  m_nKbytesProcessed = 0;
  UpdateKbytes();
 

  return kfTRUE;
 }

Notes:

This utility does the common work of ensuring that traces set on KbytesProcessed will fire: doing this requires a call to Tcl_UpdateLinkedVar which is not yet wrapped by the TCL++ library.

void SampleProcessor::UpdateKbytes() {

  CTCLInterpreter* pInterp = gpInterpreter;
  Tcl_Interp*      pI      = pInterp->getInterpreter(); // This next function is not
  Tcl_UpdateLinkedVar(pI,	                       // yet wrapped. 
		       "KbytesProcessed");

}

Note:

operator()

Several assumptions:

Bool_t SampleProcessor::operator()(const Address_t pEvent,

			    CEvent& rEvent,
			    CAnalyzer& rAnalyzer,
			    CBufferDecoder& rDecoder)
{
  UShort_t* p    = static_cast<UShort_t*>(pEvent);

  UShort_t nSize = *p++ * sizeof(UShort_t);
  UShort_t Value = *p++;

  CTclAnalyzer& rA(static_cast<CTclAnalyzer&>(rAnalyzer));
  rA.SetEventSize(nSize);

  // Now the TCL fun begins: rEvent[0] - raw parameter.
  //                         rEvent[1] - Calibrated parameter:
  // m_fSlope and m_fOffset are maintained by TCL to reflect
  // the values of the slope/offset variables.
  //

  rEvent[0] = Value;
  rEvent[1] = static_cast<float>(Value)*m_fSlope + m_fOffset; 

  // report the statistics.  Each time a k clicks over, 
  // we increment the m_nKbytesProcessed and update the linked var.

  m_nBytesProcessed += nSize;
  if( (m_nBytesProcessed / 1024) != m_nKbytesProcessed) {
    m_nKbytesProcessed = m_nBytesProcessed / 1024;
    UpdateKbytes();
    
  }
 }

Notes:

  label .l -textvariable kBytes
  # ... code to layout the label in the GUI omitted.
proc UpdatekBytes {ms} { global KbytesProcessed global kBytes

       set kBytes $KbytesProcessed
       after $ms "UpdatekBytes $ms"
  }  
  UpdatekBytes 1000