Object Fillers

Introduction

The objective of this project is twofold. First to break some unneeded dependencies between some of our phenix libraries, and second to allow ezdst to read a uDST as if it was a nanoDST (or a DST as if it was a uDST) while trying to use as much as possible the same code as the one used for nanoDSTs (uDSTs) production.

I will explain the flaws of the current design (which partly supports reading (u)DST as a (nano)uDST), and then a solution to solve them.

I would appreciate a rather fast feedback so this can eventually be made available soon to all users.

Presentation of the problem
    Problem recap

A solution
    The new players
    The object filler registry
    The node maker
    The new game
    Some details
    Plan for integration


Presentation of the problem

What we are basically after is how does ezdst can give you access to objects that are not on the xDST you're reading. For example, say you read a uDST but wants to treat it as a nanoDST, e.g. have a code like the following in which you ask for object PHPhoton (nanoDST object) whereas the uDST only contains EmcClusterLocalExt object.

int process_event(DstContent* dst)
{
  PHPhoton* photons = 
     static_cast(dst->getClass("PHPhoton"));
  return 0;
}

There's of course no magic, so there's some code behind the scene that do convert a EmcClusterLocalExt into a PHPhoton. It's presently coded like this :

int DstNewNodes::copyData(PHCompositeNode *topNode)
{
  PHNodeIterator iter(topNode);
  PHCompositeNode *dstNode;
  dstNode = static_cast
     (iter.findFirst("PHCompositeNode", "DST"));

  vector::iterator newnodeiter;

  for (newnodeiter = newnode.begin();newnodeiter != newnode.end(); 
       newnodeiter++)
    {
      
      if (!newnodeiter->compare("CglTrackBack"))
        {
          copyCglTrack(dstNode, "CglTrackBack");
        }
      else if (!newnodeiter->compare("EmcCalibTower"))
        {
          copyEmcCalibTower(dstNode);
        }
// etc...
       else if (!newnodeiter->compare("PHPhoton"))
        {
          copyPHPhoton(dstNode);
        }
}

where DstNewNodes::copyData is called for you by ezdst for each event before getting into the process_event function. Each copyXXX function is defined in a separate source file DstNewNodesXXX.cc. (Adding support for a new object means changing one source file (the else if above) and adding a new one. That's alone could be a problem, but I would consider it as minor for the moment).

There's no point about the fact that the code within copyXXX has to be present somehow. The point is that currently this code is a mere duplicate of what is used to produce the nanoDST objects (located in /offline/packages/ndst). After all, ezdst or not ezdst, we do have code to convert uDST objects to nanoDST as we're able to produce nanoDST, aren't we ? Why not reuse this code for ezdst ? (well, we can not readily reuse it, as I'll explain below). But even letting this aside, there's another problem. Here's how the conversion is done for the moment :

void DstNewNodes::copyPHPhoton(PHCompositeNode *dstNode)
{
  PHPhoton* photon = grab pointer from dstNode;
 
  photon->Copy(dstNode);
}

int PHPhoton::Copy(PHCompositeNode *topNode)
{
  EmcClusterLocalExt* clus = grab pointer from topNode

  // convert clus to this.
}

This is fine in the way it does the required job but it's bad in the sense it makes PHPhoton (intended to be a nanoDST object) dependent on a uDST object (and it turn makes the nanoDST library dependent on the uDST one, which is itself dependent on ...). The same kind of remark applies to how STAF tables are converted into uDST object (by means of uDSTObject::FillFromWrapper(dStafTable*) methods)

So, to recap, I'd say the present ezdst design has three flaws :

  1. It produces unnecessary dependencies between DST/uDST/nanoDST libraries (major)
  2. It does not really reuse code (major)
  3. It does require source code change within ezdst framework itself to add support for new objects (minor)

A solution

The three flaws above can be alleviated by a) making ezdst core code independant of concrete classes (solves point 1 and 2) b) slightly modifying the way we create uDST so its code can be reused by ezdst.

Presentation of the new players

OK, here's the scheme. Let's start from the very fact that both nanodst code and ezdst need to convert udst objects into nanodst ones (and that both kinds of objects are PHObjects). Let's continue with the fact that this conversion has a very generic algorithm:

udstObject* object = grab udst object from topNode;
nanoDSTObject* particle = new nanoDSTObject();
loop over (object items)
{
  fill particle from object;
}

Algorithm which can well be wrapped into an abstract base class PHObjectFiller:

class PHObjectFiller 
{
public:
  virtual bool fill(PHCompositeNode* topNode, PHObject& destination) = 0;
};

The topNode is needed to grab the "source" object, whereas the destination is the object you'd like to create. Have a look at how the various subsystem functions are coded in /offline/framework/ndst and you'll easily get convinced how easily those functions can be converted into PHObjectFillers.

That's the basic idea. Now some add-ons are needed. First, we do not want ezdst to instantiate concrete objectFillers, otherwise we'll fall back into the dependency problem. So ezdst should only deal with a generic class. But how can ezdst know which filler fills which object ? Well, let the filler itself advertise why kind of conversion(s) it can perform, and make a new static object that would take ezdst request and delegate the work to the proper filler.

class PHObjectFiller
{
public:

  // do the actual filling
  virtual bool fill(PHCompositeNode* topNode, PHObject& destination) = 0;

  // advertise which kind of object we can fill.
  virtual bool canFill(PHObject&) const = 0;
};

class PHObjectFillerManager
{
public:
  static bool fill(PHCompositeNode* topNode, PHObject& destination);
};

Then, assuming ezdst has a pointer optr to a PHObject, here is how it could easily fill it, while depending only on two classes (PHObjectFillerManager and PHObject):

  PHObjectFillerManager::fill(topNode,*optr);

A typical object filler canFill method would be:

bool concreteObjectFiller::canFill(PHObject& object)
{
  concreteClass* c = dynamic_cast(&object);
  if ( c ) 
  { 
    return true;
  } 
  else
  {
    return false;
  }
}

So far so good. Still, how will the ObjectFillerManager know about the concrete fillers, and how will ezdst get this object pointer without performing a new concreteClass() ? Answers: get an object filler registry PHObjectFillerRegistry; look again at the current ezdst/ndst codes and grasp the commonalities into a class, PHDstNodeMaker.

The object filler registry

This static class will have only one goal : manage an internal list of PHObjectFillers, and select the right one to do a specific conversion. The PHObjectFiller base class will provide a default constructor (to be called from derived class) that will make the registry aware of any object filler newly created.

class PHObjectFillerRegistry
{

public:

  // add an object filler to the registry
  static void add(PHObjectFiller*);

  // remove an object filler from the registry
  static void remove(PHObjectFiller*);

  // return the filler able to go from compositenode to object
  static PHObjectFiller* findFiller(PHCompositeNode*, PHObject&);

  // return the filler which is a priori able to *handle* object
  static PHObjectFiller* findFiller(PHObject&);
};

class PHObjectFiller
{
public:
  PHObjectFiller() { PHObjectFillerRegistry::add(this); }
 
  ...
};

The PHObjectFillerManager will simply have to use the registry to delegate a given conversion to the right filler. The registry itself can be filled either statically (i.e. somewhere in your code you do create a new PHObjectFiller object which will automatically -through the base class ctor- register itself) or dynamically (if one of your library does contain a concrete static PHObjectFiller child, as soon as you load this library, the registry will know the filler).

The node maker

offline/framework/udst subsystem functions basically boil down to:

int init_XXX(PHCompositeNode* udstNode)
{
  // create concrete class of udst object(s)
  XXXOut* udstObject = new XXXOutv1();

  // and put it into udst node
  PHIODataNode *XXXNode = 
    new PHIODataNode(udstObject,"XXXName","PHObject");  
  udstNode->addNode(XXXNode);
}

int process_XXX(PHCompositeNode* topNode)
{
  // grap pointer to needed DST object(s)
  XXXDSTObject* dstObject = ...

  // grap pointer to needed udst object(s)
  XXXOut* udstObject = ...

  // code to convert dstObject into out
  ...
}

The last part of process_XXX is the actual filling of the destination object, i.e. the thing PHObjectFiller is meant for. All the remaining is only node management. So let's wrap this into a single object:

class PHDstNodeMaker
{
public:

  // Make a node named "nodeName" under "topNode", contaning an object
  // of class "classname" (assumed to have a default ctor).
  // This is the work currently performed by init_XXX()
  PHIODataNode* 
  makeNode(PHCompositeNode* topNode, const char* nodeName, 
	const char* classname);

  // copy topNode information into the new nodes we created above
  void copy(PHCompositeNode* topNode);
};

so we can rewrite the udst functions as:


static PHDstNodeMaker nodeMaker;

int init_XXX(PHCompositeNode* udstNode)
{
  nodeMaker.makeNode(udstNode,"XXXNodeName","XXXObjectClassv2");
  nodeMaker.makeNode(udstNode,"XXXAnotherNodeName","XXXObjectAnotherClassv5");
}

int process_XXX(PHCompositeNode* topNode)
{
  nodeMaker.copy(topNode);
  // this will result into 2 PHObjectFillers to be called with topNode
  // and the objects in nodes XXXNodeName and XXXAnotherNodeName
}

That's it! One can imagine to have one PHDstNodeMaker per subsystem or only one for all, it's a matter of choice only.

The new game

ezdst can then use the very same code (i.e. the ObjectFillers) that is defined for udst or ndst. The only thing one would have to do is to first make use of the ezaddnode function (btw, this step is only needed if you want to override the defaults specifying which concrete class to build for a given new node, defaults which should remain the same as in the current ezdst).

root -b
root[0] gSystem->Load("libyourliblinkedagainstezdst.so");
root[1] ezaddnode("EmcClusterLocalExt","EmcClusterLocalExtMicrov4");

The ezaddnode function will instruct ezdst's internal PHDstNodeMaker that if the user wants to get access to node EmcClusterLocalExt, then this node will contain a EmcClusterLocalExtMicro object, version 4.

The DstNewNodesXXX classes are simply removed and replaced by a call to ezdst's internal PHDstNodeMaker::copy(topNode) for each event:

void DstContent::getData(PHCompositeNode* topNode)
{
  // new line of code
  // only copy nodes that have been requested at least once (through
  // getClass()

  nodeMaker->copy(topNode);

  // as before...
}

void* DstContent::getClass(string classname)
{
  // search for a given class in the map, return NULL if not found
  map::const_iterator classiter = classmap.find(classname);
  if (classiter != classmap.end())
    {
      return classmap[classname];
    }
  else
    {
      // this replaces the old code based on DstNewNodes

      cout << "DstContent::getClass : delegating to DstNewNodeMaker"
	   << endl;

      void* object = nodeMaker->getClass(topNode,classname.c_str());

      if (object) 
	{
	  return object;
	}
      else
	{
	  cerr << PHWHERE << " could not get " << classname << endl;
	  return 0;
	}
    }
}

Some more details/questions

Plan for integration

OK. I've put some effort in this, and I'd of course like to get an approval to move on, that is, to commit such changes. Here's the proposal on how to proceed, step by step (waiting e.g. a successfull rebuild between steps if necessary). I would of course welcome and appreciate any help in this.

  1. Commit 4 new classes in offline/framework/phool : PHObjectFiller, PHObjectFillerManager, PHObjectFillerRegistry, PHDstNodeMaker. This is kind of a safe and standalone step as those 4 classes only depend on phool classes.
  2. Create object fillers. That's probably the biggest job. One has to start somewhere.
    1. I would propose to first write fillers to go from STAF tables to (u)DST objects. This might easily be the first test of a STAF table free analysis (and would also serve as a backward compatibility check, i.e. once we have decided not to write STAF tables anylonger, can we still read back old DSTs ? at least during the interim where both STAF tables and PHObject based object will be there).
    2. Then adapt udst framework to use DST->uDST fillers.
    3. Finally adapt ndst framework to use uDST->nanoDST fillers.
  3. Modify ezdst to use PHDstNodeMaker instead of DstNewNodes. This step requires only part of previous step to be performed if one decides to first write the fillers needed to provide the current ezdst functionality (i.e. fillers for object CglTrack, CglTrackBack, EmcCalibTower, EmcClusterLocalExt, PHPhoton, Pc1,2,3Cluster, PHDchTrackOut, TofOut and PHGlobal).
  4. Actually scatter the fillers into relevant libraries to alleviate the library dependency problem.

Go Home

Laurent APHECETCHE
Last modified: Tue Oct 15 18:28:06 CEST 2002