Compare commits

...

19 commits

Author SHA1 Message Date
Yehowshua Immanuel f2676673cd small update 2024-03-16 23:29:10 -04:00
Yehowshua Immanuel 133b1a2693
Sunsetting 2024-03-12 18:50:29 -04:00
Frans Skarman de897a5010
Merge pull request #36 from ThePerfectComputer/double_sided_query
Don't throw away the right hand time of changes
2023-12-13 08:56:15 +00:00
TheZoq2 e2e3541e3f Refactor the double-sided-query to make it less error-prone 2023-12-05 16:44:19 +01:00
TheZoq2 7d414f36dd Correctly compute next index 2023-12-05 14:59:04 +01:00
TheZoq2 9a8c2a03eb Don't throw away the right hand time of changes 2023-12-05 14:29:46 +01:00
Frans Skarman 3851c4e06c
Merge pull request #35 from ThePerfectComputer/scopefix
Add workaround for files produced by verilator which have annonymous scopes
2023-12-05 13:29:26 +00:00
TheZoq2 9ba53df728 Add workaround for files produced by verilator which have annonymous scopes 2023-11-03 19:54:26 +01:00
TheZoq2 7a2bed42a3 Add some clones and expose SignalErrors 2023-10-25 17:55:05 +02:00
TheZoq2 d42d01f9c3 Expose index 2023-10-23 18:15:30 +02:00
Frans Skarman f3e45f8497
Merge pull request #20 from ThePerfectComputer/name_width_separation
Separate name from width
2023-10-23 13:28:02 +00:00
TheZoq2 e7f2f661df Separate name from width 2023-10-23 15:27:47 +02:00
Frans Skarman 4f31ec842f
Merge pull request #33 from oscargus/bumpdependencies
Bump dependencies
2023-10-16 09:02:37 +00:00
Oscar Gustafsson a8ee52a11e Bump dependencies 2023-10-15 22:05:52 +02:00
Frans Skarman e3c60600f4
Merge pull request #32 from oscargus/maxsize
Increase maximum bit width
2023-10-11 08:20:08 +00:00
Oscar Gustafsson 9c54c3a295 Increase maximum bit width 2023-10-11 09:54:59 +02:00
Frans Skarman b9c507c9d8
Merge pull request #29 from oscargus/realparsing
Add support for parsing real values (to a String)
2023-10-10 14:15:50 +00:00
Oscar Gustafsson b886e5d26d Add support for parsing real values (to a String) 2023-10-10 16:08:09 +02:00
Frans Skarman 01eacd4028
Merge pull request #28 from ThePerfectComputer/revert-27-realvalues
Revert "Add support for real values"
2023-10-10 13:18:31 +00:00
9 changed files with 310 additions and 130 deletions

View file

@ -10,7 +10,7 @@ debug = 1
[dependencies]
num = "0.4"
clap = { version = "3.1.8", features = ["derive"] }
clap = { version = "4.4.6", features = ["derive"] }
chrono = "0.4"
# TODO : remove itertools once date parser is reworked.
itertools = "0.10.3"
itertools = "0.11"

View file

@ -1,4 +1,4 @@
Copyright - Yehowshua Immanuel
Copyright(2023) - Yehowshua Immanuel
# Vision
Imagine being able to visualize a CPU pipeline diagram by merely loading a simulation waveform dump, sprinkling in a bit of code, and dragging and dropping some diagram blocks into the visualizer. This project aims to offer such an experience.
@ -6,10 +6,7 @@ Imagine being able to visualize a CPU pipeline diagram by merely loading a simul
Since this project is written in Rust, it should also be able to run in the browser via web-assembly.
# Status
I hope to work on this more actively again soon.
The Zoq is is working on an excellent frontend call the Surfer. Check it out
[here](https://gitlab.com/surfer-project/surfer)!
As of January 2024, work on the Fastwave Backend is stalled. It has been a fun journey watching Fastwave enable the first iterations of the [surfer waveform viewer](https://surfer-project.org). Now surfer uses an even better backend called [Wellen](https://github.com/ekiwi/wellen?tab=readme-ov-file). Go check it out! I hear it's really good. Perhaps I will soon archive the Fastwave Backend.
Browser demo: https://app.surfer-project.org/

View file

@ -10,7 +10,6 @@ use fastwave_backend::parse_vcd;
#[derive(Parser)]
struct Cli {
/// The path to the file to read
#[clap(parse(from_os_str))]
path: std::path::PathBuf,
}

View file

@ -27,7 +27,7 @@ fn visit_all_scopes(vcd: &VCD) {
let SignalIdx(idx) = signal_idx;
indented_print(indent + 1, &format!("{},{}", signal.name(), idx));
}
visit_all_scope_children(child_scope_idx, vcd.clone(), indent + 1);
visit_all_scope_children(child_scope_idx, vcd, indent + 1);
}
}
}

View file

@ -5,7 +5,7 @@
mod vcd;
pub use vcd::parse::parse_vcd;
pub use vcd::signal::{Signal, SignalType, SignalValue};
pub use vcd::signal::{Signal, SignalType, SignalValue, SignalErrors};
pub use vcd::types::{Metadata, Timescale, Version};
pub use vcd::types::{ScopeIdx, SignalIdx, VCD};

View file

@ -71,10 +71,10 @@ pub(super) fn parse_events<R: std::io::Read>(
// handle the case of an n bit signal whose value must be parsed
"b" => {
let binary_value = &word[1..];
let observed_num_bits = u16::try_from(binary_value.len()).map_err(|_| {
let observed_num_bits = u32::try_from(binary_value.len()).map_err(|_| {
format!(
"Error near {}:{}, {cursor:?}. \
Found signal with more than 2^16 - 1 bits.",
Found signal with more than 2^32 - 1 bits.",
file!(),
line!()
)
@ -200,11 +200,11 @@ pub(super) fn parse_events<R: std::io::Read>(
format!("Error near {}:{}. num_bytes empty.", file!(), line!())
})?;
let mut curr_num_bytes =
u8::try_from(value_u8.len()).map_err(|_| {
u16::try_from(value_u8.len()).map_err(|_| {
format!(
"Error near {}:{}. \
Found signal {name} with with value change of greater \
than 2^16 - 1 bits on {cursor:?}.",
than 2^32 - 1 bits on {cursor:?}.",
file!(),
line!()
)
@ -492,7 +492,8 @@ pub(super) fn parse_events<R: std::io::Read>(
}
}?;
}
"s" => {
// Store real values as a string as well and let the user parse it to an f64
"s" | " S" | "r" | "R" => {
let val = word[1..].to_string();
let (hash, cursor) = next_word!(word_reader)?;
// lokup signal idx

View file

@ -104,10 +104,10 @@ pub(super) fn parse_var<R: std::io::Read>(
let num_bits = word
.parse::<usize>()
.unwrap_or_else(|_| panic!("{}", parse_err));
let num_bits = u16::try_from(num_bits).map_err(|_| {
let num_bits = u32::try_from(num_bits).map_err(|_| {
format!(
"Error near {}:{} while parsing vcd file at {cursor:?}. \
This signal has {num_bits} > 2^16 - 1 bits.",
This signal has {num_bits} > 2^32 - 1 bits.",
file!(),
line!()
)
@ -126,10 +126,12 @@ pub(super) fn parse_var<R: std::io::Read>(
// $var parameter 3 a IDLE $end
// ^^^^ - full_signal_name(can extend until $end)
let mut full_signal_name = Vec::<String>::new();
let mut size = None;
loop {
let (word, _) = next_word!(word_reader)?;
match word {
"$end" => break,
other if other.starts_with('[') => size = Some(other.to_string()),
_ => full_signal_name.push(word.to_string()),
}
}
@ -170,6 +172,7 @@ pub(super) fn parse_var<R: std::io::Read>(
.chain([full_signal_name])
.collect::<Vec<String>>(),
signal_type: var_type,
index: size,
signal_error: None,
num_bits,
num_bytes,
@ -286,87 +289,154 @@ fn parse_scopes_inner<R: std::io::Read>(
// $scope module reg_mag_i $end
// ^^^^^^^^^ - scope name
let (scope_name, _) = next_word!(word_reader)?;
// In some cases there are VCD files which have scopes without names.
// since these occur in the wild, we'll tolerate them even if it is unclear
// if it is supported or not by the spec.
if scope_name != "$end" {
let mut path = path.clone();
path.push(scope_name.to_string());
let mut path = path.clone();
path.push(scope_name.to_string());
let curr_scope_idx = ScopeIdx(vcd.all_scopes.len());
let curr_scope_idx = ScopeIdx(vcd.all_scopes.len());
// register this scope as a child of the current parent scope
// if there is a parent scope, or else we register this scope as
// root scope
match parent_scope_idx {
Some(ScopeIdx(parent_scope_idx)) => {
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap();
parent_scope.child_scopes.push(curr_scope_idx);
// register this scope as a child of the current parent scope
// if there is a parent scope, or else we register this scope as
// root scope
match parent_scope_idx {
Some(ScopeIdx(parent_scope_idx)) => {
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap();
parent_scope.child_scopes.push(curr_scope_idx);
}
None => vcd.root_scopes.push(curr_scope_idx),
}
None => vcd.root_scopes.push(curr_scope_idx),
}
// add this scope to list of existing scopes
vcd.all_scopes.push(Scope {
name: scope_name.to_string(),
self_idx: curr_scope_idx,
child_signals: vec![],
child_scopes: vec![],
});
// add this scope to list of existing scopes
vcd.all_scopes.push(Scope {
name: scope_name.to_string(),
self_idx: curr_scope_idx,
child_signals: vec![],
child_scopes: vec![],
});
// $scope module reg_mag_i $end
// ^^^^ - end keyword
ident(word_reader, "$end")?;
// $scope module reg_mag_i $end
// ^^^^ - end keyword
ident(word_reader, "$end")?;
loop {
let (word, cursor) = next_word!(word_reader)?;
let ParseResult { matched, residual } = tag(word, "$");
match matched {
// we hope that this word starts with a `$`
"$" => {
match residual {
"scope" => {
// recursive - parse inside of current scope tree
parse_scopes_inner(
word_reader,
Some(curr_scope_idx),
vcd,
signal_map,
&path,
)?;
}
"var" => {
parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?;
}
"upscope" => {
ident(word_reader, "$end")?;
break;
}
// we ignore comments
"comment" => loop {
if ident(word_reader, "$end").is_ok() {
loop {
let (word, cursor) = next_word!(word_reader)?;
let ParseResult { matched, residual } = tag(word, "$");
match matched {
// we hope that this word starts with a `$`
"$" => {
match residual {
"scope" => {
// recursive - parse inside of current scope tree
parse_scopes_inner(
word_reader,
Some(curr_scope_idx),
vcd,
signal_map,
&path,
)?;
}
"var" => {
parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?;
}
"upscope" => {
ident(word_reader, "$end")?;
break;
}
},
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{residual}` but expected \
`$scope`, `$var`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
// we ignore comments
"comment" => loop {
if ident(word_reader, "$end").is_ok() {
break;
}
},
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{residual}` but expected \
`$scope`, `$var`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
}
}
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
}
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
} else {
// We'll be conservative and only allow new scopes in this case, and make the nameless
// scope completely transparent. I.e.
// $scope module a $end
// $scope module $end
// $scope module b $end
// ...
// $upscope
// $upscope
// $upscope
// will create `a.b`
loop {
let (word, cursor) = next_word!(word_reader)?;
let ParseResult { matched, residual } = tag(word, "$");
match matched {
// we hope that this word starts with a `$`
"$" => {
match residual {
"scope" => {
// recursive - parse inside of current scope tree
parse_scopes_inner(
word_reader,
parent_scope_idx,
vcd,
signal_map,
&path,
)?;
}
"upscope" => {
ident(word_reader, "$end")?;
break;
}
// we ignore comments
"comment" => loop {
if ident(word_reader, "$end").is_ok() {
break;
}
},
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{residual}` in annonyoums scope but expected \
`$scope`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
}
}
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
}
}
}

View file

@ -11,7 +11,7 @@ use num::BigUint;
#[derive(Debug, Copy, Clone)]
pub struct LsbIdxOfTmstmpValOnTmln(pub(super) u32);
#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum SignalType {
Event,
Integer,
@ -40,6 +40,11 @@ pub enum SignalValue {
String(String),
}
pub struct QueryResult<T> {
pub current: Option<(TimeStamp, T)>,
pub next: Option<TimeStamp>,
}
pub struct Signal<'a>(pub(super) &'a SignalEnum);
impl<'a> Signal<'a> {
@ -48,6 +53,16 @@ impl<'a> Signal<'a> {
signal_enum.name()
}
pub fn name_with_index(&self) -> String {
let Signal(signal_enum) = &self;
signal_enum.name_with_index()
}
pub fn index(&self) -> Option<String> {
let Signal(signal_enum) = &self;
signal_enum.index()
}
pub fn path(&self) -> &[String] {
match self.0 {
SignalEnum::Data { path, .. } => path,
@ -67,11 +82,14 @@ impl<'a> Signal<'a> {
}
}
pub fn num_bits(&self) -> Option<u16> {
pub fn num_bits(&self) -> Option<u32> {
let Signal(signal_enum) = &self;
signal_enum.bits_required()
}
// NOTE: (zoq) I am removing thse because they aren't used in Surfer so I can't test them
// properly
/*
pub fn query_string_val_on_tmln(
&self,
desired_time: &BigUint,
@ -80,32 +98,33 @@ impl<'a> Signal<'a> {
let Signal(signal_enum) = &self;
signal_enum
.query_string_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals)
.map(|(val, _)| val)
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
}
pub fn query_num_val_on_tmln(
&self,
desired_time: &BigUint,
vcd: &types::VCD,
) -> Result<BigUint, SignalErrors> {
) -> Result<Option<BigUint>, SignalErrors> {
let Signal(signal_enum) = &self;
signal_enum
.query_num_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals)
.map(|(val, _)| val)
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
}
*/
pub fn query_val_on_tmln(
&self,
desired_time: &BigUint,
vcd: &types::VCD,
) -> Result<(TimeStamp, SignalValue), SignalErrors> {
) -> Result<QueryResult<SignalValue>, SignalErrors> {
let Signal(signal_enum) = &self;
let num_val = signal_enum.query_num_val_on_tmln(
let num_query_out = signal_enum.query_num_val_on_tmln(
desired_time,
&vcd.tmstmps_encoded_as_u8s,
&vcd.all_signals,
);
let str_val = signal_enum.query_string_val_on_tmln(
let str_query_out = signal_enum.query_string_val_on_tmln(
desired_time,
&vcd.tmstmps_encoded_as_u8s,
&vcd.all_signals,
@ -114,16 +133,44 @@ impl<'a> Signal<'a> {
// Both num and str will return the newest value that is closest to
// the desired time. If both have valid values, select the most recent
// one
match (num_val, str_val) {
(Ok((num_val, num_time)), Ok((str_val, str_time))) => {
if num_time > str_time {
Ok((num_time, SignalValue::BigUint(num_val)))
} else {
Ok((str_time, SignalValue::String(str_val)))
match (num_query_out, str_query_out) {
(Ok(num_result), Ok(str_result)) => {
let next = match (num_result.next, str_result.next) {
(Some(n), Some(s)) => Some(n.min(s)),
(Some(n), None) => Some(n),
(None, Some(s)) => Some(s),
(None, None) => None,
};
match (num_result.current, str_result.current) {
(Some((num_time, num_value)), Some((str_time, str_value))) => {
if num_time > str_time {
Ok(QueryResult {
current: Some((num_time, SignalValue::BigUint(num_value))),
next,
})
} else {
Ok(QueryResult {
current: Some((str_time, SignalValue::String(str_value))),
next,
})
}
}
(Some((num_time, num_val)), None) => Ok(QueryResult {
current: Some((num_time, SignalValue::BigUint(num_val))),
next,
}),
(None, Some((str_time, str_value))) => Ok(QueryResult {
current: Some((str_time, SignalValue::String(str_value))),
next,
}),
(None, None) => Ok(QueryResult {
current: None,
next,
}),
}
}
(Ok((num_val, time)), Err(_)) => Ok((time, SignalValue::BigUint(num_val))),
(Err(_), Ok((str_val, time))) => Ok((time, SignalValue::String(str_val))),
(_e, Err(e)) => Err(e),
(Err(e), _e) => Err(e),
}
}
@ -135,13 +182,16 @@ pub(super) enum SignalEnum {
name: String,
path: Vec<String>,
signal_type: SignalType,
/// The optional [start:end] part of the signal name that is sometimes
/// added to signals
index: Option<String>,
/// I've seen a 0 bit signal parameter in a xilinx
/// simulation before that gets assigned 1 bit values.
/// I consider this to be bad behavior. We capture such
/// errors in the following type:
signal_error: Option<String>,
num_bits: Option<u16>,
num_bytes: Option<u8>,
num_bits: Option<u32>,
num_bytes: Option<u16>,
/// TODO : may be able to remove self_idx
self_idx: SignalIdx,
/// A signal may take on a new value and hold that value
@ -179,10 +229,6 @@ pub(super) enum SignalEnum {
#[derive(Debug)]
pub enum SignalErrors {
PreTimeline {
desired_time: BigUint,
timeline_start_time: BigUint,
},
EmptyTimeline,
TimelineNotMultiple,
StrTmlnLenMismatch,
@ -218,6 +264,27 @@ impl SignalEnum {
}
.clone()
}
pub fn name_with_index(&self) -> String {
match self {
SignalEnum::Data {
name, index: None, ..
} => format!("{name}"),
SignalEnum::Data {
name,
index: Some(size),
..
} => format!("{name} {size}"),
SignalEnum::Alias { name, .. } => name.clone(),
}
}
pub fn index(&self) -> Option<String> {
match self {
SignalEnum::Data { index, .. } => index.clone(),
SignalEnum::Alias { .. } => None,
}
}
}
// helper functions ultimately used by Signal's query functions later on
@ -225,12 +292,12 @@ impl SignalEnum {
/// Computes the bytes required to store a signal's numerical value
/// using the num_bits which another function would provide from
/// the num_bits field of the Signal::Data variant.
pub(super) fn bytes_required(num_bits: u16, name: &String) -> Result<u8, String> {
pub(super) fn bytes_required(num_bits: u32, name: &String) -> Result<u16, String> {
let bytes_required = (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 };
let bytes_required = u8::try_from(bytes_required).map_err(|_| {
let bytes_required = u16::try_from(bytes_required).map_err(|_| {
format!(
"Error near {}:{}. Signal {name} of length num_bits requires \
{bytes_required} > 256 bytes.",
{bytes_required} > 65536 bytes.",
file!(),
line!()
)
@ -343,7 +410,7 @@ impl SignalEnum {
Ok((timestamp, signal_val))
}
fn bits_required(&self) -> Option<u16> {
fn bits_required(&self) -> Option<u32> {
match self {
SignalEnum::Data { num_bits, .. } => *num_bits,
// TODO: Follow aliases?
@ -362,7 +429,7 @@ impl SignalEnum {
desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>,
) -> Result<(String, TimeStamp), SignalErrors> {
) -> Result<QueryResult<String>, SignalErrors> {
let signal_idx = match self {
Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx;
@ -396,7 +463,10 @@ impl SignalEnum {
// this signal should at least have some events, otherwise, trying to index into
// an empty vector later on would fail
if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline);
return Ok(QueryResult {
current: None,
next: None
});
}
// the vector of string timeline lsb indices should have the same
@ -410,9 +480,9 @@ impl SignalEnum {
let (timeline_start_time, _) =
self.time_and_str_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
if *desired_time < timeline_start_time {
return Err(SignalErrors::PreTimeline {
desired_time: desired_time.clone(),
timeline_start_time,
return Ok(QueryResult {
current: None,
next: Some(timeline_start_time),
});
}
@ -424,7 +494,10 @@ impl SignalEnum {
// check if we're requesting a value that occurs beyond the end of the timeline,
// if so, return the last value in this timeline
if *desired_time > timeline_end_time {
return Ok((timeline_end_val.to_string(), timeline_end_time));
return Ok(QueryResult {
current: Some((timeline_end_time, timeline_end_val.to_string())),
next: None,
});
}
// This while loop is the meat of the lookup. Performance is log2(n),
@ -443,7 +516,21 @@ impl SignalEnum {
lower_idx = mid_idx + 1;
}
std::cmp::Ordering::Equal => {
return Ok((curr_val.to_string(), curr_time));
let next_time = if mid_idx < lsb_indxs_of_string_tmstmp_vals_on_tmln.len() - 1 {
Some(
self.time_and_str_val_at_event_idx(
mid_idx + 1,
tmstmps_encoded_as_u8s,
)?
.0,
)
} else {
None
};
return Ok(QueryResult {
current: Some((curr_time, curr_val.to_string())),
next: next_time,
});
}
std::cmp::Ordering::Greater => {
upper_idx = mid_idx - 1;
@ -466,14 +553,17 @@ impl SignalEnum {
});
}
Ok((left_val.to_string(), left_time))
Ok(QueryResult {
current: Some((left_time, left_val.to_string())),
next: Some(right_time),
})
}
pub fn query_num_val_on_tmln(
&self,
desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>,
) -> Result<(BigUint, TimeStamp), SignalErrors> {
) -> Result<QueryResult<BigUint>, SignalErrors> {
let signal_idx = match self {
Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx;
@ -518,7 +608,10 @@ impl SignalEnum {
// this signal should at least have some events, otherwise, trying to index into
// an empty vector later on would fail
if lsb_indxs_of_num_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline);
return Ok(QueryResult {
current: None,
next: None
});
}
// assertion that value_sequence is a proper multiple of
@ -541,9 +634,9 @@ impl SignalEnum {
let (timeline_start_time, _) =
self.time_and_num_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
if *desired_time < timeline_start_time {
return Err(SignalErrors::PreTimeline {
desired_time: desired_time.clone(),
timeline_start_time,
return Ok(QueryResult {
current: None,
next: Some(timeline_start_time),
});
}
@ -555,7 +648,10 @@ impl SignalEnum {
// check if we're requesting a value that occurs beyond the end of the timeline,
// if so, return the last value in this timeline
if *desired_time > timeline_end_time {
return Ok((timeline_end_val, timeline_end_time));
return Ok(QueryResult {
current: Some((timeline_end_time, timeline_end_val)),
next: None,
});
}
// This while loop is the meat of the lookup. Performance is log2(n),
@ -574,7 +670,21 @@ impl SignalEnum {
lower_idx = mid_idx + 1;
}
std::cmp::Ordering::Equal => {
return Ok((curr_val, curr_time));
let next_time = if mid_idx < lsb_indxs_of_num_tmstmp_vals_on_tmln.len() - 1 {
Some(
self.time_and_num_val_at_event_idx(
mid_idx + 1,
tmstmps_encoded_as_u8s,
)?
.0,
)
} else {
None
};
return Ok(QueryResult {
current: Some((curr_time, curr_val)),
next: next_time,
});
}
std::cmp::Ordering::Greater => {
upper_idx = mid_idx - 1;
@ -597,6 +707,9 @@ impl SignalEnum {
});
}
Ok((left_val, left_time))
return Ok(QueryResult {
current: Some((left_time, left_val)),
next: Some(right_time),
});
}
}

View file

@ -9,7 +9,7 @@ use chrono::prelude::{DateTime, Utc};
use num::BigUint;
use std::fmt;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Version(pub String);
#[derive(Debug, Clone, Copy, Eq, PartialEq)]