Skip to content
This repository was archived by the owner on Oct 30, 2025. It is now read-only.

Commit 028c454

Browse files
committed
groot/rtree: add support for reading packed-struct
Fixes #879. Signed-off-by: Sebastien Binet <[email protected]>
1 parent 236817c commit 028c454

File tree

4 files changed

+262
-7
lines changed

4 files changed

+262
-7
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright ©2025 The go-hep Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build ignore
6+
7+
package main
8+
9+
import (
10+
"flag"
11+
"log"
12+
13+
"go-hep.org/x/hep/groot/internal/rtests"
14+
)
15+
16+
var (
17+
root = flag.String("f", "test-bufevt.root", "output ROOT file")
18+
split = flag.Int("split", 0, "default split-level for TTree")
19+
)
20+
21+
func main() {
22+
flag.Parse()
23+
24+
out, err := rtests.RunCxxROOT("gentree", []byte(script), *root, *split)
25+
if err != nil {
26+
log.Fatalf("could not run ROOT macro:\noutput:\n%v\nerror: %+v", string(out), err)
27+
}
28+
}
29+
30+
const script = `
31+
#include <string.h>
32+
#include <stdio.h>
33+
34+
const int ARRAYSZ = 10;
35+
const int MAXSLICE = 20;
36+
const int MAXSTR = 32;
37+
38+
#define OFFSET 0
39+
40+
struct __attribute__((packed)) Event {
41+
bool Bool;
42+
char Str[6];
43+
int8_t I8;
44+
int16_t I16;
45+
int32_t I32;
46+
int64_t I64;
47+
int64_t G64;
48+
uint8_t U8;
49+
uint16_t U16;
50+
uint32_t U32;
51+
uint64_t U64;
52+
uint64_t UGG;
53+
float F32;
54+
double F64;
55+
56+
bool ArrayBs[ARRAYSZ];
57+
int8_t ArrayI8[ARRAYSZ];
58+
int16_t ArrayI16[ARRAYSZ];
59+
int32_t ArrayI32[ARRAYSZ];
60+
int64_t ArrayI64[ARRAYSZ];
61+
int64_t ArrayG64[ARRAYSZ];
62+
uint8_t ArrayU8[ARRAYSZ];
63+
uint16_t ArrayU16[ARRAYSZ];
64+
uint32_t ArrayU32[ARRAYSZ];
65+
uint64_t ArrayU64[ARRAYSZ];
66+
uint64_t ArrayUGG[ARRAYSZ];
67+
float ArrayF32[ARRAYSZ];
68+
double ArrayF64[ARRAYSZ];
69+
70+
};
71+
72+
void gentree(const char* fname, int splitlvl = 99) {
73+
int bufsize = 32000;
74+
int evtmax = 10;
75+
76+
auto f = TFile::Open(fname, "RECREATE");
77+
auto t = new TTree("tree", "my tree title");
78+
79+
Event e;
80+
t->Branch("Event",&e,
81+
"B/O"
82+
":Str[6]/C"
83+
":I8/B:I16/S:I32/I:I64/L:G64/G"
84+
":U8/b:U16/s:U32/i:U64/l:UGG/g"
85+
":F32/F:F64/D"
86+
87+
// static arrays
88+
":ArrBs[10]/O"
89+
":ArrI8[10]/B:ArrI16[10]/S:ArrI32[10]/I:ArrI64[10]/L:ArrG64[10]/G"
90+
":ArrU8[10]/b:ArrU16[10]/s:ArrU32[10]/i:ArrU64[10]/l:ArrUGG[10]/g"
91+
":ArrF32[10]/F:ArrF64[10]/D"
92+
);
93+
94+
for (int j = 0; j != evtmax; j++) {
95+
int i = j + OFFSET;
96+
e.Bool = (i % 2) == 0;
97+
strncpy(e.Str, TString::Format("str-%d\0", i).Data(), 32);
98+
e.I8 = -i;
99+
e.I16 = -i;
100+
e.I32 = -i;
101+
e.I64 = -i;
102+
e.G64 = -i;
103+
e.U8 = i;
104+
e.U16 = i;
105+
e.U32 = i;
106+
e.U64 = i;
107+
e.UGG = i;
108+
e.F32 = float(i);
109+
e.F64 = double(i);
110+
111+
for (int ii = 0; ii != ARRAYSZ; ii++) {
112+
e.ArrayBs[ii] = ii == i;
113+
e.ArrayI8[ii] = -i;
114+
e.ArrayI16[ii] = -i;
115+
e.ArrayI32[ii] = -i;
116+
e.ArrayI64[ii] = -i;
117+
e.ArrayG64[ii] = -i;
118+
e.ArrayU8[ii] = i;
119+
e.ArrayU16[ii] = i;
120+
e.ArrayU32[ii] = i;
121+
e.ArrayU64[ii] = i;
122+
e.ArrayUGG[ii] = i;
123+
e.ArrayF32[ii] = float(i);
124+
e.ArrayF64[ii] = double(i);
125+
}
126+
127+
t->Fill();
128+
}
129+
130+
f->Write();
131+
f->Close();
132+
133+
exit(0);
134+
}
135+
`

groot/rtree/reader.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package rtree
77
import (
88
"fmt"
99
"io"
10+
"reflect"
11+
"strings"
1012

1113
"go-hep.org/x/hep/groot/rtree/rfunc"
1214
)
@@ -196,28 +198,46 @@ func (r *Reader) Formula(f rfunc.Formula) (rfunc.Formula, error) {
196198
}
197199

198200
func sanitizeRVars(t Tree, rvars []ReadVar) ([]ReadVar, error) {
201+
rvs := make([]ReadVar, 0, len(rvars))
199202
for i := range rvars {
200203
rvar := &rvars[i]
201204
if rvar.Leaf == "" {
202205
rvar.Leaf = rvar.Name
203206
}
204207
if rvar.count != "" {
208+
rvs = append(rvs, *rvar)
205209
continue
206210
}
207211
br := t.Branch(rvar.Name)
208212
if br == nil {
209213
return nil, fmt.Errorf("rtree: tree %q has no branch named %q", t.Name(), rvar.Name)
210214
}
211215
leaf := br.Leaf(rvar.Leaf)
212-
if leaf == nil {
213-
continue
214-
}
215-
lfc := leaf.LeafCount()
216-
if lfc != nil {
217-
rvar.count = lfc.Name()
216+
switch {
217+
case leaf == nil:
218+
kind := reflect.ValueOf(rvar.Value).Elem().Kind()
219+
switch {
220+
case kind == reflect.Struct && strings.Contains(br.Title(), ":"):
221+
// maybe a branch with a contiguous buffer of bytes.
222+
subs := ReadVarsFromStruct(rvar.Value)
223+
for i := range subs {
224+
sub := &subs[i]
225+
sub.Name = rvar.Name
226+
sub.leaf = br.Leaf(sub.Leaf)
227+
}
228+
rvs = append(rvs, subs...)
229+
default:
230+
rvs = append(rvs, *rvar)
231+
}
232+
default:
233+
lfc := leaf.LeafCount()
234+
if lfc != nil {
235+
rvar.count = lfc.Name()
236+
}
237+
rvs = append(rvs, *rvar)
218238
}
219239
}
220-
return rvars, nil
240+
return rvs, nil
221241
}
222242

223243
type reader interface {

groot/rtree/reader_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,106 @@ func TestReaderStruct(t *testing.T) {
320320
}
321321
}
322322

323+
func TestReaderWithBufferEvent(t *testing.T) {
324+
type Event struct {
325+
B bool `groot:"B"`
326+
Str string `groot:"Str"`
327+
I8 int8 `groot:"I8"`
328+
I16 int16 `groot:"I16"`
329+
I32 int32 `groot:"I32"`
330+
I64 int64 `groot:"I64"`
331+
U8 uint8 `groot:"U8"`
332+
U16 uint16 `groot:"U16"`
333+
U32 uint32 `groot:"U32"`
334+
U64 uint64 `groot:"U64"`
335+
F32 float32 `groot:"F32"`
336+
F64 float64 `groot:"F64"`
337+
ArrBs [10]bool `groot:"ArrBs[10]"`
338+
ArrI8 [10]int8 `groot:"ArrI8[10]"`
339+
ArrI16 [10]int16 `groot:"ArrI16[10]"`
340+
ArrI32 [10]int32 `groot:"ArrI32[10]"`
341+
ArrI64 [10]int64 `groot:"ArrI64[10]"`
342+
ArrU8 [10]uint8 `groot:"ArrU8[10]"`
343+
ArrU16 [10]uint16 `groot:"ArrU16[10]"`
344+
ArrU32 [10]uint32 `groot:"ArrU32[10]"`
345+
ArrU64 [10]uint64 `groot:"ArrU64[10]"`
346+
ArrF32 [10]float32 `groot:"ArrF32[10]"`
347+
ArrF64 [10]float64 `groot:"ArrF64[10]"`
348+
}
349+
wantEvent := func(i int64) (evt Event) {
350+
evt.B = i%2 == 0
351+
evt.Str = fmt.Sprintf("str-%d", i)
352+
evt.I8 = int8(-i)
353+
evt.I16 = int16(-i)
354+
evt.I32 = int32(-i)
355+
evt.I64 = int64(-i)
356+
evt.U8 = uint8(i)
357+
evt.U16 = uint16(i)
358+
evt.U32 = uint32(i)
359+
evt.U64 = uint64(i)
360+
evt.F32 = float32(i)
361+
evt.F64 = float64(i)
362+
for ii := range evt.ArrI32 {
363+
evt.ArrBs[ii] = ii == int(i)
364+
evt.ArrI8[ii] = int8(-i)
365+
evt.ArrI16[ii] = int16(-i)
366+
evt.ArrI32[ii] = int32(-i)
367+
evt.ArrI64[ii] = int64(-i)
368+
evt.ArrU8[ii] = uint8(i)
369+
evt.ArrU16[ii] = uint16(i)
370+
evt.ArrU32[ii] = uint32(i)
371+
evt.ArrU64[ii] = uint64(i)
372+
evt.ArrF32[ii] = float32(i)
373+
evt.ArrF64[ii] = float64(i)
374+
}
375+
return evt
376+
}
377+
378+
files := []string{
379+
"../testdata/x-flat-bufevt.root",
380+
}
381+
for i := range files {
382+
fname := files[i]
383+
t.Run(fname, func(t *testing.T) {
384+
t.Parallel()
385+
386+
f, err := riofs.Open(fname)
387+
if err != nil {
388+
t.Fatal(err.Error())
389+
}
390+
defer f.Close()
391+
392+
obj, err := f.Get("tree")
393+
if err != nil {
394+
t.Fatal(err)
395+
}
396+
tree := obj.(Tree)
397+
398+
var (
399+
want = wantEvent
400+
data Event
401+
)
402+
r, err := NewReader(tree, []ReadVar{{Name: "Event", Value: &data}})
403+
if err != nil {
404+
t.Fatal(err)
405+
}
406+
defer r.Close()
407+
err = r.Read(func(ctx RCtx) error {
408+
if got, want := data, want(ctx.Entry); !reflect.DeepEqual(got, want) {
409+
return fmt.Errorf(
410+
"entry[%d]:\ngot= %#v\nwant=%#v\n",
411+
ctx.Entry, got, want,
412+
)
413+
}
414+
return nil
415+
})
416+
if err != nil && err != io.EOF {
417+
t.Fatal(err)
418+
}
419+
})
420+
}
421+
}
422+
323423
func TestReaderVars(t *testing.T) {
324424
files := []string{
325425
"../testdata/x-flat-tree.root",

groot/testdata/x-flat-bufevt.root

7.23 KB
Binary file not shown.

0 commit comments

Comments
 (0)