@@ -1503,6 +1503,10 @@ void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &i
1503
1503
}
1504
1504
}
1505
1505
1506
+ void SetEmptyBranchesHelper (TTree *inputTree, TTree &outputTree, RBranchSet &outputBranches,
1507
+ const std::string &inputBranchName, const std::string &outputBranchName,
1508
+ const std::type_info &typeInfo, int basketSize);
1509
+
1506
1510
// / Ensure that the TTree with the resulting snapshot can be written to the target TFile. This means checking that the
1507
1511
// / TFile can be opened in the mode specified in `opts`, deleting any existing TTrees in case
1508
1512
// / `opts.fOverwriteIfExists = true`, or throwing an error otherwise.
@@ -1618,6 +1622,18 @@ public:
1618
1622
(void )expander; // avoid unused variable warnings for older compilers such as gcc 4.9
1619
1623
}
1620
1624
1625
+ template <std::size_t ... S>
1626
+ void SetEmptyBranches (TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1627
+ {
1628
+ RBranchSet outputBranches{};
1629
+ // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1630
+ int expander[] = {(SetEmptyBranchesHelper (inputTree, outputTree, outputBranches, fInputBranchNames [S],
1631
+ fOutputBranchNames [S], typeid (ColTypes), fOptions .fBasketSize ),
1632
+ 0 )...,
1633
+ 0 };
1634
+ (void )expander;
1635
+ }
1636
+
1621
1637
void Initialize ()
1622
1638
{
1623
1639
fOutputFile .reset (
@@ -1648,6 +1664,12 @@ public:
1648
1664
assert (fOutputTree != nullptr );
1649
1665
assert (fOutputFile != nullptr );
1650
1666
1667
+ // There were no entries to fill the TTree with (either the input TTree was empty or no event passed after
1668
+ // filtering). We have already created an empty TTree, now also create the branches to preserve the schema
1669
+ if (fOutputTree ->GetEntries () == 0 ) {
1670
+ using ind_t = std::index_sequence_for<ColTypes...>;
1671
+ SetEmptyBranches (fInputTree , *fOutputTree , ind_t {});
1672
+ }
1651
1673
// use AutoSave to flush TTree contents because TTree::Write writes in gDirectory, not in fDirectory
1652
1674
fOutputTree ->AutoSave (" flushbaskets" );
1653
1675
// must destroy the TTree first, otherwise TFile will delete it too leading to a double delete
@@ -1715,6 +1737,7 @@ class R__CLING_PTRCHECK(off) SnapshotTTreeHelperMT : public RActionImpl<Snapshot
1715
1737
std::vector<bool > fIsDefine ;
1716
1738
ROOT::Detail::RDF::RLoopManager *fOutputLoopManager ;
1717
1739
ROOT::Detail::RDF::RLoopManager *fInputLoopManager ;
1740
+ TFile *fOutputFile ; // Non-owning view on the output file
1718
1741
1719
1742
public:
1720
1743
using ColumnTypes_t = TypeList<ColTypes...>;
@@ -1847,39 +1870,67 @@ public:
1847
1870
(void )expander; // avoid unused parameter warnings (gcc 12.1)
1848
1871
}
1849
1872
1873
+ template <std::size_t ... S>
1874
+ void SetEmptyBranches (TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1875
+ {
1876
+ RBranchSet outputBranches{};
1877
+ // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1878
+ int expander[] = {(SetEmptyBranchesHelper (inputTree, outputTree, outputBranches, fInputBranchNames [S],
1879
+ fOutputBranchNames [S], typeid (ColTypes), fOptions .fBasketSize ),
1880
+ 0 )...,
1881
+ 0 };
1882
+ (void )expander;
1883
+ }
1884
+
1850
1885
void Initialize ()
1851
1886
{
1852
1887
const auto cs = ROOT::CompressionSettings (fOptions .fCompressionAlgorithm , fOptions .fCompressionLevel );
1853
- auto out_file = TFile::Open (fFileName .c_str (), fOptions .fMode .c_str (), /* ftitle=*/ fFileName .c_str (), cs);
1854
- if (!out_file)
1888
+ auto outFile = std::unique_ptr<TFile>{
1889
+ TFile::Open (fFileName .c_str (), fOptions .fMode .c_str (), /* ftitle=*/ fFileName .c_str (), cs)};
1890
+ if (!outFile)
1855
1891
throw std::runtime_error (" Snapshot: could not create output file " + fFileName );
1856
- fMerger = std::make_unique<ROOT::TBufferMerger>(std::unique_ptr<TFile>(out_file));
1892
+ fOutputFile = outFile.get ();
1893
+ fMerger = std::make_unique<ROOT::TBufferMerger>(std::move (outFile));
1857
1894
}
1858
1895
1859
1896
void Finalize ()
1860
1897
{
1861
1898
assert (std::any_of (fOutputFiles .begin (), fOutputFiles .end (), [](const auto &ptr) { return ptr != nullptr ; }));
1862
1899
1863
- auto fileWritten = false ;
1864
1900
for (auto &file : fOutputFiles ) {
1865
1901
if (file) {
1866
1902
file->Write ();
1867
1903
file->Close ();
1868
- fileWritten = true ;
1869
1904
}
1870
1905
}
1871
1906
1872
- if (!fileWritten) {
1873
- Warning (" Snapshot" ,
1874
- " No input entries (input TTree was empty or no entry passed the Filters). Output TTree is empty." );
1907
+ // If there were no entries to fill the TTree with (either the input TTree was empty or no event passed after
1908
+ // filtering), create an empty TTree in the output file and create the branches to preserve the schema
1909
+ auto fullTreeName = fDirName .empty () ? fTreeName : fDirName + ' /' + fTreeName ;
1910
+ assert (fOutputFile && " Missing output file in Snapshot finalization." );
1911
+ if (!fOutputFile ->Get (fullTreeName.c_str ())) {
1912
+
1913
+ // First find in which directory we need to write the output TTree
1914
+ TDirectory *treeDirectory = fOutputFile ;
1915
+ if (!fDirName .empty ()) {
1916
+ treeDirectory = fOutputFile ->mkdir (fDirName .c_str (), " " , true );
1917
+ }
1918
+ ::TDirectory::TContext c{treeDirectory};
1919
+
1920
+ // Create the output TTree and create the user-requested branches
1921
+ auto outTree =
1922
+ std::make_unique<TTree>(fTreeName .c_str (), fTreeName .c_str (), fOptions .fSplitLevel , /* dir=*/ treeDirectory);
1923
+ using ind_t = std::index_sequence_for<ColTypes...>;
1924
+ SetEmptyBranches (fInputLoopManager ->GetTree (), *outTree, ind_t {});
1925
+
1926
+ fOutputFile ->Write ();
1875
1927
}
1876
1928
1877
1929
// flush all buffers to disk by destroying the TBufferMerger
1878
1930
fOutputFiles .clear ();
1879
1931
fMerger .reset ();
1880
1932
1881
1933
// Now connect the data source to the loop manager so it can be used for further processing
1882
- auto fullTreeName = fDirName .empty () ? fTreeName : fDirName + ' /' + fTreeName ;
1883
1934
fOutputLoopManager ->SetDataSource (std::make_unique<ROOT::Internal::RDF::RTTreeDS>(fullTreeName, fFileName ));
1884
1935
}
1885
1936
0 commit comments