Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions tree/ntupleutil/inc/ROOT/RNTupleImporter.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,30 @@ private:
RResult<void> Transform(const RImportBranch &branch, RImportField &field) final;
};

/// Transform a TClonesArray("CustomClass") to a std::vector<CustomClass>
struct RTClonesArrayTransformation : public RImportTransformation {
RTClonesArrayTransformation(std::size_t b, std::size_t f) : RImportTransformation(b, f) {}
~RTClonesArrayTransformation() override = default;
// Rule of five
RTClonesArrayTransformation(const RTClonesArrayTransformation &) = delete;
RTClonesArrayTransformation &operator=(const RTClonesArrayTransformation &) = delete;
RTClonesArrayTransformation(RTClonesArrayTransformation &&) = delete;
RTClonesArrayTransformation &operator=(RTClonesArrayTransformation &&) = delete;
RResult<void> Transform(const RImportBranch &branch, RImportField &field) final;
};

// Transform a TString into a std::string
struct RTStringTransformation : public RImportTransformation {
RTStringTransformation(std::size_t b, std::size_t f) : RImportTransformation(b, f) {}
~RTStringTransformation() override = default;
// Rule of five
RTStringTransformation(const RTStringTransformation &) = delete;
RTStringTransformation &operator=(const RTStringTransformation &) = delete;
RTStringTransformation(RTStringTransformation &&) = delete;
RTStringTransformation &operator=(RTStringTransformation &&) = delete;
RResult<void> Transform(const RImportBranch &branch, RImportField &field) final;
};

RNTupleImporter() = default;

std::unique_ptr<TFile> fSourceFile;
Expand Down
61 changes: 60 additions & 1 deletion tree/ntupleutil/src/RNTupleImporter.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
#include <string_view>

#include <TBranch.h>
#include <TClonesArray.h>
#include <TChain.h>
#include <TClass.h>
#include <TDataType.h>
#include <TLeaf.h>
#include <TLeafC.h>
#include <TLeafElement.h>
#include <TLeafObject.h>
#include <TString.h>

#include <cassert>
#include <cstdint>
Expand Down Expand Up @@ -76,6 +78,41 @@ ROOT::Experimental::RNTupleImporter::RCStringTransformation::Transform(const RIm
return RResult<void>::Success();
}

ROOT::RResult<void>
ROOT::Experimental::RNTupleImporter::RTClonesArrayTransformation::Transform(const RImportBranch &branch,
RImportField &field)
{
auto clonesArrayPtr = *reinterpret_cast<TClonesArray **>(branch.fBranchBuffer.get());
auto clonesArraySize = clonesArrayPtr->GetEntries();
// Get the size of the connection value type
auto collectionValueField = field.fField->GetConstSubfields()[0];
assert(collectionValueField);
auto fieldCollectionValueSize = collectionValueField->GetValueSize();
auto collectionSize = fieldCollectionValueSize * clonesArraySize;
// Copy the contents of the TClonesArray for the current entry into a temporary buffer
std::vector<std::byte> dest;
dest.reserve(collectionSize);
for (decltype(clonesArraySize) i = 0; i < clonesArraySize; i++) {
std::byte *clonesArrayEl = reinterpret_cast<std::byte *>(clonesArrayPtr->At(i));
std::copy(clonesArrayEl, clonesArrayEl + fieldCollectionValueSize, std::back_inserter(dest));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that properly handle the copy of the virtual table or any potential self referential pointers? And/or are the classes that may be using those features already rejected earlier (i.e. at the time of the model construction)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know for sure. If you tell me how to create a TTree reproducer of that type of object, I'll add it to the tests 👍 Regarding the second part of your question, if the RNTuple Fill method is reached with a type that RNTuple does not support, then the user will get a clear error, so that's safe.

}

// Assign the copied contents to the field buffer, later on it will be reinterpreted to the correct type
// std::vector<T> when reading
*reinterpret_cast<std::vector<std::byte> *>(field.fFieldBuffer) = dest;

return RResult<void>::Success();
}

ROOT::RResult<void>
ROOT::Experimental::RNTupleImporter::RTStringTransformation::Transform(const RImportBranch &branch, RImportField &field)
{
auto TStringBufPtr = reinterpret_cast<TString **>(branch.fBranchBuffer.get());
auto TStringPtr = *TStringBufPtr;
*reinterpret_cast<std::string *>(field.fFieldBuffer) = *TStringPtr;
return RResult<void>::Success();
}

std::unique_ptr<ROOT::Experimental::RNTupleImporter>
ROOT::Experimental::RNTupleImporter::Create(std::string_view sourceFileName, std::string_view treeName,
std::string_view destFileName)
Expand Down Expand Up @@ -205,6 +242,7 @@ ROOT::RResult<void> ROOT::Experimental::RNTupleImporter::PrepareSchema()
// For leaf count arrays, we expect to find a single leaf; we don't add a field right away but only
// later through a projection
bool isLeafCountArray = false;
bool isTClonesArray = false;
for (auto l : TRangeDynCast<TLeaf>(b->GetListOfLeaves())) {
if (l->IsA() == TLeafObject::Class()) {
return R__FAIL("unsupported: TObject branches, branch: " + std::string(b->GetName()));
Expand Down Expand Up @@ -232,6 +270,21 @@ ROOT::RResult<void> ROOT::Experimental::RNTupleImporter::PrepareSchema()
if (isClass)
fieldType = b->GetClassName();

if (fieldType == "TClonesArray") {
auto be = dynamic_cast<TBranchElement *>(b);
assert(be);
isTClonesArray = true;
fieldType = std::string("std::vector<") + be->GetClonesName() + ">";
fImportTransformations.emplace_back(
std::make_unique<RTClonesArrayTransformation>(fImportBranches.size(), fImportFields.size()));
}

if (fieldType == "TString") {
fieldType = "std::string";
fImportTransformations.emplace_back(
std::make_unique<RTStringTransformation>(fImportBranches.size(), fImportFields.size()));
}

if (isFixedSizeArray)
fieldType = "std::array<" + fieldType + "," + std::to_string(countval) + ">";

Expand All @@ -256,6 +309,12 @@ ROOT::RResult<void> ROOT::Experimental::RNTupleImporter::PrepareSchema()
// For classes, the branch buffer contains a pointer to object, which gets instantiated by TTree upon
// calling SetBranchAddress()
branchBufferSize = sizeof(void *) * countval;
// For TClonesArray, we create a value so that we can fill its buffer with the contents copied from the
// TTree branch
if (isTClonesArray || fieldType == "TString") {
f.fValue = std::make_unique<ROOT::RFieldBase::RValue>(field->CreateValue());
f.fFieldBuffer = f.fValue->GetPtr<void>().get();
}
} else if (isLeafCountArray) {
branchBufferSize = fLeafCountCollections[countleaf->GetName()].fMaxLength * field->GetValueSize();
} else {
Expand Down Expand Up @@ -301,7 +360,7 @@ ROOT::RResult<void> ROOT::Experimental::RNTupleImporter::PrepareSchema()
}

// If the TTree branch type and the RNTuple field type match, use the branch read buffer as RNTuple write buffer
if (!isLeafCountArray && !fImportFields.back().fFieldBuffer) {
if (!isLeafCountArray && !fImportFields.back().fFieldBuffer && !isTClonesArray) {
fImportFields.back().fFieldBuffer =
isClass ? *reinterpret_cast<void **>(ib.fBranchBuffer.get()) : ib.fBranchBuffer.get();
}
Expand Down
19 changes: 19 additions & 0 deletions tree/ntupleutil/test/CustomStructUtil.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <string>
#include <vector>

#include <TObject.h>

struct BaseUtil {
int base;
};
Expand Down Expand Up @@ -87,4 +89,21 @@ struct ComplexStructUtil : BaseUtil {
}
};

struct CustomStructObj final : public TObject {
int fInt;
float fFloat = 0.0;
std::vector<float> fVecFl;
std::vector<std::vector<float>> fVecVecFl;
std::string fStr;

CustomStructObj() = default;
CustomStructObj(int a1, float a2, const std::vector<float> &a3, const std::vector<std::vector<float>> &a4,
const std::string &a5)
: fInt(a1), fFloat(a2), fVecFl(a3), fVecVecFl(a4), fStr(a5)
{
}

ClassDefNV(CustomStructObj, 1);
};

#endif
2 changes: 2 additions & 0 deletions tree/ntupleutil/test/CustomStructUtilLinkDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
#pragma link C++ class ComplexStructUtil + ;
#pragma link C++ class std::vector < BaseUtil> + ;

#pragma link C++ class CustomStructObj+;

#endif
92 changes: 92 additions & 0 deletions tree/ntupleutil/test/ntuple_importer.cxx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <ROOT/RNTupleImporter.hxx>

#include <TClonesArray.h>
#include <TFile.h>
#include <TTree.h>
#include <TChain.h>
#include <TString.h>

#include <cstdio>
#include <string>
Expand Down Expand Up @@ -729,3 +731,93 @@ TEST(RNTUpleImporter, MaxEntries)
reader = RNTupleReader::Open("ntuple4", fileGuard.GetPath());
EXPECT_EQ(5U, reader->GetNEntries());
}

void check_customstructobj_eq(const CustomStructObj &a, const CustomStructObj &b)
{
EXPECT_EQ(a.fInt, b.fInt);
EXPECT_FLOAT_EQ(a.fFloat, b.fFloat);
EXPECT_EQ(a.fVecFl.size(), b.fVecFl.size());
for (std::size_t i = 0; i < a.fVecFl.size(); i++) {
EXPECT_FLOAT_EQ(a.fVecFl[i], b.fVecFl[i]);
}
EXPECT_EQ(a.fVecVecFl.size(), b.fVecVecFl.size());
for (std::size_t i = 0; i < a.fVecVecFl.size(); i++) {
EXPECT_EQ(a.fVecVecFl[i].size(), b.fVecVecFl[i].size());
for (std::size_t j = 0; j < a.fVecVecFl[i].size(); j++) {
EXPECT_FLOAT_EQ(a.fVecVecFl[i][j], b.fVecVecFl[i][j]);
}
}
EXPECT_EQ(a.fStr, b.fStr);
}

TEST(RNTUpleImporter, TClonesArray)
{
FileRaii fileGuard("test_ntuple_importer_tclonesarray.root");
{
auto f = std::make_unique<TFile>(fileGuard.GetPath().c_str(), "recreate");
auto t = std::make_unique<TTree>("tree", "tree");
auto ca = std::make_unique<TClonesArray>("CustomStructObj");
auto branchData = ca.get();
auto &caRef = *ca;
t->Branch("ca", &branchData);
new (caRef[0]) CustomStructObj{1, 2.2, {3.3, 4.4}, {{5.5}, {6.6, 7.7, 8.8}}, "first"};
new (caRef[1])
CustomStructObj{9, 11.11, {12.12, 13.13, 14.14}, {{15.15, 16.16}, {17.17, 18.18}, {19.19}}, "second"};
t->Fill();
f->Write();
}

auto importer = RNTupleImporter::Create(fileGuard.GetPath(), "tree", fileGuard.GetPath());

importer->SetNTupleName("ntuple");
importer->SetIsQuiet(true);
importer->Import();

auto reader = RNTupleReader::Open("ntuple", fileGuard.GetPath());
EXPECT_EQ(reader->GetNEntries(), 1);

auto ca = reader->GetModel().GetDefaultEntry().GetPtr<std::vector<CustomStructObj>>("ca");
reader->LoadEntry(0);

CustomStructObj refFirst{1, 2.2, {3.3, 4.4}, {{5.5}, {6.6, 7.7, 8.8}}, "first"};
CustomStructObj refSecond{9, 11.11, {12.12, 13.13, 14.14}, {{15.15, 16.16}, {17.17, 18.18}, {19.19}}, "second"};

EXPECT_EQ(ca->size(), 2);
check_customstructobj_eq(ca->at(0), refFirst);
check_customstructobj_eq(ca->at(1), refSecond);
}

TEST(RNTupleImporter, TString)
{
FileRaii fileGuard("test_ntuple_importer_tstring.root");
const auto nEntries = 5;
std::vector<std::string> ref(nEntries);
for (auto i = 0; i < nEntries; i++) {
ref[i] = "string_" + std::to_string(i);
}
{
auto f = std::make_unique<TFile>(fileGuard.GetPath().c_str(), "recreate");
auto t = std::make_unique<TTree>("tree", "tree");
TString mystr{};
t->Branch("str", &mystr);
for (const auto &str : ref) {
mystr = str;
t->Fill();
}
f->Write();
}

auto importer = RNTupleImporter::Create(fileGuard.GetPath(), "tree", fileGuard.GetPath());
importer->SetNTupleName("ntuple");
importer->SetIsQuiet(true);
importer->Import();

auto reader = RNTupleReader::Open("ntuple", fileGuard.GetPath());
EXPECT_EQ(reader->GetNEntries(), nEntries);

auto mystr = reader->GetModel().GetDefaultEntry().GetPtr<std::string>("str");
for (auto i = 0; i < nEntries; i++) {
reader->LoadEntry(i);
EXPECT_EQ(*mystr, ref[i]);
}
}
Loading