18 #include <sys/types.h> 55 static const char*
const colorNames[] = {
"Blue",
"Green",
"Red",
"IR"};
62 gettimeofday(&t,
nullptr);
65 strftime(buf,
sizeof(buf),
"%F %R %S s", localtime(&t.tv_sec));
66 buf[
sizeof(buf) - 1] = 0;
69 buf2 << buf <<
" " << ((t.tv_usec + 500) / 1000) <<
" ms";
78 outputDir_(pset.getParameter<
std::
string>(
"outputDir")),
79 fedSubDirs_(pset.getParameter<
std::vector<
std::
string> >(
"fedSubDirs")),
80 timeLogFile_(pset.getUntrackedParameter<
std::
string>(
"timeLogFile",
"")),
81 disableOutput_(pset.getUntrackedParameter<
bool>(
"disableOutput",
false)),
83 outputListFile_(pset.getUntrackedParameter<
string>(
"outputListFile",
"")),
85 verbosity_(pset.getUntrackedParameter<
int>(
"verbosity", 0)),
86 iNoFullReadoutDccError_(0),
87 maxFullReadoutDccError_(pset.getParameter<
int>(
"maxFullReadoutDccError")),
89 maxNoEcalDataMess_(pset.getParameter<
int>(
"maxNoEcalDataMess")),
90 lumiBlockSpan_(pset.getParameter<
int>(
"lumiBlockSpan")),
91 fedRawDataCollectionTag_(pset.getParameter<
edm::InputTag>(
"fedRawDataCollectionTag")),
93 overWriteLumiBlockId_(pset.getParameter<
bool>(
"overWriteLumiBlockId")),
94 orbitCountInALumiBlock_(pset.getParameter<
int>(
"orbitCountInALumiBlock")),
97 gettimeofday(&
timer_,
nullptr);
100 const unsigned nEcalFeds = 54;
103 <<
"fedSubDirs parameter must be a vector " 104 <<
" of " << nEcalFeds <<
" strings" 105 <<
" (subdirectory for unknown triggered FED followed by " 106 "subdirectories for FED ID 601 " 107 "to FED ID 654 in increasing FED ID order)";
116 <<
"' for logging of output file path list.";
124 cout <<
"[LaserSorter " <<
now() <<
"] " 125 <<
"Failed to open file " <<
timeLogFile_ <<
" to log timing.\n";
132 struct stat fileStat;
134 if (!S_ISDIR(fileStat.st_mode)) {
159 <<
" with unusable DCC ID values, " <<
stats_.
nRestoredDcc <<
" restored DCC ID based on DCC block size\n";
166 gettimeofday(&t,
nullptr);
167 timeLog_ << t.tv_sec <<
"." << setfill(
'0') << setw(3) << (t.tv_usec + 500) / 1000 << setfill(
' ') <<
"\t" 168 << (t.tv_usec -
timer_.tv_usec) * 1. + (t.tv_sec -
timer_.tv_sec) * 1.e6 <<
"\t";
191 orbit_ =
event.orbitNumber();
211 std::cout <<
"[LaserSorter " <<
now() <<
"] Overwrite LB mode. LB number changed from: " << lb <<
" to " 218 std::cout <<
"[LaserSorter " <<
now() <<
"] Standard LB mode. LB number changed from: " << lb <<
" to " 233 if (dttProba < 1. || triggeredFedId < ecalDccFedIdMin_ || triggeredFedId >
ecalDccFedIdMax_) {
237 if (triggeredFedId < ecalDccFedIdMin_ || triggeredFedId > ecalDccFedIdMax_) {
239 cout <<
"[LaserSorter " <<
now() <<
"] " 240 <<
"DCC ID (" << dccId <<
") found in trigger type is out of range.";
245 cout <<
" No fully read-out DCC found\n";
248 }
else if (ids.size() == 1) {
249 triggeredFedId = ids[0];
251 cout <<
" ID guessed from DCC payloads\n";
255 cout <<
" Several fully read-out Dccs:";
256 for (
unsigned i = 0;
i < ids.size(); ++
i)
257 cout <<
" " << ids[
i];
264 cout <<
"\n----------------------------------------------------------------------\n" 266 <<
" " <<
event.id() <<
"\n" 269 <<
" FED: " << triggeredFedId <<
" side:" << side <<
"\n" 270 <<
"\n----------------------------------------------------------------------\n";
274 cout <<
"\n----------------------------------------------------------------------\n" 276 <<
" " <<
event.id() <<
"\n" 279 <<
"\n----------------------------------------------------------------------\n";
282 logFile_ <<
event.id().run() <<
"\t" <<
lumiBlock_ <<
"\t" <<
event.id().event() <<
"\t" << trigType <<
"\t" 283 << triggeredFedId <<
"\t" << side;
285 bool written =
false;
294 for (
int lb1 = minLumi; lb1 <=
maxLumi; ++lb1) {
313 if (out !=
nullptr) {
317 cout <<
"[LaserSorter " <<
now() <<
"] " 318 <<
"Writing out event from FED " << triggeredFedId <<
" LB " <<
lumiBlock_ <<
" orbit " <<
orbit_ 321 written = written ||
writeEvent(*out, event, dtt, *rawdata);
325 cout <<
"[LaserSorter " <<
now() <<
"] " 327 <<
"already contains calibration event from FED " << triggeredFedId <<
", LB = " <<
lumiBlock_ 328 <<
" with orbit ID " <<
orbit_ <<
". Event skipped.\n";
339 logFile_ <<
"\t" << (written ?
"Y" :
"N") <<
"\n";
344 gettimeofday(&t,
nullptr);
351 int fedid = (dcc % 600) + 600;
356 lmes.push_back(fedid - 601 + 83);
357 }
else if (fedid == 608) {
360 }
else if (fedid == 609) {
364 else if (fedid >= 610 && fedid <= 645) {
365 lmes.push_back(2 * (fedid - 610) + 1);
366 lmes.push_back(lmes[0] + 1);
368 else if (fedid >= 646) {
370 lmes.push_back(fedid - 646 + 73);
371 }
else if (fedid == 653) {
374 }
else if (fedid == 654) {
378 return lmes.empty() ? -1 : lmes[
min(lmes.size(), (size_t)side)];
382 const int orbit32 = 6;
387 if (data.
size() >= 4 * (orbit32 + 1)) {
388 const uint32_t* pData32 = (
const uint32_t*)data.
data();
398 return pData32[orbit32];
406 bool ecalData =
false;
411 const int detailedTrigger32 = 5;
413 cout <<
"[LaserSorter " <<
now() <<
"] " 414 <<
"FED " <<
id <<
" data size: " << data.
size() <<
"\n";
415 if (data.
size() >= 4 * (detailedTrigger32 + 1)) {
417 const uint32_t* pData32 = (
const uint32_t*)data.
data();
418 int tType = pData32[detailedTrigger32] & 0xFFF;
420 cout <<
"[LaserSorter " <<
now() <<
"] " 421 <<
"Trigger type " << tType <<
"\n";
428 int tType = stat.
result(&p);
433 "(This warning will be disabled for the current run after " 440 edm::LogWarning(
"EventCorruption") <<
"Inconsitency in detailed trigger type indicated in ECAL DCC data headers\n";
461 if (it->startingLumiBlock() < minLumiBlock || it->startingLumiBlock() > maxLumiBlock) {
464 cout <<
"[LaserSorter " <<
now() <<
"] " 465 <<
"Closing file for " 466 <<
"FED " << it->fedId() <<
" LB " << it->startingLumiBlock() <<
"\n";
475 if ((fedId != -1) && (fedId < ecalDccFedIdMin_ || fedId >
ecalDccFedIdMax_))
479 cout <<
"[LaserSorter " <<
now() <<
"] " 480 <<
"Looking for an opened output file for FED " << fedId <<
" LB " << lumiBlock <<
"\n";
484 if (it->fedId() == fedId && (
abs((
int)it->startingLumiBlock() - (
int)lumiBlock) <=
lumiBlockSpan_)) {
492 cout <<
"[LaserSorter " <<
now() <<
"] " 493 <<
"File not yet opened. Opening it.\n";
495 OutStreamList::iterator streamRecord =
createOutStream(fedId, lumiBlock);
496 return streamRecord !=
outStreamList_.end() ? &(*streamRecord) :
nullptr;
503 ofstream&
out = *outRcd.
out();
505 vector<unsigned> fedIds;
509 uint32_t evtStart = out.tellp();
515 struct timeval ts = {0, 0};
518 uint32_t orb = mre.getOrbitId();
519 if (ts.tv_sec != 0) {
520 div_t
dt = div(orb * 89.1, 1000 * 1000);
530 for (
unsigned iFed = 0; iFed < fedIds.size() && rc; ++iFed) {
532 cout <<
"[LaserSorter " <<
now() <<
"] " 533 <<
"Writing data block of FED " << fedIds[iFed] <<
". Data size: " << data.
FEDData(fedIds[iFed]).
size()
540 vector<IndexRecord>& indices = *outRcd.
indices();
543 <<
" written successfully. " 544 <<
"Orbit: " <<
orbit_ <<
"\tFile index: " << evtStart <<
"\n";
547 indices.push_back(indexRcd);
554 if (data.
size() > 4) {
555 const uint32_t* pData =
reinterpret_cast<const uint32_t*
>(data.
data());
557 uint32_t dccLen64 = pData[2] & 0x00FFFFFF;
562 throw cms::Exception(
"LaserSorter") <<
"Mismatch between FED fragment size indicated in header " 563 <<
"(" << dccLen64 <<
"*8 Byte) " 564 <<
"and actual size (" << data.
size() <<
" Byte) " 565 <<
"for FED ID " << ((pData[0] >> 8) & 0xFFF) <<
"!\n";
569 cout <<
"[LaserSorter " <<
now() <<
"] " 570 <<
"Event fragment size: " << data.
size() <<
" Byte" 571 <<
"\t From Dcc header: " << dccLen64 * 8 <<
" Byte\n";
573 const size_t nBytes = data.
size();
578 cout <<
"[LaserSorter " <<
now() <<
"] " 579 <<
"Problem with stream!\n";
580 out.write((
char*)data.
data(), nBytes);
583 throw cms::Exception(
"Bug") <<
"Bug found in " << __FILE__ <<
":" << __LINE__ <<
".\n";
593 stringstream newFileName_;
595 newFileName_.str(
"");
596 newFileName_ << fileName <<
"~";
599 err = link(fileName.c_str(), newFileName_.str().c_str());
601 newFileName = newFileName_.str();
602 err = unlink(fileName.c_str());
605 }
while ((err != 0) && (errno == EEXIST) && (i < maxTries));
611 cout <<
"[LaserSorter " <<
now() <<
"] " 612 <<
"Creating a stream for FED " << fedId <<
" lumi block " << lumiBlock <<
".\n";
622 if (out->is_open()) {
625 throw cms::Exception(
"LaserSorter") <<
"Failed to rename file " << tmpName <<
" to " << newName <<
"\n";
628 cout <<
"[LaserSorter " <<
now() <<
"] " 629 <<
"Already existing File " << tmpName <<
" renamed to " << newName <<
"\n";
638 throw cms::Exception(
"LaserSorter") <<
"Failed to create file " << tmpName <<
" for writing event from FED " 639 << fedId <<
" lumi block " << lumiBlock <<
": " << strerror(errno) <<
"\n.";
642 ifstream
in(finalName.c_str());
646 cout <<
"[LaserSorter " <<
now() <<
"] " 647 <<
"File " << finalName <<
" already exists. It will be updated if needed.\n";
650 streamsize nread = -1;
653 edm::LogWarning(
"LaserSorter") <<
"File " << tmpName.c_str() <<
" is not an LMF file despite its extension or " 654 <<
"it is corrupted.\n";
656 edm::LogWarning(
"LaserSorter") <<
"Cannot include events already in file " << tmpName.c_str()
657 <<
" because of version " 658 <<
"mismatch (found version " << (
int)vers <<
" while " 663 const int indexTableOffsetPos8 = 1 *
sizeof(uint32_t);
664 uint32_t indexTableOffsetValue = 0;
666 in.seekg(indexTableOffsetPos8, ios::beg);
667 in.read((
char*)&indexTableOffsetValue,
sizeof(indexTableOffsetValue));
669 cout <<
"[LaserSorter " <<
now() <<
"] " 670 <<
"Failed to read offset of index table " 671 " in the existing file " 672 << finalName <<
"\n";
675 cout <<
"[LaserSorter " <<
now() <<
"] " 676 <<
"Index table offset of " 678 << finalName <<
": 0x" << hex << setfill(
'0') << setw(8) << indexTableOffsetValue <<
dec << setfill(
' ')
682 in.seekg(0, ios::beg);
685 uint32_t toRead = indexTableOffsetValue;
686 cout <<
"[LaserSorter " <<
now() <<
"] " 687 <<
"Copying " << finalName <<
" to " << tmpName << endl;
688 while (!
in.eof() && (toRead > 0) && (nread =
in.readsome(buffer,
min(toRead, (uint32_t)
sizeof(buffer)))) != 0) {
693 out->write(buffer, nread);
696 <<
"Error while writing to file " << tmpName <<
". Check if there is enough " 697 <<
"space on the device.\n";
702 indexTableOffsetValue = 0;
704 out->seekp(indexTableOffsetPos8, ios::beg);
705 out->write((
char*)&indexTableOffsetValue,
sizeof(uint32_t));
713 cout <<
"Press enter... file name was " << tmpName << endl;
735 uint32_t
id =
'L' | (
'M' << 8) | (
'F' << 16) | (
formatVersion_ << 24);
737 out.write((
char*)&
id,
sizeof(uint32_t));
741 out.write((
char*)&zero,
sizeof(uint32_t));
744 throw cms::Exception(
"LaserSorter") <<
"Failed to write file header.\n";
752 tt.tv_usec = evt.
time().
value() & 0xFFFFFFFF;
755 div_t
dt = div(
orbit_ * 89.1, 1000 * 1000);
758 if (tt.tv_usec > 1000 * 1000) {
759 tt.tv_usec -= 1000 * 1000;
764 data[0] = tt.tv_usec;
776 cout <<
"[LaserSorter " <<
now() <<
"] " 777 <<
"Write header of event: " 779 <<
", Bx: " << evt.
bunchCrossing() <<
", Event ID: " << evt.
id().
event() <<
", Detailed trigger type: 0x" 781 <<
", DCC " << (dtt & 0x3f) <<
", side " << ((dtt >> 10) & 0x1) <<
")" 782 <<
", number of FEDs: " 787 out.write((
char*)data,
sizeof(data));
798 }
else if (fedId < 0) {
803 if (iFed < -1 || iFed >= (
int)
fedSubDirs_.size()) {
804 throw cms::Exception(
"LaserSorter") <<
"Bug found at " << __FILE__ <<
":" << __LINE__
805 <<
". FED ID is out of index!";
808 struct stat fileStat;
813 string dir = buf.str();
814 if (0 ==
stat(dir.c_str(), &fileStat)) {
815 if (!S_ISDIR(fileStat.st_mode)) {
816 throw cms::Exception(
"[LaserSorter]") <<
"File " << dir <<
" exists but is not a directory " 820 if (0 !=
mkdir(dir.c_str(), 0755)) {
821 throw cms::Exception(
"[LaserSorter]") <<
"Failed to create directory " << dir <<
" for writing data.";
826 buf <<
"Run" <<
runNumber_ <<
"_LB" << setfill(
'0') << setw(4) << lumiBlock <<
".lmf";
828 string tmpFileName = fileName +
".part";
831 tmpName = dir +
"/" + tmpFileName;
834 cout <<
"[LaserSorter " <<
now() <<
"] " 835 <<
"File path: " << finalName <<
"\n";
843 cout <<
"[LaserSorter " <<
now() <<
"] " 844 <<
"Writing Index table of file " << streamRecord->finalFileName() <<
"\n";
845 ofstream&
out = *streamRecord->out();
848 cout <<
"Error while writing index table for file " << streamRecord->finalFileName() <<
". " 849 <<
"Resulting file might be corrupted. " 850 <<
"The error can be due to a lack of disk space.";
854 cout <<
"[LaserSorter " <<
now() <<
"] " 855 <<
"Closing file " << streamRecord->finalFileName() <<
".\n";
858 const std::string& tmpFileName = streamRecord->tmpFileName();
859 const std::string& finalFileName = streamRecord->finalFileName();
862 cout <<
"[LaserSorter " <<
now() <<
"] " 863 <<
"Renaming " << tmpFileName <<
" to " << finalFileName <<
".\n";
865 if (0 !=
rename(tmpFileName.c_str(), finalFileName.c_str())) {
866 cout <<
"[LaserSorter " <<
now() <<
"] " 867 <<
" Failed to rename output file from " << tmpFileName <<
" to " << finalFileName <<
". " << strerror(errno)
873 time_t
t =
time(
nullptr);
874 strftime(buf,
sizeof(buf),
"%F %R:%S", localtime(&t));
876 ifstream
f(
".watcherfile");
879 outputList_ << finalFileName <<
"\t" << buf <<
"\t" << inputFile << endl;
901 const unsigned nWord32 = data.
size() /
sizeof(uint32_t);
906 for (
unsigned iWord32 = 0; iWord32 < nWord32; iWord32 += 2) {
907 const uint32_t* data32 = ((
const uint32_t*)(data.
data())) + iWord32;
908 int dataType = (data32[1] >> 28) & 0xF;
913 if (0 == (dataType >> 2)) {
914 const int dccHeaderId = (data32[1] >> 24) & 0x3F;
915 if (dccHeaderId == 1) {
917 *dccLen = ((data32[0] >> 0) & 0xFFFFFF);
920 if ((dataType >> 2) == 3) {
941 std::vector<unsigned>& fedIds)
const {
942 fedIds.erase(fedIds.begin(), fedIds.end());
947 fedIds.push_back(
id);
950 edm::LogWarning(
"LaserSorter") <<
"Length error in data of FED " <<
id <<
" in event " <<
event.id()
951 <<
", Data of this FED dropped.";
971 result.push_back(fed);
977 uint32_t indexTablePos = out.tellp();
978 uint32_t nevts = indices.size();
981 out.write((
char*)&nevts,
sizeof(nevts));
982 const uint32_t reserved = 0;
983 out.write((
const char*)&reserved,
sizeof(reserved));
988 sort(indices.begin(), indices.end());
990 for (
unsigned i = 0;
i < indices.size(); ++
i) {
992 data[0] = indices[
i].orbit;
993 data[1] = indices[
i].filePos;
994 out.write((
char*)data,
sizeof(data));
1007 out.write((
char*)&indexTablePos,
sizeof(uint32_t));
1009 bool rc = !out.bad();
1019 stringstream errMsg;
1027 uint32_t fileHeader[2];
1028 s->read((
char*)&fileHeader[0],
sizeof(fileHeader));
1029 uint32_t indexTablePos = fileHeader[1];
1034 errMsg <<
"Failed to read header of file " << inName <<
".";
1036 *err = errMsg.str();
1040 s->seekg(indexTablePos);
1043 s->read((
char*)&nevts,
sizeof(nevts));
1046 errMsg <<
"Failed to read index table from file " << inName <<
".";
1048 *err = errMsg.str();
1052 errMsg <<
"Number of events indicated in event index of file " << inName <<
" (" << nevts <<
") " 1053 <<
"is unexpectively large.";
1055 *err = errMsg.str();
1058 outRcd.
indices()->resize(nevts);
1062 errMsg <<
"Failed to read index table from file " << inName <<
".";
1064 *err = errMsg.str();
1068 errMsg <<
"Number of events indicated in event index of file " << inName <<
" is unexpectively large.";
1070 *err = errMsg.str();
1076 cout <<
"[LaserSorter " <<
now() <<
"] " 1077 <<
"Orbit IDs of events " 1078 <<
"already contained in the file " << inName <<
":";
1079 for (
unsigned i = 0;
i < outRcd.
indices()->size(); ++
i) {
1081 cout <<
" " << setw(9) << (*outRcd.
indices())[
i].orbit;
1093 streampos
p = in.tellg();
1097 in.read((
char*)&data,
sizeof(data));
1101 magic[0] = data & 0xFF;
1102 magic[1] = (data >> 8) & 0xFF;
1103 magic[2] = (data >> 16) & 0xFF;
1106 const string lmf =
string(
"LMF");
1108 if (in.good() && lmf == magic) {
1109 vers = (data >> 24) & 0xFF;
1113 edm::LogWarning(
"LaserSorter") <<
"File " << fileName <<
"is not an LMF file.\n";
1124 time_t tsec = t >> 32;
1126 uint32_t tusec = t & 0xFFFFFFFF;
1127 strftime(buf,
sizeof(buf),
"%F %R %S s", localtime(&tsec));
1128 buf[
sizeof(buf) - 1] = 0;
1131 buf2 << (tusec + 500) / 1000;
1133 return string(buf) +
" " + buf2.str() +
" ms";
1150 if (
stat(fileName.c_str(), &
s) == 0) {
static const char runNumber_[]
edm::InputTag fedRawDataCollectionTag_
EventNumber_t event() const
OutStreamRecord * getStream(int fedId, edm::LuminosityBlockNumber_t lumiBlock)
std::vector< int > getFullyReadoutDccs(const FEDRawDataCollection &data) const
int maxFullReadoutDccError_
double nRestoredDcc
number of events whose DCC ID was restored based on FED block sizes
static const char *const detailedTrigNames[]
static std::string toString(uint64_t t)
static const int ecalDccFedIdMin_
static const int indexOffset32_
bool writeIndexTable(std::ofstream &out, std::vector< IndexRecord > &indices)
bool readIndexTable(std::ifstream &in, std::string &inName, OutStreamRecord &outRcd, std::string *err)
bool writeEvent(OutStreamRecord &out, const edm::Event &event, int detailedTriggerType, const FEDRawDataCollection &data)
edm::RunNumber_t runNumber_
void writeFileHeader(std::ofstream &out)
int bunchCrossing() const
std::ofstream * out() const
edm::LuminosityBlockNumber_t luminosityBlock() const
void streamFileName(int fedId, edm::LuminosityBlockNumber_t lumiBlock, std::string &tmpName, std::string &finalName)
edm::LuminosityBlockNumber_t lumiBlock_
std::set< uint32_t > & excludedOrbit()
void getOutputFedList(const edm::Event &event, const FEDRawDataCollection &data, std::vector< unsigned > &fedIds) const
static const int matacqFedId_
OutStreamList::iterator closeOutStream(OutStreamList::iterator streamRecord)
unsigned int LuminosityBlockNumber_t
std::string finalFileName() const
size_t size() const
Lenght of the data buffer in bytes.
std::vector< std::string > fedSubDirs_
struct timeval orbitZeroTime_
edm::LuminosityBlockNumber_t startingLumiBlock() const
const FEDRawData & FEDData(int fedid) const
retrieve data for fed
double nRead
number of events read out
unsigned char formatVersion_
std::vector< IndexRecord > * indices()
static const struct timeval nullTime
int dcc2Lme(int dccNum, int dccSide)
Abs< T >::type abs(const T &t)
bool writeFedBlock(std::ofstream &out, const FEDRawData &data)
std::string outputListFile_
int getOrbitFromDcc(const edm::Handle< FEDRawDataCollection > &rawdata) const
void beginRun(edm::Run const &, edm::EventSetup const &) override
bool overWriteLumiBlockId_
static const unsigned maxEvents_
void closeOldStreams(edm::LuminosityBlockNumber_t lumiBlock)
static const size_t indexReserve_
static const stats_t stats_init
int getDetailedTriggerType(const edm::Handle< FEDRawDataCollection > &rawdata, double *proba=nullptr)
LaserSorter(const edm::ParameterSet &)
unsigned long long uint64_t
static const int ecalDccFedIdMax_
std::ofstream outputList_
int readFormatVersion(std::ifstream &in, const std::string &fileName)
int orbitCountInALumiBlock_
struct LaserSorter::stats_t stats_
char data[epos_bytes_allocation]
bool writeEventHeader(std::ofstream &out, const edm::Event &evt, int fedId, unsigned nFeds)
time_t getTimeStamp() const
const unsigned char * data() const
Return a const pointer to the beginning of the data buffer.
OutStreamList::iterator createOutStream(int fedId, edm::LuminosityBlockNumber_t lumiBlock)
edm::LuminosityBlockNumber_t lumiBlockPrev_
static const char *const colorNames[]
int iNoFullReadoutDccError_
void restoreStreamsOfLumiBlock(int lumiBlock)
T result(double *proba) const
double nWritten
number of events written out
edm::EDGetTokenT< FEDRawDataCollection > fedRawDataCollectionToken_
unsigned dccId(DetId const &)
OutStreamList outStreamList_
TimeValue_t value() const
edm::Timestamp time() const
void analyze(const edm::Event &, const edm::EventSetup &) override
bool renameAsBackup(const std::string &fileName, std::string &newFileName)
bool isDccEventEmpty(const FEDRawData &data, size_t *dccLen=nullptr, int *nTowerBlocks=nullptr) const