From f3f6cff2b4e531f4b904666dfeeca21c5469fc0d Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 12 Feb 2026 11:40:47 +0100 Subject: [PATCH 1/5] Make function to read field --- modules/rntuple.mjs | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/modules/rntuple.mjs b/modules/rntuple.mjs index a6568f517..0bce994e4 100644 --- a/modules/rntuple.mjs +++ b/modules/rntuple.mjs @@ -852,8 +852,8 @@ class RNTupleDescriptorBuilder { } /** @summary Return array of columns for specified field */ - findColumns(name) { - const res = [], field = this.findField(name); + findColumns(field) { + const res = []; if (!field) return res; for (const colDesc of this.columnDescriptors) { @@ -1304,29 +1304,40 @@ async function rntupleProcess(rntuple, selector, args = {}) { }); } + function addFieldReading(field, tgtname) { + const columns = rntuple.builder.findColumns(field); + if (!columns?.length) + throw new Error(`No columns found for field '${field.fieldName}' in RNTuple`); + + const item = new ReaderItem(columns[0], tgtname); + item.assignReadFunc(); + handle.arr.push(item); + + if ((columns.length === 2) && (field.typeName === 'std::string')) { + const item2 = new ReaderItem(columns[1], tgtname); + item2.assignStringReader(item); + handle.arr.push(item2); + return item2; // second item performs complete reading of the string + } + + return item; + } + return readHeaderFooter(rntuple).then(res => { if (!res) throw new Error('Not able to read header for the RNtuple'); for (let i = 0; i < selector.numBranches(); ++i) { - const name = getSelectorFieldName(selector, i); + const name = getSelectorFieldName(selector, i), + tgtname = selector.nameOfBranch(i); if (!name) throw new Error(`Not able to extract name for field ${i}`); - const columns = rntuple.builder.findColumns(name); - if (!columns?.length) - throw new Error(`No columns found for field '${name}' in RNTuple`); + const field = rntuple.builder.findField(name); + if (!field) + throw new Error(`Field ${name} not found`); - const tgtname = selector.nameOfBranch(i), - item = new ReaderItem(columns[0], tgtname); - item.assignReadFunc(); - handle.arr.push(item); - - if (columns.length === 2) { - const item2 = new ReaderItem(columns[1], tgtname); - item2.assignStringReader(item); - handle.arr.push(item2); - } + addFieldReading(field, tgtname); } // calculate number of entries From 38ac247c2869433b07cf5496ae179fceca04fa95 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 12 Feb 2026 12:45:08 +0100 Subject: [PATCH 2/5] [rntuple] first implementation of std::vector reading Only will work for basic types --- modules/rntuple.mjs | 64 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/modules/rntuple.mjs b/modules/rntuple.mjs index 0bce994e4..3e0638d8f 100644 --- a/modules/rntuple.mjs +++ b/modules/rntuple.mjs @@ -851,6 +851,17 @@ class RNTupleDescriptorBuilder { } } + /** @summary Return all childs of specified field */ + findChildFields(field) { + const indx = this.fieldDescriptors.indexOf(field), res = []; + for (let n = 0; n < this.fieldDescriptors.length; ++n) { + const fld = this.fieldDescriptors[n]; + if ((fld !== field) && (fld.parentFieldId === indx)) + res.push(fld); + } + return res; + } + /** @summary Return array of columns for specified field */ findColumns(field) { const res = []; @@ -863,6 +874,7 @@ class RNTupleDescriptorBuilder { return res; } + } // class RNTupleDescriptorBuilder @@ -1105,6 +1117,7 @@ class ReaderItem { /** @summary identify if this item used as offset for std::string or similar */ is_offset_item() { return this.item1; } + /** @summary implements reading of std::string where item1 provides offsets */ assignStringReader(item1) { this.item1 = item1; this.off0 = 0; @@ -1130,7 +1143,7 @@ class ReaderItem { this.shift = function(entries) { if (entries > 0) { - this.item1.shift0(entries); + this.item1.shift0(entries - 1); this.item1.func0(this.$tgt); this.off0 = Number(this.$tgt[this.name]); this.shift_o(this.off0); @@ -1138,6 +1151,44 @@ class ReaderItem { }; } + /** @summary implement reading of std::vector where item1 provides elements numbers */ + assignVectorReader(item1) { + this.item1 = item1; + this.off0 = 0; + this.$tgt = {}; + + item1.func0 = item1.func; + item1.shift0 = item1.shift; + // assign noop + item1.func = item1.shift = () => {}; + + // remember own read function - they need to be used + this.func0 = this.func; + this.shift0 = this.shift; + + this.func = function(tgtobj) { + this.item1.func0(this.$tgt); + const off = Number(this.$tgt[this.name]); + let len = off - this.off0; + const arr = [], tmp = {}; + while (len-- > 0) { + this.func0(tmp); + arr.push(tmp[this.name]); + } + tgtobj[this.name] = arr; + this.off0 = off; + }; + + this.shift = function(entries) { + if (entries > 0) { + this.item1.shift0(entries - 1); + this.item1.func0(this.$tgt); + this.off0 = Number(this.$tgt[this.name]); + this.shift0(this.off0); + } + }; + } + collectPages(cluster_locations, dataToRead, itemsToRead, pagesToRead, emin, emax, elist) { const pages = cluster_locations[this.id].pages; @@ -1309,7 +1360,7 @@ async function rntupleProcess(rntuple, selector, args = {}) { if (!columns?.length) throw new Error(`No columns found for field '${field.fieldName}' in RNTuple`); - const item = new ReaderItem(columns[0], tgtname); + let item = new ReaderItem(columns[0], tgtname); item.assignReadFunc(); handle.arr.push(item); @@ -1317,7 +1368,14 @@ async function rntupleProcess(rntuple, selector, args = {}) { const item2 = new ReaderItem(columns[1], tgtname); item2.assignStringReader(item); handle.arr.push(item2); - return item2; // second item performs complete reading of the string + item = item2; // second item performs complete reading of the string + } + + const childs = rntuple.builder.findChildFields(field); + if ((childs.length === 1) && (field.typeName.indexOf('std::vector') === 0)) { + const item2 = addFieldReading(childs[0], tgtname); + item2.assignVectorReader(item); + item = item2; // second item makes actual reading of vector } return item; From ba45fcca95cf45e56a4d04885700625021d2ad47 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 12 Feb 2026 12:46:24 +0100 Subject: [PATCH 3/5] [rntuple] use branch dump for single field --- modules/draw/RNTuple.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/draw/RNTuple.mjs b/modules/draw/RNTuple.mjs index c1c1ee977..817f18595 100644 --- a/modules/draw/RNTuple.mjs +++ b/modules/draw/RNTuple.mjs @@ -10,13 +10,14 @@ async function drawRNTuple(dom, obj, opt) { const args = {}; let tuple; - if (obj?.$tuple) { + if (obj?.$tuple && obj.$field) { // case of fictional ROOT::RNTupleField tuple = obj.$tuple; args.expr = obj._name; - if (isStr(opt) && opt.indexOf('dump') === 0) + if (isStr(opt) && opt.indexOf('dump') === 0) { args.expr += '>>' + opt; - else if (opt) + args.branch = obj.$field; + } else if (opt) args.expr += opt; } else { tuple = obj; From db5281a9f3eb8963e07057f866e8558c3efb4d46 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 12 Feb 2026 12:48:37 +0100 Subject: [PATCH 4/5] [rntuple] avoid deep copt for field dump --- modules/draw/RNTuple.mjs | 1 + modules/tree.mjs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/draw/RNTuple.mjs b/modules/draw/RNTuple.mjs index 817f18595..f0c48f578 100644 --- a/modules/draw/RNTuple.mjs +++ b/modules/draw/RNTuple.mjs @@ -17,6 +17,7 @@ async function drawRNTuple(dom, obj, opt) { if (isStr(opt) && opt.indexOf('dump') === 0) { args.expr += '>>' + opt; args.branch = obj.$field; + args.copy_fields = false; // no need to copy fields, reading is simple } else if (opt) args.expr += opt; } else { diff --git a/modules/tree.mjs b/modules/tree.mjs index ff2abc99f..882938151 100644 --- a/modules/tree.mjs +++ b/modules/tree.mjs @@ -1128,7 +1128,7 @@ class TDrawSelector extends TSelector { this.leaf = args.leaf; // branch object remains, therefore we need to copy fields to see them all - this.copy_fields = ((args.branch.fLeaves?.arr.length > 1) || args.branch.fBranches?.arr.length) && !args.leaf; + this.copy_fields = args.copy_fields ?? (((args.branch.fLeaves?.arr.length > 1) || args.branch.fBranches?.arr.length) && !args.leaf); this.addBranch(branch, 'br0', args.direct_branch); // add branch From 6dc886ed980930b316a704b40271f57408e0fe2a Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 12 Feb 2026 12:50:45 +0100 Subject: [PATCH 5/5] Changes --- changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changes.md b/changes.md index d7d1dcb81..76a522073 100644 --- a/changes.md +++ b/changes.md @@ -2,6 +2,7 @@ ## Changes in dev +1. Support std::vector in `RNtuple` 1. Resort order of ranges in http request, fixing several long-standing problems #374 1. Implement for `TPie` 3d, text, title drawing including interactivity 1. Implement `TCanvas` support in `build3d` function #373