/* ======================================================================== *\ ! ! * ! * This file is part of MARS, the MAGIC Analysis and Reconstruction ! * Software. It is distributed to you in the hope that it can be a useful ! * and timesaving tool in analysing Data of imaging Cerenkov telescopes. ! * It is distributed WITHOUT ANY WARRANTY. ! * ! * Permission to use, copy, modify and distribute this software and its ! * documentation for any purpose is hereby granted without fee, ! * provided that the above copyright notice appear in all copies and ! * that both that copyright notice and this permission notice appear ! * in supporting documentation. It is provided "as is" without express ! * or implied warranty. ! * ! ! ! Author(s): Thomas Bretz, 12/2000 ! ! Copyright: MAGIC Software Development, 2000-2003 ! ! \* ======================================================================== */ ////////////////////////////////////////////////////////////////////////////// // // MEvtLoop // // This class is the core of each event processing. // First you must set the parameter list to use. The parameter list // must contain the task list (MTaskList) to use. The name of the task // list can be specified if you call Eventloop. The standard name is // "MTaskList". The name you specify must match the name of the MTaskList // object. // // If you call Eventloop() first all PreProcess functions - with the // parameter list as an argument - of the tasks in the task list are // executed. If one of them returns kFALSE then the execution is stopped. // If the preprocessing was ok, The Process function of the tasks are // executed as long as one function returns kSTOP. Only the tasks which // are marked as "All" or with a string which matches the MInputStreamID // of MTaskList are executed. If one tasks returns kCONTINUE the pending // tasks in the list are skipped and the execution in continued with // the first one in the list. // Afterwards the PostProcess functions are executed. // // If you want to display the progress in a gui you can use SetProgressBar // and a TGProgressBar or a MProgressBar. If you set a MStatusDisplay // using SetDisplay, the Progress bar from this display is used. // // You can create a macro from a completely setup eventloop by: // evtloop.MakeMacro("mymacro.C"); // // You will always need to check the macro, it will not run, but it // should have al important information. // // // You can also write all this information to a root file: // TFile file("myfile.root"); // evtloop.Write("MyEvtloopKey"); // // You can afterwards read the information from an open file by: // evtloop.Read("MyEvtloopKey"); // // To lookup the information write it to a file using MakeMacro // ////////////////////////////////////////////////////////////////////////////// #include "MEvtLoop.h" #include // time_t #include // ofstream, SavePrimitive #include // gApplication, TRint::Class() #include // TTime #include // gFile #include // TThread::Self() #include // TDatime #include // gSystem #include #include #include "MLog.h" #include "MLogManip.h" #include "MParList.h" #include "MTaskList.h" #ifdef __MARS__ #include "MRead.h" // for setting progress bar #include "MProgressBar.h" // MProgressBar::GetBar #include "MStatusDisplay.h" // MStatusDisplay::GetBar #endif ClassImp(MEvtLoop); using namespace std; // -------------------------------------------------------------------------- // // default constructor // MEvtLoop::MEvtLoop(const char *name) : fParList(NULL), fProgress(NULL) { fName = name; gROOT->GetListOfCleanups()->Add(this); // To remove fDisplay SetBit(kMustCleanup); *fLog << inf << underline << "Instantiated MEvtLoop (" << name << "), using ROOT v" << ROOTVER << endl; } // -------------------------------------------------------------------------- // // destructor // MEvtLoop::~MEvtLoop() { if (TestBit(kIsOwner) && fParList) delete fParList; } void MEvtLoop::SetParList(MParList *p) { if (!p) return; p->SetBit(kMustCleanup); fParList = p; } // -------------------------------------------------------------------------- // // If the evntloop knows its tasklist search for the task there, // otherwise return NULL. // MTask *MEvtLoop::FindTask(const char *name) const { return fTaskList ? fTaskList->FindTask(name) : NULL; } // -------------------------------------------------------------------------- // // If the evntloop knows its tasklist search for the task there, // otherwise return NULL. // MTask *MEvtLoop::FindTask(const MTask *obj) const { return fTaskList ? fTaskList->FindTask(obj) : NULL; } // -------------------------------------------------------------------------- // // if you set the Eventloop as owner the destructor of the given parameter // list is calles by the destructor of MEvtLoop, otherwise not. // void MEvtLoop::SetOwner(Bool_t enable) { enable ? SetBit(kIsOwner) : ResetBit(kIsOwner); } void MEvtLoop::SetProgressBar(TGProgressBar *bar) { fProgress = bar; if (fProgress) fProgress->SetBit(kMustCleanup); } #ifdef __MARS__ // -------------------------------------------------------------------------- // // Specify an existing MProgressBar object. It will display the progress // graphically. This will make thing about 1-2% slower. // void MEvtLoop::SetProgressBar(MProgressBar *bar) { SetProgressBar(bar->GetBar()); } #endif void MEvtLoop::SetDisplay(MStatusDisplay *d) { MParContainer::SetDisplay(d); if (!d) fProgress=NULL; else { d->SetBit(kMustCleanup); // Get pointer to update Progress bar fProgress = fDisplay->GetBar(); } if (fParList) fParList->SetDisplay(d); } // -------------------------------------------------------------------------- // // The proprocessing part of the eventloop. Be careful, this is // for developers or use in special jobs only! // Bool_t MEvtLoop::PreProcess(const char *tlist) { fTaskList = NULL; // // check if the needed parameter list is set. // if (!fParList) { *fLog << err << dbginf << "Parlist not initialized." << endl; return kFALSE; } // // check for the existance of the specified task list // the default name is "MTaskList" // fTaskList = (MTaskList*)fParList->FindObject(tlist, "MTaskList"); if (!fTaskList) { *fLog << err << dbginf << "Cannot find tasklist '" << tlist << "' in parameter list." << endl; return kFALSE; } if (fLog != &gLog) fParList->SetLogStream(fLog); #ifdef __MARS__ // // Check whether display is still existing // if (fDisplay) { // Lock display to prevent user from deleting it fDisplay->Lock(); // Don't display context menus fDisplay->SetNoContextMenu(); // Set window and icon name fDisplay->SetWindowName(TString("Status Display: ")+fName); fDisplay->SetIconName(fName); // Start automatic update fDisplay->StartUpdate(); // Cascade display through childs fParList->SetDisplay(fDisplay); } #endif // // execute the preprocess of all tasks // connect the different tasks with the right containers in // the parameter list // if (!fTaskList->PreProcess(fParList)) { *fLog << err << "Error detected while PreProcessing." << endl; return kFALSE; } *fLog << endl; return kTRUE; } Bool_t MEvtLoop::ProcessGuiEvents(Int_t num) { if (gROOT->IsBatch()) return kTRUE; // // Check status of display // Bool_t rc = kTRUE; if (fDisplay) switch (fDisplay->CheckStatus()) { case MStatusDisplay::kLoopNone: break; case MStatusDisplay::kLoopStop: rc = kFALSE; fDisplay->ClearStatus(); break; // // If the display is not on the heap (means: not created // with the new operator) the object is deleted somewhere // else in the code. It is the responsibility of the // application which instantiated the object to make // sure that the correct action is taken. This can be // done by calling MStatusDisplay::CheckStatus() // // Because we are synchronous we can safely delete it here! // // Close means: Close the display but leave analysis running // Exit means: Close the display and stop analysis // case MStatusDisplay::kFileClose: case MStatusDisplay::kFileExit: rc = fDisplay->CheckStatus() == MStatusDisplay::kFileClose; if (fDisplay->IsOnHeap()) delete fDisplay; // // This makes the display really disappear physically on // the screen in case of MStatusDisplay::kFileClose // gSystem->ProcessEvents(); return rc; default: *fLog << warn << "MEvtloop: fDisplay->CheckStatus() has returned unknown status #" << fDisplay->CheckStatus() << "... cleared." << endl; fDisplay->ClearStatus(); break; } // // Check System time (don't loose too much time by updating the GUI) // // FIXME: Not thread safe (if you have more than one eventloop running) static Int_t start = num; static TTime t1 = gSystem->Now(); static TTime t2 = t1; // // No update < 20ms // const TTime t0 = gSystem->Now(); if (t0-t1 < (TTime)20) return rc; t1 = t0; // // Update current speed each second // if (fDisplay && t0-t2>(TTime)1000) { const Int_t speed = 1000*(num-start)/(long int)(t0-t2); TString txt = "Processing..."; if (speed>0) { txt += " ("; txt += speed; txt += "Evts/s"; if (fNumEvents>0) { txt += ", est: "; txt += (int)((long int)(t0-t2)*fNumEvents/(60000.*(num-start))); txt += "m"; } //txt += (int)fmod(entries/(1000.*(num-start)/(long int)(t0-t2)), 60); //txt += "s"; txt += ")"; } fDisplay->SetStatusLine1(txt); start = num; t2 = t1; } // // Set new progress bar position // if (fProgress && fNumEvents>0) fProgress->SetPosition((Double_t)num/fNumEvents); // FIXME: This is a workaround, because TApplication::Run is not // thread safe against ProcessEvents. We assume, that if // we are not in the Main-Thread ProcessEvents() is // called by the TApplication Event Loop... if (!TThread::Self()/*gApplication->InheritsFrom(TRint::Class())*/) { // // Handle GUI events (display changes) // #if ROOT_VERSION_CODE < ROOT_VERSION(3,02,06) gSystem->ProcessEvents(); #else if (fDisplay) gSystem->ProcessEvents(); else if (fProgress) gClient->ProcessEventsFor(fProgress); #endif } return rc; } // -------------------------------------------------------------------------- // // The processing part of the eventloop. Be careful, this is // for developers or use in special jobs only! // Int_t MEvtLoop::Process(UInt_t maxcnt) { if (!fTaskList) return kFALSE; // // loop over all events and process all tasks for // each event // *fLog << all <<"Eventloop running ("; if (maxcnt==0) *fLog << "all"; else *fLog << dec << maxcnt; *fLog << " events)..." << flush; UInt_t entries = kMaxUInt; fNumEvents = 0; if (fProgress && !gROOT->IsBatch()) { fProgress->Reset(); fProgress->SetRange(0, 1); #ifdef __MARS__ MRead *read = (MRead*)fTaskList->FindObject("MRead"); if (read && read->GetEntries()>0) entries = read->GetEntries(); #endif if (maxcnt>0) fNumEvents = TMath::Min(maxcnt, entries); else if (entries!=kMaxUInt) fNumEvents = entries; } if (fDisplay) { fDisplay->SetStatusLine1("Processing..."); fDisplay->SetStatusLine2(""); } // // start a stopwatch // TStopwatch clock; clock.Start(); // // This is the MAIN EVENTLOOP which processes the data // if maxcnt==0 the number of processed events is counted // else only maxcnt events are processed // UInt_t numcnts = 0; UInt_t dummy = maxcnt; Int_t rc = kTRUE; if (maxcnt==0) // process first and increment if sucessfull while ((rc=fTaskList->Process())==kTRUE) { numcnts++; if (!ProcessGuiEvents(++dummy)) break; } else // check for number and break if unsuccessfull while (dummy-- && (rc=fTaskList->Process())==kTRUE) { numcnts++; if (!ProcessGuiEvents(maxcnt - dummy)) break; } // // stop stop-watch, print results // clock.Stop(); if (fProgress && !gROOT->IsBatch()) { //fProgress->SetPosition(maxcnt>0 ? TMath::Min(maxcnt, entries) : entries); fProgress->SetPosition(1); // FIXME: This is a workaround, because TApplication::Run is not // thread safe against ProcessEvents. We assume, that if // we are not in the Main-Thread ProcessEvents() is // called by the TApplication Event Loop... if (!TThread::Self()/*gApplication->InheritsFrom(TRint::Class())*/) { #if ROOT_VERSION_CODE < ROOT_VERSION(3,02,06) gSystem->ProcessEvents(); #else gClient->ProcessEventsFor(fDisplay ? fDisplay->GetBar() : fProgress); #endif } } *fLog << all << "Ready!" << endl << endl; *fLog << dec << endl << "CPU - " << "Time: " << clock.CpuTime() << "s" << " for " << numcnts << " Events" << " --> " << numcnts/clock.CpuTime() << " Events/s" << endl; *fLog << "Real - " << "Time: " << clock.RealTime() << "s" << " for " << numcnts << " Events" << " --> " << numcnts/clock.RealTime() << " Events/s" << endl << endl; return rc!=kERROR; } // -------------------------------------------------------------------------- // // The postprocessing part of the eventloop. Be careful, this is // for developers or use in special jobs only! // Bool_t MEvtLoop::PostProcess() const { // // execute the post process of all tasks // return fTaskList ? fTaskList->PostProcess() : kTRUE; } // -------------------------------------------------------------------------- // // See class description above. Returns kTRUE if PreProcessing, // Processing and PostProcessing was successfull, otherwise kFALSE. // maxcnt==0 means: all events // tlist is the name of the task-list to be used. Be carefull, this // feature is not finally implemented - it will only work if no // task will access the tasklist. // Bool_t MEvtLoop::Eventloop(UInt_t maxcnt, const char *tlist) { TDatime d; *fLog << inf << underline << "Eventloop: " << fName << " started at " << d.AsString() << endl; Bool_t rc = PreProcess(tlist); // // If all Tasks were PreProcesses successfully start Processing. // if (rc) rc = Process(maxcnt); // // Now postprocess all tasks. Only successfully preprocessed tasks // are postprocessed. If the Postprocessing of one task fails // return an error. // if (!PostProcess()) { *fLog << err << "Error detected while PostProcessing." << endl; rc = kFALSE; } if (!fDisplay) return rc; // Set status lines fDisplay->SetStatusLine1(fName); fDisplay->SetStatusLine2(rc ? "Done." : "Error!"); // Stop automatic update fDisplay->StopUpdate(); // Reallow context menus fDisplay->SetNoContextMenu(kFALSE); // Reallow user to exit window by File menu fDisplay->UnLock(); // // If postprocessing of all preprocessed tasks was sucefully return rc. // This gives an error in case the preprocessing has failed already. // Otherwise the eventloop is considered: successfully. // return rc; } // -------------------------------------------------------------------------- // // After you setup (or read) an Evtloop you can use MakeMacro() to write // the eventloop setup as a macro. The default name is "evtloop.C". The // default extension is .C If the extension is not given, .C is added. // If the last character in the argument is a '+' the file is not closed. // This is usefull if you have an eventloop which runs three times and // you want to write one macro. If the first character is a '+' no // opening is written, eg: // // MEvtLoop evtloop; // // some setup // evtloop.MakeMacro("mymacro+"); // // replace the tasklist the first time // evtloop.MakeMacro("+mymacro+"); // // replace the tasklist the second time // evtloop.MakeMacro("+mymacro"); // void MEvtLoop::MakeMacro(const char *filename) { TString name(filename); name = name.Strip(TString::kBoth); Bool_t open = kTRUE; Bool_t close = kTRUE; if (name[0]=='+') { open = kFALSE; name.Remove(0, 1); name = name.Strip(TString::kBoth); } if (name[name.Length()-1]=='+') { close = kFALSE; name.Remove(name.Length()-1, 1); name = name.Strip(TString::kBoth); } if (!name.EndsWith(".C")) name += ".C"; ofstream fout; if (!open) { fout.open(name, ios::app); fout << endl; fout << " // ----------------------------------------------------------------------" << endl; fout << endl; } else { fout.open(name); time_t t = time(NULL); fout << "/* ======================================================================== *\\" << endl << "!" << endl << "! *" << endl << "! * This file is part of MARS, the MAGIC Analysis and Reconstruction" << endl << "! * Software. It is distributed to you in the hope that it can be a useful" << endl << "! * and timesaving tool in analysing Data of imaging Cerenkov telescopes." << endl << "! * It is distributed WITHOUT ANY WARRANTY." << endl << "! *" << endl << "! * Permission to use, copy, modify and distribute this software and its" << endl << "! * documentation for any purpose is hereby granted without fee," << endl << "! * provided that the above copyright notice appear in all copies and" << endl << "! * that both that copyright notice and this permission notice appear" << endl << "! * in supporting documentation. It is provided \"as is\" without express" << endl << "! * or implied warranty." << endl << "! *" << endl << "!" << endl << "!" << endl << "! Author(s): Thomas Bretz et al. " << endl << "!" << endl << "! Copyright: MAGIC Software Development, 2000-2002" << endl << "!" << endl << "!" << endl << "\\* ======================================================================== */" << endl << endl << "// ------------------------------------------------------------------------" << endl << "//" << endl << "// This macro was automatically created on" << endl<< "// " << ctime(&t) << "// with the MEvtLoop::MakeMacro tool." << endl << "//" << endl << "// ------------------------------------------------------------------------" << endl << endl << "void " << name(0, name.Length()-2) << "()" << endl << "{" << endl; } SavePrimitive(fout, (TString)"" + (open?"open":"") + (close?"close":"")); if (!close) return; fout << "}" << endl; *fLog << inf << "Macro '" << name << "' written." << endl; } // -------------------------------------------------------------------------- // // Implementation of SavePrimitive. Used to write the call to a constructor // to a macro. In the original root implementation it is used to write // gui elements to a macro-file. // void MEvtLoop::StreamPrimitive(ofstream &out) const { out << " MEvtLoop " << GetUniqueName(); if (fName!="Evtloop") out << "(\"" << fName << "\")"; out << ";" << endl; } // -------------------------------------------------------------------------- // // void MEvtLoop::SavePrimitive(ofstream &out, Option_t *opt) { TString options = opt; options.ToLower(); if (HasDuplicateNames("MEvtLoop::SavePrimitive")) { out << " // !" << endl; out << " // ! WARNING - Your eventloop (MParList, MTaskList, ...) contains more than" << endl; out << " // ! one object (MParContainer, MTask, ...) with the same name. The created macro" << endl; out << " // ! may need manual intervention before it can be used." << endl; out << " // !" << endl; out << endl; } if (!options.Contains("open")) { if (gListOfPrimitives) { *fLog << err << "MEvtLoop::SavePrimitive - Error: old file not closed." << endl; gListOfPrimitives->ForEach(TObject, ResetBit)(BIT(15)); delete gListOfPrimitives; } gListOfPrimitives = new TList; } if (fParList) fParList->SavePrimitive(out); MParContainer::SavePrimitive(out); if (fParList) out << " " << GetUniqueName() << ".SetParList(&" << fParList->GetUniqueName() << ");" << endl; else out << " // fParList empty..." << endl; out << " if (!" << GetUniqueName() << ".Eventloop())" << endl; out << " return;" << endl; if (!options.Contains("close")) return; gListOfPrimitives->ForEach(TObject, ResetBit)(BIT(15)); delete gListOfPrimitives; gListOfPrimitives = 0; } // -------------------------------------------------------------------------- // // Get a list of all conmtainer names which are somehow part of the // eventloop. Chack for duplicate members and print a warning if // duplicates are found. Return kTRUE if duplicates are found, otherwise // kFALSE; // Bool_t MEvtLoop::HasDuplicateNames(TObjArray &arr, const TString txt) const { arr.Sort(); TIter Next(&arr); TObject *obj; TString name; Bool_t found = kFALSE; while ((obj=Next())) { if (name==obj->GetName()) { if (!found) { *fLog << warn << endl; *fLog << " ! WARNING (" << txt << ")" << endl; *fLog << " ! Your eventloop (MParList, MTaskList, ...) contains more than" << endl; *fLog << " ! one object (MParContainer, MTask, ...) with the same name." << endl; *fLog << " ! Creating a macro from it using MEvtLoop::MakeMacro may create" << endl; *fLog << " ! a macro which needs manual intervention before it can be used." << endl; found = kTRUE; } *fLog << " ! Please rename: " << obj->GetName() << endl; } name = obj->GetName(); } return found; } // -------------------------------------------------------------------------- // // Get a list of all conmtainer names which are somehow part of the // eventloop. Chack for duplicate members and print a warning if // duplicates are found. Return kTRUE if duplicates are found, otherwise // kFALSE; // Bool_t MEvtLoop::HasDuplicateNames(const TString txt) const { if (!fParList) return kFALSE; TObjArray list; list.SetOwner(); fParList->GetNames(list); return HasDuplicateNames(list, txt); } // -------------------------------------------------------------------------- // // Reads a saved eventloop from a file. The default name is "Evtloop". // Therefor an open file must exist (See TFile for more information) // // eg: // TFile file("myfile.root", "READ"); // MEvtLoop evtloop; // evtloop.Read(); // evtloop.MakeMacro("mymacro"); // Int_t MEvtLoop::Read(const char *name) { if (!gFile) { *fLog << err << "MEvtloop::Read: No file found. Please create a TFile first." << endl; return 0; } if (!gFile->IsOpen()) { *fLog << err << "MEvtloop::Read: File not open. Please open the TFile first." << endl; return 0; } Int_t n = 0; TObjArray list; n += TObject::Read(name); if (n==0) { *fLog << err << "MEvtloop::Read: No objects read." << endl; return 0; } n += list.Read((TString)name+"_names"); fParList->SetNames(list); HasDuplicateNames(list, "MEvtLoop::Read"); *fLog << inf << "Eventloop '" << name << "' read from file." << endl; return n; } // -------------------------------------------------------------------------- // // If available print the contents of the parameter list. // void MEvtLoop::Print(Option_t *opt) const { if (fParList) fParList->Print(); else *fLog << all << "MEvtloop: No Parameter List available." << endl; } // -------------------------------------------------------------------------- // // Writes a eventloop to a file. The default name is "Evtloop". // Therefor an open file must exist (See TFile for more information) // // eg: // TFile file("myfile.root", "RECREATE"); // MEvtLoop evtloop; // evtloop.Write(); // file.Close(); // Int_t MEvtLoop::Write(const char *name, Int_t option, Int_t bufsize) { if (!gFile) { *fLog << err << "MEvtloop::Write: No file found. Please create a TFile first." << endl; return 0; } if (!gFile->IsOpen()) { *fLog << err << "MEvtloop::Write: File not open. Please open the TFile first." << endl; return 0; } if (!gFile->IsWritable()) { *fLog << err << "MEvtloop::Write: File not writable." << endl; return 0; } Int_t n = 0; TObjArray list; list.SetOwner(); fParList->GetNames(list); n += TObject::Write(name, option, bufsize); if (n==0) { *fLog << err << "MEvtloop::Read: No objects written." << endl; return 0; } n += list.Write((TString)name+"_names", kSingleKey); HasDuplicateNames(list, "MEvtLoop::Write"); *fLog << inf << "Eventloop written to file as " << name << "." << endl; return n; } // -------------------------------------------------------------------------- // // Read the contents/setup of a parameter container/task from a TEnv // instance (steering card/setup file). // The key to search for in the file should be of the syntax: // prefix.vname // While vname is a name which is specific for a single setup date // (variable) of this container and prefix is something like: // evtloopname.name // While name is the name of the containers/tasks in the parlist/tasklist // // eg. Job4.MImgCleanStd.CleaningLevel1: 3.0 // Job4.MImgCleanStd.CleaningLevel2: 2.5 // // If this cannot be found the next step is to search for // MImgCleanStd.CleaningLevel1: 3.0 // And if this doesn't exist, too, we should search for: // CleaningLevel1: 3.0 // // Warning: The programmer is responsible for the names to be unique in // all Mars classes. // Bool_t MEvtLoop::ReadEnv(const TEnv &env, TString prefix, Bool_t print) { if (!prefix.IsNull()) *fLog << warn << "WARNING - Second argument in MEvtLoop::ReadEnv has no meaning... ignored." << endl; prefix = fName; prefix += "."; *fLog << inf << "Reading resources for " << prefix /*TEnv::fRcName << " from " << env.GetRcName()*/ << endl; if (fParList->ReadEnv(env, prefix, print)==kERROR) { *fLog << err << "ERROR - Reading Environment file." << endl; return kFALSE; } return kTRUE; } // -------------------------------------------------------------------------- // // Write the contents/setup of a parameter container/task to a TEnv // instance (steering card/setup file). // The key to search for in the file should be of the syntax: // prefix.vname // While vname is a name which is specific for a single setup date // (variable) of this container and prefix is something like: // evtloopname.name // While name is the name of the containers/tasks in the parlist/tasklist // // eg. Job4.MImgCleanStd.CleaningLevel1: 3.0 // Job4.MImgCleanStd.CleaningLevel2: 2.5 // // If this cannot be found the next step is to search for // MImgCleanStd.CleaningLevel1: 3.0 // And if this doesn't exist, too, we should search for: // CleaningLevel1: 3.0 // // Warning: The programmer is responsible for the names to be unique in // all Mars classes. // Bool_t MEvtLoop::WriteEnv(TEnv &env, TString prefix, Bool_t print) const { if (!prefix.IsNull()) *fLog << warn << "WARNING - Second argument in MEvtLoop::WriteEnv has no meaning... ignored." << endl; prefix = fName; prefix += "."; *fLog << inf << "Writing resources: " << prefix /*TEnv::fRcName << " to " << env.GetRcName()*/ << endl; if (fParList->WriteEnv(env, prefix, print)!=kTRUE) { *fLog << err << "ERROR - Writing Environment file." << endl; return kFALSE; } return kTRUE; } void MEvtLoop::RecursiveRemove(TObject *obj) { if (obj==fParList) { fParList=NULL; fTaskList=NULL; } if (obj==fProgress) fProgress = NULL; if (obj==fDisplay) SetDisplay(NULL); if (obj==fLog) { if (fParList) fParList->SetLogStream(NULL); SetLogStream(NULL); } }