Skip to content
Snippets Groups Projects
  • Administrator's avatar
    66f89a15
    Add several compiler warnings · 66f89a15
    Administrator authored
    Use struct instead of class for forward declarations if the object is defined
    as struct.
    Remove unused data members, variables and parameters.
    Remove an explicite usage of std::move to allow copy ellision.
    Correct type in printf statements.
    Add missing virtual destructor in some class.
    Fix two issues where closing braces were at the wrong place.
    66f89a15
    History
    Add several compiler warnings
    Administrator authored
    Use struct instead of class for forward declarations if the object is defined
    as struct.
    Remove unused data members, variables and parameters.
    Remove an explicite usage of std::move to allow copy ellision.
    Correct type in printf statements.
    Add missing virtual destructor in some class.
    Fix two issues where closing braces were at the wrong place.
CbmDeviceDigiEventSink.cxx 47.78 KiB
/* Copyright (C) 2020-2021 Facility for Antiproton and Ion Research in Europe, Darmstadt
   SPDX-License-Identifier: GPL-3.0-only
   Authors: Pierre-Alain Loizeau [committer] */

/**
 * CbmDeviceDigiEventSink.cxx
 *
 * @since 2020-05-24
 * @author P.-A. Loizeau
 */

#include "CbmDeviceDigiEventSink.h"


/// CBM headers
#include "CbmEvent.h"
#include "CbmFlesCanvasTools.h"
#include "CbmMQDefs.h"

#include "TimesliceMetaData.h"

/// FAIRROOT headers
#include "FairMQLogger.h"
#include "FairMQProgOptions.h"  // device->fConfig
#include "FairParGenericSet.h"
#include "FairRootFileSink.h"
#include "FairRootManager.h"
#include "FairRunOnline.h"

#include "BoostSerializer.h"

#include "RootSerializer.h"

/// FAIRSOFT headers (geant, boost, ...)
#include "TCanvas.h"
#include "TFile.h"
#include "TH1.h"
#include "TList.h"
#include "TNamed.h"
#include "TProfile.h"

#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/utility.hpp>

/// C/C++ headers
#include <thread>  // this_thread::sleep_for

#include <array>
#include <iomanip>
#include <stdexcept>
#include <string>
struct InitTaskError : std::runtime_error {
  using std::runtime_error::runtime_error;
};

using namespace std;

//Bool_t bMcbm2018MonitorTaskT0ResetHistos = kFALSE;

CbmDeviceDigiEventSink::CbmDeviceDigiEventSink() {}

void CbmDeviceDigiEventSink::InitTask()
try {
  /// Read options from executable
  LOG(info) << "Init options for CbmDeviceDigiEventSink.";

  fbStoreFullTs    = fConfig->GetValue<bool>("StoreFullTs");
  fsOutputFileName = fConfig->GetValue<std::string>("OutFileName");

  fsChannelNameDataInput = fConfig->GetValue<std::string>("EvtNameIn");
  fsAllowedChannels[0]   = fsChannelNameDataInput;

  fbBypassConsecutiveTs = fConfig->GetValue<bool>("BypassConsecutiveTs");
  fbWriteMissingTs      = fConfig->GetValue<bool>("WriteMissingTs");
  fbDisableCompression  = fConfig->GetValue<bool>("DisableCompression");
  fiTreeFileMaxSize     = fConfig->GetValue<int64_t>("TreeFileMaxSize");
  fbDigiEventInput      = fConfig->GetValue<bool>("DigiEventInput");

  fbFillHistos             = fConfig->GetValue<bool>("FillHistos");
  fuPublishFreqTs          = fConfig->GetValue<uint32_t>("PubFreqTs");
  fdMinPublishTime         = fConfig->GetValue<double_t>("PubTimeMin");
  fdMaxPublishTime         = fConfig->GetValue<double_t>("PubTimeMax");
  fsHistosSuffix           = fConfig->GetValue<std::string>("HistosSuffix");
  fsChannelNameHistosInput = fConfig->GetValue<std::string>("ChNameIn");

  /// Associate the MissedTs Channel to the corresponding handler
  OnData(fsChannelNameMissedTs, &CbmDeviceDigiEventSink::HandleMissTsData);

  /// Associate the command Channel to the corresponding handler
  OnData(fsChannelNameCommands, &CbmDeviceDigiEventSink::HandleCommand);

  /// Associate the Event + Unp data Channel to the corresponding handler
  // Get the information about created channels from the device
  // Check if the defined channels from the topology (by name)
  // are in the list of channels which are possible/allowed
  // for the device
  // The idea is to check at initilization if the devices are
  // properly connected. For the time beeing this is done with a
  // nameing convention. It is not avoided that someone sends other
  // data on this channel.
  //logger::SetLogLevel("INFO");
  int noChannel = fChannels.size();
  LOG(info) << "Number of defined channels: " << noChannel;
  for (auto const& entry : fChannels) {
    LOG(info) << "Channel name: " << entry.first;
    if (std::string::npos != entry.first.find(fsChannelNameDataInput)) {
      if (!IsChannelNameAllowed(entry.first)) throw InitTaskError("Channel name does not match.");
      OnData(entry.first, &CbmDeviceDigiEventSink::HandleData);
    }  // if( entry.first.find( "ts" )
  }    // for( auto const &entry : fChannels )

  //   InitContainers();

  /// Prepare storage TClonesArrays
  /// TS MetaData storage
  fTimeSliceMetaDataArray = new TClonesArray("TimesliceMetaData", 1);
  if (NULL == fTimeSliceMetaDataArray) {
    throw InitTaskError("Failed creating the TS meta data TClonesarray ");
  }  // if( NULL == fTimeSliceMetaDataArray )
     /// Events storage
  /// TODO: remove TObject from CbmEvent and switch to vectors!
  fEventsSel = new std::vector<CbmDigiEvent>();

  /// Prepare root output
  if ("" != fsOutputFileName) {
    fpRun         = new FairRunOnline();
    FairRootFileSink* pSink = new FairRootFileSink(fsOutputFileName);
    fpFairRootMgr = FairRootManager::Instance();
    fpFairRootMgr->SetSink(pSink);
    if (nullptr == fpFairRootMgr->GetOutFile()) {
      throw InitTaskError("Could not open root file");
    }  // if( nullptr == fpFairRootMgr->GetOutFile() )
    if (fbDisableCompression) {
      /// Completely disable the root file compression
      pSink->GetRootFile()->SetCompressionLevel(0);
    }
    /// Set global size limit for all TTree in this process/Root instance
    TTree::SetMaxTreeSize(fiTreeFileMaxSize);
  }    // if( "" != fsOutputFileName )
  else {
    throw InitTaskError("Empty output filename!");
  }  // else of if( "" != fsOutputFileName )

  LOG(info) << "Init Root Output to " << fsOutputFileName;

  fpFairRootMgr->InitSink();
  fEvtHeader = new CbmTsEventHeader();
  fpFairRootMgr->Register("EventHeader.", "Event", fEvtHeader, kTRUE);

  /// Register all input data members with the FairRoot manager
  /// TS MetaData
  fpFairRootMgr->Register("TimesliceMetaData", "TS Meta Data", fTimeSliceMetaDataArray, kTRUE);
  /// CbmEvent
  fpFairRootMgr->RegisterAny("DigiEvent", fEventsSel, kTRUE);

  /// Full TS Digis storage (optional usage, controlled by fbStoreFullTs!)
  if (fbStoreFullTs) {
    fvDigiT0   = new std::vector<CbmTofDigi>();
    fvDigiSts  = new std::vector<CbmStsDigi>();
    fvDigiMuch = new std::vector<CbmMuchDigi>();
    fvDigiTrd  = new std::vector<CbmTrdDigi>();
    fvDigiTof  = new std::vector<CbmTofDigi>();
    fvDigiRich = new std::vector<CbmRichDigi>();
    fvDigiPsd  = new std::vector<CbmPsdDigi>();

    fpFairRootMgr->RegisterAny("T0Digi", fvDigiT0, kTRUE);
    fpFairRootMgr->RegisterAny("StsDigi", fvDigiSts, kTRUE);
    fpFairRootMgr->RegisterAny("MuchDigi", fvDigiMuch, kTRUE);
    fpFairRootMgr->RegisterAny("TrdDigi", fvDigiTrd, kTRUE);
    fpFairRootMgr->RegisterAny("TofDigi", fvDigiTof, kTRUE);
    fpFairRootMgr->RegisterAny("RichDigi", fvDigiRich, kTRUE);
    fpFairRootMgr->RegisterAny("PsdDigi", fvDigiPsd, kTRUE);
  }

  fpFairRootMgr->WriteFolder();

  LOG(info) << "Initialized outTree with rootMgr at " << fpFairRootMgr;

  /// Histograms management
  if (kTRUE == fbFillHistos) {
    /// Comment to prevent clang format single lining
    if (kFALSE == InitHistograms()) { throw InitTaskError("Failed to initialize the histograms."); }
  }  // if( kTRUE == fbFillHistos )
  fbInitDone = true;
}
catch (InitTaskError& e) {
  LOG(error) << e.what();
  // Wrapper defined in CbmMQDefs.h to support different FairMQ versions
  cbm::mq::ChangeState(this, cbm::mq::Transition::ErrorFound);
}

bool CbmDeviceDigiEventSink::IsChannelNameAllowed(std::string channelName)
{
  for (auto const& entry : fsAllowedChannels) {
    std::size_t pos1 = channelName.find(entry);
    if (pos1 != std::string::npos) {
      const vector<std::string>::const_iterator pos =
        std::find(fsAllowedChannels.begin(), fsAllowedChannels.end(), entry);
      const vector<std::string>::size_type idx = pos - fsAllowedChannels.begin();
      LOG(info) << "Found " << entry << " in " << channelName;
      LOG(info) << "Channel name " << channelName << " found in list of allowed channel names at position " << idx;
      return true;
    }  // if (pos1!=std::string::npos)
  }    // for(auto const &entry : fsAllowedChannels)
  LOG(info) << "Channel name " << channelName << " not found in list of allowed channel names.";
  LOG(error) << "Stop device.";
  return false;
}

bool CbmDeviceDigiEventSink::InitHistograms()
{
  /// Histos creation and obtain pointer on them
  /// Trigger histo creation, filling vHistos and vCanvases
  // bool initOK =CreateHistograms();
  bool initOK = true;

  /// Obtain vector of pointers on each histo from the algo (+ optionally desired folder) or create them locally
  // ALGO: std::vector<std::pair<TNamed*, std::string>> vHistos = fMonitorAlgo->GetHistoVector();
  std::vector<std::pair<TNamed*, std::string>> vHistos = {};

  /* clang-format off */
  fhFullTsBuffSizeEvo = new TProfile(Form("hFullTsBuffSizeEvo%s", fsHistosSuffix.data()),
                                     "Evo. of the full TS buffer size; Time in run [s]; Size []",
                                     720, 0, 7200);
  fhMissTsBuffSizeEvo = new TProfile(Form("hMissTsBuffSizeEvo%s", fsHistosSuffix.data()),
                                     "Evo. of the missed TS buffer size; Time in run [s]; Size []",
                                     720, 0, 7200);
  fhFullTsProcEvo  = new TH1I(Form("hFullTsProcEvo%s", fsHistosSuffix.data()),
                              "Processed full TS; Time in run [s]; # []",
                              720, 0, 7200);
  fhMissTsProcEvo  = new TH1I(Form("hMissTsProcEvo%s", fsHistosSuffix.data()),
                              "Processed missing TS; Time in run [s]; # []",
                              720, 0, 7200);
  fhTotalTsProcEvo = new TH1I(Form("hTotalTsProcEvo%s", fsHistosSuffix.data()),
                              "Total processed TS; Time in run [s]; # []",
                              720, 0, 7200);
  fhTotalEventsEvo = new TH1I(Form("hTotalEventsEvo%s", fsHistosSuffix.data()),
                              "Processed events; Time in run [s]; # []",
                              720, 0, 7200);
  /* clang-format on */

  std::string sFolder = std::string("EvtSink") + fsHistosSuffix;
  vHistos.push_back(std::pair<TNamed*, std::string>(fhFullTsBuffSizeEvo, sFolder));
  vHistos.push_back(std::pair<TNamed*, std::string>(fhMissTsBuffSizeEvo, sFolder));
  vHistos.push_back(std::pair<TNamed*, std::string>(fhFullTsProcEvo, sFolder));
  vHistos.push_back(std::pair<TNamed*, std::string>(fhMissTsProcEvo, sFolder));
  vHistos.push_back(std::pair<TNamed*, std::string>(fhTotalTsProcEvo, sFolder));
  vHistos.push_back(std::pair<TNamed*, std::string>(fhTotalEventsEvo, sFolder));

  /// Obtain vector of pointers on each canvas from the algo (+ optionally desired folder) or create them locally
  // ALGO: std::vector<std::pair<TCanvas*, std::string>> vCanvases = fMonitorAlgo->GetCanvasVector();
  std::vector<std::pair<TCanvas*, std::string>> vCanvases = {};

  fcEventSinkAllHist = new TCanvas(Form("cEventSinkAllHist%s", fsHistosSuffix.data()), "Event Sink Monitoring");
  fcEventSinkAllHist->Divide(3, 2);

  fcEventSinkAllHist->cd(1);
  gPad->SetGridx();
  gPad->SetGridy();
  fhFullTsBuffSizeEvo->Draw("hist");

  fcEventSinkAllHist->cd(2);
  gPad->SetGridx();
  gPad->SetGridy();
  fhMissTsBuffSizeEvo->Draw("hist");

  fcEventSinkAllHist->cd(3);
  gPad->SetGridx();
  gPad->SetGridy();
  fhFullTsProcEvo->Draw("hist");

  fcEventSinkAllHist->cd(4);
  gPad->SetGridx();
  gPad->SetGridy();
  fhMissTsProcEvo->Draw("hist");

  fcEventSinkAllHist->cd(5);
  gPad->SetGridx();
  gPad->SetGridy();
  fhTotalTsProcEvo->Draw("hist");

  fcEventSinkAllHist->cd(6);
  gPad->SetGridx();
  gPad->SetGridy();
  gPad->SetLogy();
  fhTotalEventsEvo->Draw("hist");

  vCanvases.push_back(std::pair<TCanvas*, std::string>(fcEventSinkAllHist, std::string("canvases") + fsHistosSuffix));

  /// Add pointers to each histo in the histo array
  /// Create histo config vector
  /// ===> Use an std::vector< std::pair< std::string, std::string > > with < Histo name, Folder >
  ///      and send it through a separate channel using the BoostSerializer
  for (UInt_t uHisto = 0; uHisto < vHistos.size(); ++uHisto) {
    //         LOG(info) << "Registering  " << vHistos[ uHisto ].first->GetName()
    //                   << " in " << vHistos[ uHisto ].second.data()
    //                   ;
    fArrayHisto.Add(vHistos[uHisto].first);
    std::pair<std::string, std::string> psHistoConfig(vHistos[uHisto].first->GetName(), vHistos[uHisto].second);
    fvpsHistosFolder.push_back(psHistoConfig);

    LOG(info) << "Config of hist  " << psHistoConfig.first.data() << " in folder " << psHistoConfig.second.data();
  }  // for( UInt_t uHisto = 0; uHisto < vHistos.size(); ++uHisto )

  /// Create canvas config vector
  /// ===> Use an std::vector< std::pair< std::string, std::string > > with < Canvas name, config >
  ///      and send it through a separate channel using the BoostSerializer
  for (UInt_t uCanv = 0; uCanv < vCanvases.size(); ++uCanv) {
    //         LOG(info) << "Registering  " << vCanvases[ uCanv ].first->GetName()
    //                   << " in " << vCanvases[ uCanv ].second.data();
    std::string sCanvName = (vCanvases[uCanv].first)->GetName();
    std::string sCanvConf = GenerateCanvasConfigString(vCanvases[uCanv].first);

    std::pair<std::string, std::string> psCanvConfig(sCanvName, sCanvConf);

    fvpsCanvasConfig.push_back(psCanvConfig);

    LOG(info) << "Config string of Canvas  " << psCanvConfig.first.data() << " is " << psCanvConfig.second.data();
  }  //  for( UInt_t uCanv = 0; uCanv < vCanvases.size(); ++uCanv )

  return initOK;
}
bool CbmDeviceDigiEventSink::ResetHistograms(bool bResetStartTime)
{
  fhFullTsBuffSizeEvo->Reset();
  fhMissTsBuffSizeEvo->Reset();
  fhFullTsProcEvo->Reset();
  fhMissTsProcEvo->Reset();
  fhTotalTsProcEvo->Reset();
  fhTotalEventsEvo->Reset();
  if (bResetStartTime) {
    /// Reset the start time of the time evolution histograms
    fStartTime = std::chrono::system_clock::now();
  }
  return true;
}

//--------------------------------------------------------------------//
// handler is called whenever a message arrives on fsChannelNameMissedTs, with a reference to the message and a sub-channel index (here 0)
bool CbmDeviceDigiEventSink::HandleMissTsData(FairMQMessagePtr& msg, int /*index*/)
{
  std::vector<uint64_t> vIndices;
  std::string msgStrMissTs(static_cast<char*>(msg->GetData()), msg->GetSize());
  std::istringstream issMissTs(msgStrMissTs);
  boost::archive::binary_iarchive inputArchiveMissTs(issMissTs);
  inputArchiveMissTs >> vIndices;

  fvulMissedTsIndices.insert(fvulMissedTsIndices.end(), vIndices.begin(), vIndices.end());

  /// Check TS queue and process it if needed (in case it filled a hole!)
  if (!fbBypassConsecutiveTs) {
    /// But only if Consecutive TS check is not disabled explicitly by user
    CheckTsQueues();
  }

  return true;
}
//--------------------------------------------------------------------//
// handler is called whenever a message arrives on "data", with a reference to the message and a sub-channel index (here 0)
bool CbmDeviceDigiEventSink::HandleData(FairMQParts& parts, int /*index*/)
{
  fulNumMessages++;
  LOG(debug) << "Received message number " << fulNumMessages << " with " << parts.Size() << " parts"
             << ", size0: " << parts.At(0)->GetSize();

  if (0 == fulNumMessages % 10000) LOG(info) << "Received " << fulNumMessages << " messages";

  /// Unpack the message
  CbmEventTimeslice unpTs(parts, fbDigiEventInput);

  /// FIXME: Need to check if TS arrived in order (probably not!!!) + buffer!!!
  LOG(debug) << "Next TS check " << fuPrevTsIndex << " " << fulTsCounter << " " << unpTs.fTsMetaData.GetIndex()
             << " Storage size: " << fmFullTsStorage.size();
  if (fbBypassConsecutiveTs || (fuPrevTsIndex + 1 == unpTs.fTsMetaData.GetIndex())
      || (0 == fuPrevTsIndex && 0 == fulTsCounter && 0 == unpTs.fTsMetaData.GetIndex())) {
    LOG(debug) << "TS direct to dump";
    /// Fill all storage variables registers for data output
    PrepareTreeEntry(unpTs);
    /// Trigger FairRoot manager to dump Tree entry
    DumpTreeEntry();
    /// Update counters
    fuPrevTsIndex = unpTs.fTsMetaData.GetIndex();
    fulTsCounter++;
  }
  else {
    LOG(debug) << "TS direct to storage";
    /// If not consecutive to last TS sent,
    fmFullTsStorage.emplace_hint(fmFullTsStorage.end(),
                                 std::pair<uint64_t, CbmEventTimeslice>(unpTs.fTsMetaData.GetIndex(), unpTs));
  }
  LOG(debug) << "TS metadata checked";

  /// Clear metadata => crashes, maybe not needed as due to move the pointer is invalidated?
  //   delete fTsMetaData;

  if (fbBypassConsecutiveTs) {
    /// Skip checking the TS buffer as writing straight to file
    /// => Just check if we are done and can close the file or not
    if (fbReceivedEof) {
      /// In this case we cannot check if the last TS received/processed is the final one due to lack of order
      /// => use instead the fact that we received all expected TS
      if ((fulTsCounter + fvulMissedTsIndices.size()) == fuTotalTsCount) {
        LOG(info) << "CbmDeviceDigiEventSink::HandleData => "
                  << "Found all expected TS (" << fulTsCounter << ") and total nb of TS " << fuTotalTsCount
                  << " after accounting for the ones reported as missing by the source (" << fvulMissedTsIndices.size()
                  << ")";
        Finish();
      }  // if ((fulTsCounter + fvulMissedTsIndices.size()) == fuTotalTsCount)
    }
  }
  else {
    /// Check TS queue and process it if needed (in case it filled a hole!)
    CheckTsQueues();
    LOG(debug) << "TS queues checked";
  }

  /// Histograms management
  if (kTRUE == fbFillHistos) {
    std::chrono::system_clock::time_point currentTime = std::chrono::system_clock::now();
    /// Fill histograms every 5 or more seconds
    /// TODO: make it a parameter
    std::chrono::duration<double_t> elapsedSecondsFill = currentTime - fLastFillTime;
    if (1.0 < elapsedSecondsFill.count()) {
      std::chrono::duration<double_t> secInRun = currentTime - fStartTime;

      /// Rely on the fact that all histos have same X axis to avoid multiple "current bin" search
      /*
      int32_t iBinIndex = fhFullTsBuffSizeEvo->FindBin(secInRun.count());
      fhFullTsBuffSizeEvo->SetBinContent(iBinIndex, fmFullTsStorage.size());
      fhMissTsBuffSizeEvo->SetBinContent(iBinIndex, fvulMissedTsIndices.size());
      fhFullTsProcEvo->SetBinContent(iBinIndex, fulTsCounter);
      fhMissTsProcEvo->SetBinContent(iBinIndex, fulMissedTsCounter);
      fhTotalTsProcEvo->SetBinContent(iBinIndex, (fulTsCounter + fulMissedTsCounter));
      fhTotalEventsEvo->SetBinContent(iBinIndex, fulProcessedEvents);
      */
      fhFullTsBuffSizeEvo->Fill(secInRun.count(), fmFullTsStorage.size());
      fhMissTsBuffSizeEvo->Fill(secInRun.count(), fvulMissedTsIndices.size());
      fhFullTsProcEvo->Fill(secInRun.count(), (fulTsCounter - fulLastFullTsCounter));
      fhMissTsProcEvo->Fill(secInRun.count(), (fulMissedTsCounter - fulLastMissTsCounter));
      fhTotalTsProcEvo->Fill(secInRun.count(),
                             (fulTsCounter - fulLastFullTsCounter + fulMissedTsCounter - fulLastMissTsCounter));
      fhTotalEventsEvo->Fill(secInRun.count(), fulProcessedEvents - fulLastProcessedEvents);

      fLastFillTime          = currentTime;
      fulLastFullTsCounter   = fulTsCounter;
      fulLastMissTsCounter   = fulMissedTsCounter;
      fulLastProcessedEvents = fulProcessedEvents;
    }

    /// Send histograms each N timeslices.
    /// Use also runtime checker to trigger sending after M s if
    /// processing too slow or delay sending if processing too fast
    std::chrono::duration<double_t> elapsedSeconds = currentTime - fLastPublishTime;
    if ((fdMaxPublishTime < elapsedSeconds.count())
        || (0 == fulNumMessages % fuPublishFreqTs && fdMinPublishTime < elapsedSeconds.count())) {
      if (!fbConfigSent) {
        // Send the configuration only once per run!
        fbConfigSent = SendHistoConfAndData();
      }  // if( !fbConfigSent )
      else
        SendHistograms();

      fLastPublishTime = currentTime;
    }  // if( ( fdMaxPublishTime < elapsedSeconds.count() ) || ( 0 == fulNumMessages % fuPublishFreqTs && fdMinPublishTime < elapsedSeconds.count() ) )
  }    // if( kTRUE == fbFillHistos )

  LOG(debug) << "Processed TS with saving " << (fulTsCounter + fulMissedTsCounter) << " TS (" << fulTsCounter
             << " full ones and " << fulMissedTsCounter << " missed/empty ones)";
  LOG(debug) << "Buffers are " << fmFullTsStorage.size() << " full TS and " << fvulMissedTsIndices.size()
             << " missed/empty ones)";

  return true;
}
//--------------------------------------------------------------------//
bool CbmDeviceDigiEventSink::HandleCommand(FairMQMessagePtr& msg, int /*index*/)
{
  /*
   std::string sCommand( static_cast< char * >( msg->GetData() ),
                          msg->GetSize() );
*/
  std::string sCommand;
  std::string msgStrCmd(static_cast<char*>(msg->GetData()), msg->GetSize());
  std::istringstream issCmd(msgStrCmd);
  boost::archive::binary_iarchive inputArchiveCmd(issCmd);
  inputArchiveCmd >> sCommand;

  std::string sCmdTag = sCommand;
  size_t charPosDel   = sCommand.find(' ');
  if (std::string::npos != charPosDel) {
    sCmdTag = sCommand.substr(0, charPosDel);
  }  // if( std::string::npos != charPosDel )

  if ("EOF" == sCmdTag) {
    fbReceivedEof = true;

    /// Extract the last TS index and global full TS count
    if (std::string::npos == charPosDel) {
      LOG(fatal) << "CbmDeviceDigiEventSink::HandleCommand => "
                 << "Incomplete EOF command received: " << sCommand;
      return false;
    }  // if( std::string::npos == charPosDel )
       /// Last TS index
    charPosDel++;
    std::string sNext = sCommand.substr(charPosDel);
    charPosDel        = sNext.find(' ');

    if (std::string::npos == charPosDel) {
      LOG(fatal) << "CbmDeviceDigiEventSink::HandleCommand => "
                 << "Incomplete EOF command received: " << sCommand;
      return false;
    }  // if( std::string::npos == charPosDel )
    fuLastTsIndex = std::stoul(sNext.substr(0, charPosDel));
    /// Total TS count
    charPosDel++;
    fuTotalTsCount = std::stoul(sNext.substr(charPosDel));

    LOG(info) << "CbmDeviceDigiEventSink::HandleCommand => "
              << "Received EOF command with final TS index " << fuLastTsIndex << " and total nb TS " << fuTotalTsCount;
    /// End of data: clean save of data + close file + send last state of histos if enabled
    if (fuPrevTsIndex == fuLastTsIndex && fulTsCounter == fuTotalTsCount) {
      LOG(info) << "CbmDeviceDigiEventSink::HandleCommand => "
                << "Found final TS index " << fuLastTsIndex << " and total nb TS " << fuTotalTsCount;
      Finish();
    }  // if( fuPrevTsIndex == fuLastTsIndex && fulTsCounter == fuTotalTsCount )
  }    // if( "EOF" == sCmdTag )
  else if ("STOP" == sCmdTag) {
    /// TODO: different treatment in case of "BAD" ending compared to EOF?
    /// Source failure: clean save of received data + close file + send last state of histos if enabled
    Finish();
  }  // else if( "STOP" == sCmdTag )
  else {
    LOG(warning) << "Unknown command received: " << sCmdTag << " => will be ignored!";
  }  // else if command not recognized

  return true;
}
//--------------------------------------------------------------------//
void CbmDeviceDigiEventSink::CheckTsQueues()
{
  bool bHoleFoundInBothQueues = false;

  std::map<uint64_t, CbmEventTimeslice>::iterator itFullTs = fmFullTsStorage.begin();
  std::vector<uint64_t>::iterator itMissTs                 = fvulMissedTsIndices.begin();

  while (!bHoleFoundInBothQueues) {
    /// Check if the first TS in the full TS queue is the next one
    if (fmFullTsStorage.end() != itFullTs && fuPrevTsIndex + 1 == (*itFullTs).first) {
      /// Fill all storage variables registers for data output
      PrepareTreeEntry((*itFullTs).second);
      /// Trigger FairRoot manager to dump Tree entry
      DumpTreeEntry();

      /// Update counters
      fuPrevTsIndex = (*itFullTs).first;
      fulTsCounter++;

      /// Increment iterator
      ++itFullTs;
      continue;
    }  // if( fmFullTsStorage.end() != itFullTs && fuPrevTsIndex + 1 == (*itFullTs).first() )
    if (fmFullTsStorage.end() != itFullTs)
      LOG(debug) << "CbmDeviceDigiEventSink::CheckTsQueues => Full TS " << (*itFullTs).first << " VS "
                 << (fuPrevTsIndex + 1);
    /// Check if the first TS in the missed TS queue is the next one
    if (fvulMissedTsIndices.end() != itMissTs
        && ((0 == fuPrevTsIndex && fuPrevTsIndex == (*itMissTs))
            || ((0 < fulTsCounter || 0 < fulMissedTsCounter) && fuPrevTsIndex + 1 == (*itMissTs)))) {

      if (fbWriteMissingTs) {
        /// Prepare entry with only dummy TS metadata and empty storage variables
        new ((*fTimeSliceMetaDataArray)[fTimeSliceMetaDataArray->GetEntriesFast()])
          TimesliceMetaData(0, 0, 0, (*itMissTs));

        /// Trigger FairRoot manager to dump Tree entry
        DumpTreeEntry();
      }

      /// Update counters
      fuPrevTsIndex = (*itMissTs);
      fulMissedTsCounter++;

      /// Increment iterator
      ++itMissTs;
      continue;
    }  // if( fvulMissedTsIndices.end() != itMissTs && fuPrevTsIndex + 1 == (*itMissTs ) )
    if (fvulMissedTsIndices.end() != itMissTs)
      LOG(debug) << "CbmDeviceDigiEventSink::CheckTsQueues => Empty TS " << (*itMissTs) << " VS "
                 << (fuPrevTsIndex + 1);

    /// Should be reached only if both queues at the end or hole found in both
    bHoleFoundInBothQueues = true;
  }  // while( !bHoleFoundInBothQueues )

  LOG(debug) << "CbmDeviceDigiEventSink::CheckTsQueues => buffered TS " << fmFullTsStorage.size()
             << " buffered empties " << fvulMissedTsIndices.size();
  for (auto it = fmFullTsStorage.begin(); it != fmFullTsStorage.end(); ++it) {
    LOG(debug) << "CbmDeviceDigiEventSink::CheckTsQueues => buffered TS index " << (*it).first;
  }

  /// Delete the processed entries
  fmFullTsStorage.erase(fmFullTsStorage.begin(), itFullTs);
  fvulMissedTsIndices.erase(fvulMissedTsIndices.begin(), itMissTs);

  /// End of data: clean save of data + close file + send last state of histos if enabled
  if (fbReceivedEof && fuPrevTsIndex == fuLastTsIndex && fulTsCounter == fuTotalTsCount) {
    LOG(info) << "CbmDeviceDigiEventSink::CheckTsQueues => "
              << "Found final TS index " << fuLastTsIndex << " and total nb TS " << fuTotalTsCount;
    Finish();
  }  // if( fbReceivedEof && fuPrevTsIndex == fuLastTsIndex && fulTsCounter == fuTotalTsCount )
}
//--------------------------------------------------------------------//
void CbmDeviceDigiEventSink::PrepareTreeEntry(CbmEventTimeslice unpTs)
{
  /// FIXME: poor man solution with lots of data copy until we undertsnad how to properly deal
  /// with FairMq messages ownership and memory managment

  (*fEvtHeader) = std::move(unpTs.fCbmTsEventHeader);

  /// FIXME: Not sure if this is the proper way to insert the data
  new ((*fTimeSliceMetaDataArray)[fTimeSliceMetaDataArray->GetEntriesFast()])
    TimesliceMetaData(std::move(unpTs.fTsMetaData));

  /// Extract CbmEvent vector from input message
  // FU, 29.06.22 Remove std::move to allow copy ellision
  (*fEventsSel) = unpTs.GetSelectedData();
  if (kTRUE == fbFillHistos) {
    /// Accumulated counts, will show rise + plateau pattern in spill
    fulProcessedEvents += fEventsSel->size();
  }

  /// Full TS Digis storage (optional usage, controlled by fbStoreFullTs!)
  if (fbStoreFullTs) {
    if (0 < unpTs.fvDigiT0.size()) fvDigiT0->assign(unpTs.fvDigiT0.begin(), unpTs.fvDigiT0.end());
    if (0 < unpTs.fvDigiSts.size()) fvDigiSts->assign(unpTs.fvDigiSts.begin(), unpTs.fvDigiSts.end());
    if (0 < unpTs.fvDigiMuch.size()) fvDigiMuch->assign(unpTs.fvDigiMuch.begin(), unpTs.fvDigiMuch.end());
    if (0 < unpTs.fvDigiTrd.size()) fvDigiTrd->assign(unpTs.fvDigiTrd.begin(), unpTs.fvDigiTrd.end());
    if (0 < unpTs.fvDigiTof.size()) fvDigiTof->assign(unpTs.fvDigiTof.begin(), unpTs.fvDigiTof.end());
    if (0 < unpTs.fvDigiRich.size()) fvDigiRich->assign(unpTs.fvDigiRich.begin(), unpTs.fvDigiRich.end());
    if (0 < unpTs.fvDigiPsd.size()) fvDigiPsd->assign(unpTs.fvDigiPsd.begin(), unpTs.fvDigiPsd.end());
  }
}
void CbmDeviceDigiEventSink::DumpTreeEntry()
{
  // Unpacked digis + CbmEvent output to root file
  /*
 * NH style
//      fpFairRootMgr->FillEventHeader(fEvtHeader);
//      LOG(info) << "Fill WriteOutBuffer with FairRootManager at " << fpFairRootMgr;
//      fpOutRootFile->cd();
      fpFairRootMgr->Fill();
      fpFairRootMgr->StoreWriteoutBufferData( fpFairRootMgr->GetEventTime() );
      //fpFairRootMgr->StoreAllWriteoutBufferData();
      fpFairRootMgr->DeleteOldWriteoutBufferData();
*/
  /// FairRunOnline style
  fpFairRootMgr->StoreWriteoutBufferData(fpFairRootMgr->GetEventTime());
  fpFairRootMgr->FillEventHeader(fEvtHeader);
  fpFairRootMgr->Fill();
  fpFairRootMgr->DeleteOldWriteoutBufferData();
  //  fpFairRootMgr->Write();

  /// Clear metadata array
  fTimeSliceMetaDataArray->Clear();

  /// Clear event vector
  fEventsSel->clear();
  /// Full TS Digis storage (optional usage, controlled by fbStoreFullTs!)
  if (fbStoreFullTs) {
    fvDigiT0->clear();
    fvDigiSts->clear();
    fvDigiMuch->clear();
    fvDigiTrd->clear();
    fvDigiTof->clear();
    fvDigiRich->clear();
    fvDigiPsd->clear();
  }
}

//--------------------------------------------------------------------//

bool CbmDeviceDigiEventSink::SendHistoConfAndData()
{
  /// Prepare multiparts message and header
  std::pair<uint32_t, uint32_t> pairHeader(fvpsHistosFolder.size(), fvpsCanvasConfig.size());
  FairMQMessagePtr messageHeader(NewMessage());
  //  Serialize<BoostSerializer<std::pair<uint32_t, uint32_t>>>(*messageHeader, pairHeader);
  BoostSerializer<std::pair<uint32_t, uint32_t>>().Serialize(*messageHeader, pairHeader);

  FairMQParts partsOut;
  partsOut.AddPart(std::move(messageHeader));

  for (UInt_t uHisto = 0; uHisto < fvpsHistosFolder.size(); ++uHisto) {
    /// Serialize the vector of histo config into a single MQ message
    FairMQMessagePtr messageHist(NewMessage());
    //    Serialize<BoostSerializer<std::pair<std::string, std::string>>>(*messageHist, fvpsHistosFolder[uHisto]);
    BoostSerializer<std::pair<std::string, std::string>>().Serialize(*messageHist, fvpsHistosFolder[uHisto]);

    partsOut.AddPart(std::move(messageHist));
  }  // for (UInt_t uHisto = 0; uHisto < fvpsHistosFolder.size(); ++uHisto)

  /// Catch case where no histos are registered!
  /// => Add empty message
  if (0 == fvpsHistosFolder.size()) {
    FairMQMessagePtr messageHist(NewMessage());
    partsOut.AddPart(std::move(messageHist));
  }

  for (UInt_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv) {
    /// Serialize the vector of canvas config into a single MQ message
    FairMQMessagePtr messageCan(NewMessage());
    //    Serialize<BoostSerializer<std::pair<std::string, std::string>>>(*messageCan, fvpsCanvasConfig[uCanv]);
    BoostSerializer<std::pair<std::string, std::string>>().Serialize(*messageCan, fvpsCanvasConfig[uCanv]);

    partsOut.AddPart(std::move(messageCan));
  }  // for (UInt_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv)

  /// Catch case where no Canvases are registered!
  /// => Add empty message
  if (0 == fvpsCanvasConfig.size()) {
    FairMQMessagePtr messageHist(NewMessage());
    partsOut.AddPart(std::move(messageHist));
  }

  /// Serialize the array of histos into a single MQ message
  FairMQMessagePtr msgHistos(NewMessage());
  RootSerializer().Serialize(*msgHistos, &fArrayHisto);

  partsOut.AddPart(std::move(msgHistos));

  /// Send the multi-parts message to the common histogram messages queue
  if (Send(partsOut, fsChannelNameHistosInput) < 0) {
    LOG(error) << "CbmTsConsumerReqDevExample::SendHistoConfAndData => Problem sending data";
    return false;
  }  // if( Send( partsOut, fsChannelNameHistosInput ) < 0 )

  /// Reset the histograms after sending them (but do not reset the time)
  ResetHistograms(false);

  return true;
}

bool CbmDeviceDigiEventSink::SendHistograms()
{
  /// Serialize the array of histos into a single MQ message
  FairMQMessagePtr message(NewMessage());
  RootSerializer().Serialize(*message, &fArrayHisto);

  /// Send message to the common histogram messages queue
  if (Send(message, fsChannelNameHistosInput) < 0) {
    LOG(error) << "Problem sending data";
    return false;
  }  // if( Send( message, fsChannelNameHistosInput ) < 0 )

  /// Reset the histograms after sending them (but do not reset the time)
  ResetHistograms(false);

  return true;
}

//--------------------------------------------------------------------//
CbmDeviceDigiEventSink::~CbmDeviceDigiEventSink()
{
  /// FIXME: Add pointers check before delete

  /// Close things properly if not already done
  if (fbInitDone && !fbFinishDone) Finish();

  /// Clear events vector
  if (fbInitDone) {
    fEventsSel->clear();
    delete fEventsSel;
  }

  delete fpRun;
}

void CbmDeviceDigiEventSink::Finish()
{
  LOG(info) << "Performing clean close of the file";
  // Clean closure of output to root file
  fpFairRootMgr->Write();  // Broken due to FileMaxSize?!?
  fpFairRootMgr->CloseSink();
  LOG(info) << "File closed after saving " << (fulTsCounter + fulMissedTsCounter) << " TS (" << fulTsCounter
            << " full ones and " << fulMissedTsCounter << " missed/empty ones)";
  LOG(info) << "Still buffered TS " << fmFullTsStorage.size() << " and still buffered empties "
            << fvulMissedTsIndices.size();

  if (kTRUE == fbFillHistos) {
    SendHistograms();
    fLastPublishTime = std::chrono::system_clock::now();
  }  // if( kTRUE == fbFillHistos )

  ChangeState(fair::mq::Transition::Stop);
  std::this_thread::sleep_for(std::chrono::milliseconds(3000));
  ChangeState(fair::mq::Transition::End);

  fbFinishDone = kTRUE;
}


CbmEventTimeslice::CbmEventTimeslice(FairMQParts& parts, bool bDigiEvtInput)
{
  fbDigiEvtInput = bDigiEvtInput;

  uint32_t uPartIdx = 0;

  if (fbDigiEvtInput) {
    /// Digi events => Extract selected data from input message
    if (3 != parts.Size()) {
      LOG(error) << "CbmEventTimeslice::CbmEventTimeslice => Wrong number of parts to deserialize DigiEvents: "
                 << parts.Size() << " VS 3!";
      LOG(fatal) << "Probably the wrong value was used for the option DigiEventInput of the Sink or DigiEventOutput of "
                 << "the event builder";
    }

    /// (1) TS header
    TObject* tempObjectPointer = nullptr;
    RootSerializer().Deserialize(*parts.At(uPartIdx), tempObjectPointer);
    if (tempObjectPointer && TString(tempObjectPointer->ClassName()).EqualTo("CbmTsEventHeader")) {
      fCbmTsEventHeader = *(static_cast<CbmTsEventHeader*>(tempObjectPointer));
    }
    else {
      LOG(fatal) << "Failed to deserialize the TS header";
    }
    ++uPartIdx;

    /// (2) TS metadata
    tempObjectPointer = nullptr;
    RootSerializer().Deserialize(*parts.At(uPartIdx), tempObjectPointer);

    if (tempObjectPointer && TString(tempObjectPointer->ClassName()).EqualTo("TimesliceMetaData")) {
      fTsMetaData = *(static_cast<TimesliceMetaData*>(tempObjectPointer));
    }
    else {
      LOG(fatal) << "Failed to deserialize the TS metadata";
    }
    ++uPartIdx;

    /// (3) Events
    std::string msgStrEvt(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issEvt(msgStrEvt);
    boost::archive::binary_iarchive inputArchiveEvt(issEvt);
    inputArchiveEvt >> fvDigiEvents;
    ++uPartIdx;

    LOG(debug) << "Input event array " << fvDigiEvents.size();
  }
  else {
    /// Raw data + raw events => Extract unpacked data from input message
    if (10 != parts.Size()) {
      LOG(error) << "CbmEventTimeslice::CbmEventTimeslice => Wrong number of parts to deserialize raw data + events: "
                 << parts.Size() << " VS 10!";
      LOG(fatal) << "Probably the wrong value was used for the option DigiEventInput of the Sink or DigiEventOutput of "
                 << "the event builder";
    }

    /// (1) TS header
    TObject* tempObjectPointer = nullptr;
    RootSerializer().Deserialize(*parts.At(uPartIdx), tempObjectPointer);
    if (tempObjectPointer && TString(tempObjectPointer->ClassName()).EqualTo("CbmTsEventHeader")) {
      fCbmTsEventHeader = *(static_cast<CbmTsEventHeader*>(tempObjectPointer));
    }
    else {
      LOG(fatal) << "Failed to deserialize the TS header";
    }
    ++uPartIdx;

    /// (2) T0
    std::string msgStrT0(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issT0(msgStrT0);
    boost::archive::binary_iarchive inputArchiveT0(issT0);
    inputArchiveT0 >> fvDigiT0;
    ++uPartIdx;

    /// (3) STS
    std::string msgStrSts(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issSts(msgStrSts);
    boost::archive::binary_iarchive inputArchiveSts(issSts);
    inputArchiveSts >> fvDigiSts;
    ++uPartIdx;

    /// (4) MUCH
    std::string msgStrMuch(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issMuch(msgStrMuch);
    boost::archive::binary_iarchive inputArchiveMuch(issMuch);
    inputArchiveMuch >> fvDigiMuch;
    ++uPartIdx;

    /// (5) TRD
    std::string msgStrTrd(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issTrd(msgStrTrd);
    boost::archive::binary_iarchive inputArchiveTrd(issTrd);
    inputArchiveTrd >> fvDigiTrd;
    ++uPartIdx;

    /// (6) T0F
    std::string msgStrTof(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issTof(msgStrTof);
    boost::archive::binary_iarchive inputArchiveTof(issTof);
    inputArchiveTof >> fvDigiTof;
    ++uPartIdx;

    /// (7) RICH
    std::string msgStrRich(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issRich(msgStrRich);
    boost::archive::binary_iarchive inputArchiveRich(issRich);
    inputArchiveRich >> fvDigiRich;
    ++uPartIdx;
    /// (8) PSD
    std::string msgStrPsd(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issPsd(msgStrPsd);
    boost::archive::binary_iarchive inputArchivePsd(issPsd);
    inputArchivePsd >> fvDigiPsd;
    ++uPartIdx;

    /// (9) TS metadata
    tempObjectPointer = nullptr;
    RootSerializer().Deserialize(*parts.At(uPartIdx), tempObjectPointer);

    if (tempObjectPointer && TString(tempObjectPointer->ClassName()).EqualTo("TimesliceMetaData")) {
      fTsMetaData = *(static_cast<TimesliceMetaData*>(tempObjectPointer));
    }
    else {
      LOG(fatal) << "Failed to deserialize the TS metadata";
    }
    ++uPartIdx;

    /// (10) Events
    /// FIXME: Find out if possible to use only the boost serializer/deserializer
    /*
    std::string msgStrEvt(static_cast<char*>(parts.At(uPartIdx)->GetData()), (parts.At(uPartIdx))->GetSize());
    std::istringstream issEvt(msgStrEvt);
    boost::archive::binary_iarchive inputArchiveEvt(issEvt);
    inputArchiveEvt >> fvEvents;
    ++uPartIdx;
    LOG(info) << "Input event array " << fvEvents.size();
    */
    std::vector<CbmEvent>* pvOutEvents = nullptr;
    RootSerializer().Deserialize(*parts.At(uPartIdx), pvOutEvents);
    fvEvents = std::move(*pvOutEvents);
    LOG(debug) << "Input event array " << fvEvents.size();
  }
}

CbmEventTimeslice::~CbmEventTimeslice()
{
  fvDigiT0.clear();
  fvDigiSts.clear();
  fvDigiMuch.clear();
  fvDigiTrd.clear();
  fvDigiTof.clear();
  fvDigiRich.clear();
  fvDigiPsd.clear();
  fvEvents.clear();
  fvDigiEvents.clear();
}

void CbmEventTimeslice::ExtractSelectedData()
{
  fvDigiEvents.reserve(fvEvents.size());

  /// Loop on events in input vector
  for (CbmEvent event : fvEvents) {
    CbmDigiEvent selEvent;
    selEvent.fTime   = event.GetStartTime();
    selEvent.fNumber = event.GetNumber();

    /// FIXME: for pure digi based event, we select "continuous slices of digis"
    ///        => Copy block of [First Digi index, last digi index] with assign(it_start, it_stop)
    /// FIXME: Keep TRD1D + TRD2D support, may lead to holes in the digi sequence!
    ///        => Would need to keep the loop

    /// Get the proper order for block selection as TRD1D and TRD2D may insert indices in separate loops
    /// => Needed to ensure that the start and stop of the block copy do not trigger a vector size exception
    event.SortIndices();

    /// for each detector, find the data in the Digi vectors and copy them
    /// TODO: Template + loop on list of data types?
    /// ==> T0
    uint32_t uNbDigis = (0 < event.GetNofData(ECbmDataType::kT0Digi) ? event.GetNofData(ECbmDataType::kT0Digi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiT0.begin() + event.GetIndex(ECbmDataType::kT0Digi, 0);
      auto stopIt  = fvDigiT0.begin() + event.GetIndex(ECbmDataType::kT0Digi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fT0.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kT0Digi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fT0.fDigis.push_back(fvDigiT0[event.GetIndex(ECbmDataType::kT0Digi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kT0Digi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kT0Digi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kT0Digi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the T0 block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> STS
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kStsDigi) ? event.GetNofData(ECbmDataType::kStsDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiSts.begin() + event.GetIndex(ECbmDataType::kStsDigi, 0);
      auto stopIt  = fvDigiSts.begin() + event.GetIndex(ECbmDataType::kStsDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fSts.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kStsDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fSts.fDigis.push_back(fvDigiSts[event.GetIndex(ECbmDataType::kStsDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kStsDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kStsDigi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kStsDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the STS block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> MUCH
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kMuchDigi) ? event.GetNofData(ECbmDataType::kMuchDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiMuch.begin() + event.GetIndex(ECbmDataType::kMuchDigi, 0);
      auto stopIt  = fvDigiMuch.begin() + event.GetIndex(ECbmDataType::kMuchDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fMuch.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kMuchDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fMuch.fDigis.push_back(fvDigiMuch[event.GetIndex(ECbmDataType::kMuchDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kMuchDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kMuchDigi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kMuchDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the MUCH block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> TRD + TRD2D
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kTrdDigi) ? event.GetNofData(ECbmDataType::kTrdDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiTrd.begin() + event.GetIndex(ECbmDataType::kTrdDigi, 0);
      auto stopIt  = fvDigiTrd.begin() + event.GetIndex(ECbmDataType::kTrdDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fTrd.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kTrdDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fTrd.fDigis.push_back(fvDigiTrd[event.GetIndex(ECbmDataType::kTrdDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kTrdDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kTrdDigi, uDigiIdx) - (uPrevIdx + 1);
          LOG(info) << "Extra TRD digi: prev is " << uPrevIdx << " vs new " << event.GetIndex(ECbmDataType::kTrdDigi, uDigiIdx)
                    << " index " << uDigiIdx << " out of " << uNbDigis;
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kTrdDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the TRD block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> TOF
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kTofDigi) ? event.GetNofData(ECbmDataType::kTofDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiTof.begin() + event.GetIndex(ECbmDataType::kTofDigi, 0);
      auto stopIt  = fvDigiTof.begin() + event.GetIndex(ECbmDataType::kTofDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fTof.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kTofDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fTof.fDigis.push_back(fvDigiTof[event.GetIndex(ECbmDataType::kTofDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kTofDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kTofDigi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kTofDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the TOF block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> RICH
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kRichDigi) ? event.GetNofData(ECbmDataType::kRichDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiRich.begin() + event.GetIndex(ECbmDataType::kRichDigi, 0);
      auto stopIt  = fvDigiRich.begin() + event.GetIndex(ECbmDataType::kRichDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fRich.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kRichDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fRich.fDigis.push_back(fvDigiRich[event.GetIndex(ECbmDataType::kRichDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kRichDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kRichDigi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kRichDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the RICH block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    /// ==> PSD
    uNbDigis = (0 < event.GetNofData(ECbmDataType::kPsdDigi) ? event.GetNofData(ECbmDataType::kPsdDigi) : 0);
    if (uNbDigis) {
      auto startIt = fvDigiPsd.begin() + event.GetIndex(ECbmDataType::kPsdDigi, 0);
      auto stopIt  = fvDigiPsd.begin() + event.GetIndex(ECbmDataType::kPsdDigi, uNbDigis - 1);
      ++stopIt;
      selEvent.fData.fPsd.fDigis.assign(startIt, stopIt);

      /*
      uint32_t uPrevIdx = event.GetIndex(ECbmDataType::kPsdDigi, 0);
      uint32_t uNbExtra = 0;
      for (uint32_t uDigiIdx = 1; uDigiIdx < uNbDigis; ++uDigiIdx) {
        // selEvent.fData.fPsd.fDigis.push_back(fvDigiPsd[event.GetIndex(ECbmDataType::kPsdDigi, uDigiIdx)]);
        if (uPrevIdx + 1 != event.GetIndex(ECbmDataType::kPsdDigi, uDigiIdx)) {
          uNbExtra += event.GetIndex(ECbmDataType::kPsdDigi, uDigiIdx) - (uPrevIdx + 1);
        }
        uPrevIdx = event.GetIndex(ECbmDataType::kPsdDigi, uDigiIdx);
      }
      if (0 < uNbExtra) {
        LOG(info) << "In event " << event.GetNumber() << " the PSD block selection added " << uNbExtra
                  << " extra digis compared to the loop one";
      }
      */
    }

    fvDigiEvents.push_back(selEvent);
  }
}