Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ingest ref_iter branch #13

Merged
merged 27 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5b7f86d
initial index-support
asura6 May 9, 2023
f56a6f2
Merge branch 'codes-index' into initial-index-support
Quba1 Jan 27, 2024
31c7cde
Merge pull request #2 from asura6/initial-index-support
Quba1 Jan 27, 2024
5df6d99
make index_handle public only within crate
Quba1 May 17, 2023
a3c25cb
re-export CodesIndex in lib.rs for consistency
Quba1 Jan 27, 2024
e76f3bc
make codes_index a feature
Quba1 Jan 27, 2024
0e1ff8e
mark featured modules in docs
Quba1 Jan 27, 2024
4b0847e
reolve cargo doc warnings
Quba1 Jan 27, 2024
95c59b6
update gh actions
Quba1 Jan 27, 2024
993efb9
fix testing in gh actions
Quba1 Jan 27, 2024
db3555c
(deosn't compile) extract index binds to separate file
Quba1 Jan 28, 2024
8909556
WIP mostly finish CodesIndex API and conversion to CH
Quba1 Jan 28, 2024
640122f
WIP codes_handle iterator with index works
Quba1 Jan 28, 2024
5a30252
compiles but does not allow for editing index
Quba1 Jan 28, 2024
952a111
actually, the API makes sense (and tests now pass)
Quba1 Jan 28, 2024
2590cb2
move high-level index tests to integration tests
Quba1 Jan 29, 2024
9f8a137
add integration tests with multithreading
Quba1 Jan 29, 2024
bb2558c
add mutex for problematic functions
Quba1 Jan 30, 2024
9d8d83a
add error and panic tests for codes_index
Quba1 Jan 30, 2024
c099d58
add features to gh actions
Quba1 Jan 30, 2024
cff3157
add index handle interference test
Quba1 Jan 30, 2024
d3682ff
clippy
Quba1 Jan 30, 2024
166d701
make ec_index default feature
Quba1 Feb 3, 2024
58ab75e
safeguard all index functions
Quba1 Feb 3, 2024
2512067
initial preparation
Quba1 Feb 2, 2024
7275212
implement streaming iter and adapt tests
Quba1 Feb 3, 2024
58d92ac
make index feature experimental
Quba1 Feb 3, 2024
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
12 changes: 6 additions & 6 deletions .github/workflows/rust-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ jobs:
cargo clean
- name: Test with cargo
run: |
RUST_BACKTRACE=full cargo test
RUST_BACKTRACE=full cargo test --features "experimental_index"
- name: Check with clippy
run: |
cargo clippy -- -W clippy::pedantic
cargo clippy --features "experimental_index"
- name: Build release
run: |
cargo build --release
cargo build --release --features "experimental_index"

build-macos:

Expand All @@ -45,10 +45,10 @@ jobs:
cargo clean
- name: Test with cargo
run: |
RUST_BACKTRACE=full cargo test
RUST_BACKTRACE=full cargo test --features "experimental_index"
- name: Check with clippy
run: |
cargo clippy -- -W clippy::pedantic
cargo clippy --features "experimental_index"
- name: Build release
run: |
cargo build --release
cargo build --release --features "experimental_index"
8 changes: 4 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ jobs:
cargo clean
- name: Build with cargo
run: |
cargo build --release
cargo build --release --features "experimental_index"
cargo clean
- name: Test with cargo
run: |
cargo test
cargo test --features "experimental_index"
cargo clean
- name: Benchmark with criterion
run: |
Expand All @@ -53,11 +53,11 @@ jobs:
cargo clean
- name: Build with cargo
run: |
cargo build --release
cargo build --release --features "experimental_index"
cargo clean
- name: Test with cargo
run: |
cargo test
cargo test --features "experimental_index"
cargo clean
- name: Benchmark with criterion
run: |
Expand Down
14 changes: 10 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "eccodes"
description = "Unofficial high-level Rust bindings of the latest ecCodes release"
repository = "https://github.com/ScaleWeather/eccodes"
version = "0.8.0"
version = "0.9.0"
readme = "README.md"
authors = ["Jakub Lewandowski <[email protected]>"]
keywords = ["eccodes", "grib", "bufr", "meteorology", "weather"]
Expand All @@ -18,7 +18,7 @@ edition = "2021"
exclude = [".github/*", ".vscode/*", ".idea/*", "data/*"]

[dependencies]
eccodes-sys = "0.5.1"
eccodes-sys = "0.5.2"
libc = "0.2"
thiserror = "1.0"
bytes = "1.5"
Expand All @@ -27,20 +27,26 @@ errno = "0.3"
num-derive = "0.4.1"
num-traits = "0.2"
fallible-iterator = "0.3"
fallible-streaming-iterator = "0.1.9"

[dev-dependencies]
eccodes-sys = "0.5.1"
reqwest = { version = "0.11", features = ["rustls-tls"] }
tokio = { version = "1.35", features = ["macros", "rt"] }
criterion = "0.5"
testing_logger = "0.1"
rand = "0.8"
anyhow = "1.0"

[features]
docs = ["eccodes-sys/docs"]
experimental_index = []

[package.metadata.docs.rs]
features = ["docs"]
features = ["docs", "experimental_index"]

[[bench]]
name = "main"
harness = false

[lib]
doctest = false
2 changes: 1 addition & 1 deletion benches/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use eccodes::FallibleIterator;
use eccodes::FallibleStreamingIterator;
use std::path::Path;

use criterion::{black_box, criterion_group, criterion_main, Criterion};
Expand Down
Binary file added data/iceland-surface.idx
Binary file not shown.
181 changes: 123 additions & 58 deletions src/codes_handle/iterator.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use eccodes_sys::codes_handle;
use fallible_iterator::FallibleIterator;
use std::ptr;

use fallible_streaming_iterator::FallibleStreamingIterator;

use crate::{
codes_handle::{CodesHandle, KeyedMessage},
errors::CodesError,
intermediate_bindings::{
codes_get_message_copy, codes_handle_delete, codes_handle_new_from_file,
codes_handle_new_from_message_copy,
},
intermediate_bindings::{codes_handle_delete, codes_handle_new_from_file},
};
#[cfg(feature = "experimental_index")]
use crate::{intermediate_bindings::codes_index::codes_handle_new_from_index, CodesIndex};

use super::GribFile;

///`FallibleIterator` implementation for `CodesHandle` to access GRIB messages inside file.
///
Expand All @@ -18,7 +20,7 @@ use crate::{
///Therefore this crate utilizes the `Iterator` to provide the access to GRIB messages in
///a safe and convienient way.
///
///[`FallibleIterator`](fallible_iterator::FallibleIterator) is used instead of classic `Iterator`
///[`FallibleIterator`] is used instead of classic `Iterator`
///because internal ecCodes functions can return error codes when the GRIB file
///is corrupted and for some other reasons. The usage of `FallibleIterator` is sligthly different
///than usage of `Iterator`, check its documentation for more details.
Expand Down Expand Up @@ -77,81 +79,141 @@ use crate::{
///## Errors
///The `next()` method will return [`CodesInternal`](crate::errors::CodesInternal)
///when internal ecCodes function returns non-zero code.
impl FallibleIterator for CodesHandle {
impl FallibleStreamingIterator for CodesHandle<GribFile> {
type Item = KeyedMessage;

type Error = CodesError;

fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
let file_handle;
fn advance(&mut self) -> Result<(), Self::Error> {
unsafe {
codes_handle_delete(self.file_handle)?;
file_handle = codes_handle_new_from_file(self.file_pointer, self.product_kind);
codes_handle_delete(self.unsafe_message.message_handle)?;
}

match file_handle {
Ok(h) => {
self.file_handle = h;
// nullify message handle so that destructor is harmless
// it might be excessive but it follows the correct pattern
self.unsafe_message.message_handle = ptr::null_mut();

if self.file_handle.is_null() {
Ok(None)
} else {
let message = get_message_from_handle(h);
Ok(Some(message))
}
}
Err(e) => Err(e),
let new_eccodes_handle =
unsafe { codes_handle_new_from_file(self.source.pointer, self.product_kind)? };

self.unsafe_message = KeyedMessage {
message_handle: new_eccodes_handle,
iterator_flags: None,
iterator_namespace: None,
keys_iterator: None,
keys_iterator_next_item_exists: false,
nearest_handle: None,
};

Ok(())
}

fn get(&self) -> Option<&Self::Item> {
if self.unsafe_message.message_handle.is_null() {
None
} else {
Some(&self.unsafe_message)
}
}
}

fn get_message_from_handle(handle: *mut codes_handle) -> KeyedMessage {
let new_handle;
let new_buffer;
#[cfg(feature = "experimental_index")]
impl FallibleStreamingIterator for CodesHandle<CodesIndex> {
type Item = KeyedMessage;

unsafe {
new_buffer = codes_get_message_copy(handle).expect(
"Getting message clone failed.
Please report this panic on Github",
);
new_handle = codes_handle_new_from_message_copy(&new_buffer);
type Error = CodesError;

fn advance(&mut self) -> Result<(), Self::Error> {
unsafe {
codes_handle_delete(self.unsafe_message.message_handle)?;
}

// nullify message handle so that destructor is harmless
// it might be excessive but it follows the correct pattern
self.unsafe_message.message_handle = ptr::null_mut();

let new_eccodes_handle = unsafe { codes_handle_new_from_index(self.source.pointer)? };

self.unsafe_message = KeyedMessage {
message_handle: new_eccodes_handle,
iterator_flags: None,
iterator_namespace: None,
keys_iterator: None,
keys_iterator_next_item_exists: false,
nearest_handle: None,
};

Ok(())
}

KeyedMessage {
message_handle: new_handle,
iterator_flags: None,
iterator_namespace: None,
keys_iterator: None,
keys_iterator_next_item_exists: false,
nearest_handle: None,
fn get(&self) -> Option<&Self::Item> {
if self.unsafe_message.message_handle.is_null() {
None
} else {
Some(&self.unsafe_message)
}
}
}

#[cfg(test)]
mod tests {
use crate::codes_handle::{CodesHandle, KeyType, KeyedMessage, ProductKind};
use crate::FallibleIterator;
use crate::codes_handle::{CodesHandle, KeyType, ProductKind};
use anyhow::Result;
use fallible_streaming_iterator::FallibleStreamingIterator;
use std::path::Path;

#[test]
fn iterator_fn() {
fn iterator_lifetimes() -> Result<()> {
let file_path = Path::new("./data/iceland-levels.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();

let msg1 = handle.next()?.unwrap();
let key1 = msg1.read_key("typeOfLevel")?;

let msg2 = handle.next()?.unwrap();
let key2 = msg2.read_key("typeOfLevel")?;

let msg3 = handle.next()?.unwrap();
let key3 = msg3.read_key("typeOfLevel")?;

assert_eq!(key1.value, KeyType::Str("isobaricInhPa".to_string()));
assert_eq!(key2.value, KeyType::Str("isobaricInhPa".to_string()));
assert_eq!(key3.value, KeyType::Str("isobaricInhPa".to_string()));

Ok(())
}

#[test]
fn iterator_fn() -> Result<()> {
let file_path = Path::new("./data/iceland-surface.grib");
let product_kind = ProductKind::GRIB;

let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();

while let Some(msg) = handle.next().unwrap() {
let key = msg.read_key("shortName").unwrap();
while let Some(msg) = handle.next()? {
let key = msg.read_key("shortName")?;

match key.value {
KeyType::Str(_) => {}
_ => panic!("Incorrect variant of string key"),
}
}

let handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();
Ok(())
}

let handle_collected: Vec<KeyedMessage> = handle.collect().unwrap();
#[test]
fn iterator_collected() -> Result<()> {
let file_path = Path::new("./data/iceland-surface.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();

let mut handle_collected = vec![];

while let Some(msg) = handle.next()? {
handle_collected.push(msg.clone());
}

for msg in handle_collected {
let key = msg.read_key("name").unwrap();
Expand All @@ -160,6 +222,8 @@ mod tests {
_ => panic!("Incorrect variant of string key"),
}
}

Ok(())
}

#[test]
Expand All @@ -178,24 +242,23 @@ mod tests {
}

#[test]
fn iterator_collect() {
fn iterator_filter() -> Result<()> {
let file_path = Path::new("./data/iceland.grib");
let product_kind = ProductKind::GRIB;

let handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();
let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap();

// Use iterator to get a Keyed message with shortName "msl" and typeOfLevel "surface"
// First, filter and collect the messages to get those that we want
let mut level: Vec<KeyedMessage> = handle
.filter(|msg| {
Ok(
msg.read_key("shortName")?.value == KeyType::Str("msl".to_string())
&& msg.read_key("typeOfLevel")?.value
== KeyType::Str("surface".to_string()),
)
})
.collect()
.unwrap();
let mut level = vec![];

while let Some(msg) = handle.next()? {
if msg.read_key("shortName")?.value == KeyType::Str("msl".to_string())
&& msg.read_key("typeOfLevel")?.value == KeyType::Str("surface".to_string())
{
level.push(msg.clone());
}
}

// Now unwrap and access the first and only element of resulting vector
// Find nearest modifies internal KeyedMessage fields so we need mutable reference
Expand All @@ -211,5 +274,7 @@ mod tests {
"value: {}, distance: {}",
nearest_gridpoints[3].value, nearest_gridpoints[3].distance
);

Ok(())
}
}
Loading