Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ units = ["units-db"]
units-db = []
timezone = ["timezone-db"]
timezone-db = []
encoders = ["value", "json", "zinc"]
encoders = ["value", "json", "zinc", "brio"]
json = ["json-encoding", "json-decoding"]
json-encoding = []
json-decoding = []
zinc = ["zinc-encoding", "zinc-decoding"]
zinc-encoding = []
zinc-decoding = []
brio = ["brio-encoding", "brio-decoding"]
brio-encoding = []
brio-decoding = []

[dependencies]
serde = "1.0"
Expand Down
Binary file added benches/brio/points.brio
Binary file not shown.
28 changes: 28 additions & 0 deletions benches/gen_brio_fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (C) 2020 - 2022, J2 Innovations

//! Generates `benches/brio/points.brio` from `benches/json/points.json`.
//!
//! Run with:
//!
//! ```
//! cargo run --benches gen_brio_fixture --features brio
//! ```

use libhaystack::encoding::brio::encode::ToBrio;
use libhaystack::haystack::val::{Grid, Value};
use std::fs;

fn main() {
let json = fs::read_to_string("benches/json/points.json")
.expect("cannot read benches/json/points.json");

let value: Value = serde_json::from_str(&json).expect("JSON parse failed");
let grid = Grid::try_from(&value).expect("not a Grid");

let bytes = Value::from(grid).to_brio_vec().expect("brio encode failed");

fs::create_dir_all("benches/brio").expect("cannot create benches/brio");
fs::write("benches/brio/points.brio", &bytes).expect("cannot write benches/brio/points.brio");

println!("Wrote {} bytes to benches/brio/points.brio", bytes.len());
}
21 changes: 20 additions & 1 deletion benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use criterion::{criterion_group, criterion_main, Criterion};

use libhaystack::haystack::encoding::brio::decode::from_brio;
use libhaystack::haystack::encoding::zinc::decode::*;
use libhaystack::haystack::val::*;
use std::fs;
Expand Down Expand Up @@ -37,7 +38,25 @@ fn criterion_zinc_parse(bench: &mut Criterion) {
});
}

criterion_group!(benches, criterion_zinc_parse, criterion_json_parse);
fn criterion_brio_parse(bench: &mut Criterion) {
let bytes = fs::read("benches/brio/points.brio").expect("Invalid brio test file");
bench.bench_function("Brio parse points", |b| {
b.iter(|| {
let value = from_brio(&mut bytes.as_slice()).expect("Value");

let grid = Grid::try_from(&value).expect("Grid");

assert!(!grid.is_empty());
});
});
}

criterion_group!(
benches,
criterion_zinc_parse,
criterion_json_parse,
criterion_brio_parse
);
criterion_main!(benches);

#[cfg(never)]
Expand Down
173 changes: 173 additions & 0 deletions fan/BrioGen.fan
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//
// Copyright (c) 2026, J2 Innovations
// All Rights Reserved
//
// History:
// 25 Feb 26 Gareth Johnson Creation
//

using haystack

**
** In order to test Brio (binary encoding/decoding for haystack data), we ideally need
** to generate byte dumps of all the various haystack data types from Haxall (https://haxall.io).
** Running this Fantom code will then generate an output that can be used in
** src/haystack/encoding/brio/haxall_features.rs for further testing.
**
class BrioGen {

Buf encode(Obj? val) {
buf := Buf()
BrioWriter(buf.out).writeVal(val)
return buf.flip
}

Void p(Str name, Obj? val) {
echo("${name}|${encode(val).toHex}")
}

static Void main() {
g := BrioGen()
// scalars
g.p("null", null)
g.p("marker", Marker.val)
g.p("na", NA.val)
g.p("remove", Remove.val)
g.p("false", false)
g.p("true", true)

// numbers - I2 range (-32767..32767)
g.p("n_zero", Number(0f))
g.p("n_i2_42", Number(42f))
g.p("n_i2_neg", Number(-1f))
g.p("n_i2_max", Number(32767f))
g.p("n_i2_min", Number(-32767f))

// numbers - I4 range
g.p("n_i4_pos", Number(32768f))
g.p("n_i4_neg", Number(-32768f))
g.p("n_i4_max", Number(2147483647f))
g.p("n_i4_min", Number(-2147483648f))

// numbers - F8 (float / out of i4 range)
g.p("n_f8_pi", Number(3.141592653589793f))
g.p("n_f8_big", Number(2147483648f))
g.p("n_f8_bigneg", Number(-2147483649f))

// numbers with const-table units
g.p("n_degF", Number(98.6f, Unit("\u00b0F")))
g.p("n_kW", Number(1500f, Unit("kW")))
g.p("n_kWh", Number(99f, Unit("kWh")))
g.p("n_degC", Number(22f, Unit("\u00b0C")))
g.p("n_pct", Number(75f, Unit("%")))
g.p("n_cfm", Number(400f, Unit("cfm")))

// strings
g.p("str_empty", "")
g.p("str_hello", "hello")
g.p("str_ny", "New_York")
g.p("str_siteRef", "siteRef")
g.p("str_dis", "dis")
g.p("str_cafe", "caf\u00e9")
g.p("str_degF", "temp \u00b0F")

// URIs
g.p("uri_http", `http://example.com/`)
g.p("uri_path", `a/b/c`)

// Refs - I8 form (standard 8-byte XXXXXXXX-YYYYYYYY)
g.p("ref_i8_nodis", Ref("1deb31b8-7508b187", null))
g.p("ref_i8_dis", Ref("1deb31b8-7508b187", "hi!"))
g.p("ref_i8_dis2", Ref("cafebabe-deadbeef", "Site \u0394"))

// Refs - STR form (non-standard id)
g.p("ref_str_nodis", Ref("1debX1b8-7508b187", null))
g.p("ref_str_dis", Ref("custom.ref", "My Equip"))

// Dates
g.p("date_2015", Date(2015, Month.nov, 30))
g.p("date_2000", Date(2000, Month.jan, 1))
g.p("date_1970", Date(1970, Month.jan, 1))
g.p("date_1950", Date(1950, Month.jun, 7))
g.p("date_2099", Date(2099, Month.dec, 31))

// Times
g.p("time_midnight", Time(0, 0, 0, 0))
g.p("time_noon", Time(12, 0, 0, 0))
g.p("time_hms", Time(15, 6, 13, 0))
g.p("time_ms", Time(15, 6, 13, 123000000))

// DateTimes - I4 (no sub-second), const tz
g.p("dt_i4_ny", DateTime.fromStr("2015-11-30T12:03:57-05:00 New_York"))
g.p("dt_i4_utc", DateTime.fromStr("2021-06-15T12:00:00Z UTC"))
g.p("dt_i4_pre2k", DateTime.fromStr("1999-06-07T01:02:00-04:00 New_York"))
g.p("dt_i4_pre70", DateTime.fromStr("1950-06-07T01:02:00-04:00 New_York"))

// DateTimes - I4, non-const tz
g.p("dt_i4_warsaw", DateTime.fromStr("2000-01-01T00:00:00+01:00 Warsaw"))

// DateTimes - I8 (with sub-second), const tz
g.p("dt_i8_ny_ms", DateTime.fromStr("2015-11-30T12:02:33.378-05:00 New_York"))
g.p("dt_i8_ny_us", DateTime.fromStr("2015-11-30T12:03:57.000123-05:00 New_York"))
g.p("dt_i8_pre70", DateTime.fromStr("1950-06-07T01:02:00.123-04:00 New_York"))

// DateTimes - I8, non-const tz
g.p("dt_i8_warsaw", DateTime.fromStr("2000-01-01T00:00:00.832+01:00 Warsaw"))

// Coords
g.p("coord_pos", Coord(37.54f, 77.43f))
g.p("coord_neg", Coord(-17.535f, -149.569f))
g.p("coord_zero", Coord(0f, 0f))

// Symbols
g.p("sym_const", Symbol("coolingTower"))
g.p("sym_inline", Symbol("foo-bar"))
g.p("sym_site", Symbol("site"))

// XStr
g.p("xstr_foo", XStr("Foo", "bar"))

// Dicts
g.p("dict_empty", Etc.makeDict(Str:Obj?[:]))
g.p("dict_dis", Etc.makeDict(["dis": "Hello"]))
g.p("dict_site", Etc.makeDict(["dis": "Site", "site": Marker.val]))
g.p("dict_num", Etc.makeDict(["val": Number(123f, Unit("kW"))]))

// Lists
g.p("list_empty", Obj?[,])
g.p("list_marker", Obj?[Marker.val])
g.p("list_mixed", Obj?["hello", Number(42f), Marker.val])

// Grids
// Empty grid - no columns, no rows, no meta
gbEmpty := GridBuilder()
g.p("grid_empty", gbEmpty.toGrid)

// Grid with two columns only, no rows, no meta
gbCols := GridBuilder()
gbCols.addCol("dis")
gbCols.addCol("val")
g.p("grid_cols_only", gbCols.toGrid)

// Grid with column meta
gbColMeta := GridBuilder()
gbColMeta.addCol("dis", ["doc": "Display name"])
gbColMeta.addCol("val", ["doc": "Numeric value", "unit": "kW"])
g.p("grid_col_meta", gbColMeta.toGrid)

// Grid with rows
gbRows := GridBuilder()
gbRows.addCol("dis")
gbRows.addCol("val")
gbRows.addRow(["Site A", Number(100f, Unit("kW"))])
gbRows.addRow(["Site B", Number(200f, Unit("kW"))])
g.p("grid_rows", gbRows.toGrid)

// Grid with grid-level meta
gbMeta := GridBuilder()
gbMeta.setMeta(["dis": "My Grid", "view": Marker.val])
gbMeta.addCol("equip")
gbMeta.addRow([Marker.val])
g.p("grid_meta", gbMeta.toGrid)
}
}
Loading
Loading