Compare commits

...
This repository has been archived on 2025-06-25. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

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)]