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
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 :
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.
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.
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).
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.
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; } } }
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.