diff --git a/Cargo.toml b/Cargo.toml index df663f88..5ba91062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,17 +16,42 @@ categories = [ ] [workspace.dependencies] -schemars = { version = "0.9.0" } -serde = { version = "1.0.210", features = ["derive", "rc"] } -serde_json = { version = "1.0.128" } +axum = "0.8.4" +bevy_app = "0.12.1" +bevy_core = "0.12.1" +bevy_derive = "0.12.1" +bevy_ecs = "0.12.1" +bevy_hierarchy = "0.12.1" +bevy_time = "0.12.1" +bevy_utils = "0.12.1" +clap = { version = "4.5.23", features = ["derive"] } +mime_guess = "2.0.5" +schemars = "0.9.0" +serde = "1.0.219" +serde_json = "1.0.140" +tokio = "1.47.1" +tonic = "0.14" +prost = "0.14" +tonic-prost = "0.14" +tonic-prost-build = "0.14" +prost-build = "0.14" +prost-reflect = "0.16" +tracing = "0.1.41" +tracing-subscriber = "0.3.19" +futures = "0.3.31" +futures-util = "0.3.31" +uuid = "1.13.1" +wasm-bindgen = "0.2.100" +zenoh = "1.3.2" +zenoh-ext = "1.4.0" [dependencies] bevy_impulse_derive = { path = "macros", version = "0.0.2" } -bevy_ecs = "0.12" -bevy_utils = "0.12" -bevy_hierarchy = "0.12" -bevy_derive = "0.12" -bevy_app = "0.12" +bevy_ecs = { workspace = true } +bevy_utils = { workspace = true } +bevy_hierarchy = { workspace = true } +bevy_derive = { workspace = true } +bevy_app = { workspace = true } async-task = { version = "4.7.1", optional = true } @@ -37,8 +62,8 @@ bevy_tasks = { version = "0.12", features = ["multi-threaded"] } itertools = "0.13" smallvec = { version = "1.13", features = ["serde"] } -tokio = { version = "1.39", features = ["sync"] } -futures = "0.3" +tokio = { workspace = true, features = ["sync"] } +futures = { workspace = true } backtrace = "0.3" anyhow = "1.0" thiserror = "1.0" @@ -48,35 +73,35 @@ thiserror = "1.0" # the testing module for doctests, and doctests can only # make use of default features, so we're a bit stuck with # these for now. -bevy_core = "0.12" -bevy_time = "0.12" +bevy_core = { workspace = true } +bevy_time = { workspace = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive", "rc"] } serde_json = { workspace = true, optional = true } cel-interpreter = { version = "0.9.0", features = ["json"], optional = true } # --- Dependencies for grpc feature -tonic = { version = "0.14", optional = true } -prost = { version = "0.14", optional = true } -tonic-prost = { version = "0.14", optional = true } -prost-reflect = { version = "0.16", features = ["serde"], optional = true } +tonic = { workspace = true, optional = true } +prost = { workspace = true, optional = true } +tonic-prost = { workspace = true, optional = true } +prost-reflect = { workspace = true, features = ["serde"], optional = true } http = { version = "1.3", optional = true } futures-lite = { version = "2.6", features = ["std", "race"], optional = true } async-std = { version = "1.12", optional = true} # --- end grpc # --- Dependencies for zenoh feature -zenoh = { version = "1.3.2", features = ["unstable"], optional = true } -zenoh-ext = { version = "*", features = ["unstable"], optional = true } +zenoh = { workspace = true, features = ["unstable"], optional = true } +zenoh-ext = { workspace = true, features = ["unstable"], optional = true } # --- end zenoh -tracing = "0.1.41" +tracing = { workspace = true } strum = { version = "0.26.3", optional = true, features = ["derive"] } semver = { version = "1.0.24", optional = true } [target.wasm32-unknown-unknown.dependencies] -uuid = { version = "1.13.1", default-features = false, features = ["js"] } +uuid = { workspace = true, default-features = false, features = ["js"] } getrandom = { version = "0.3.3", features = ["wasm_js"] } [features] @@ -143,4 +168,4 @@ required-features = ["diagram"] doc = false [build-dependencies] -tonic-prost-build = { version = "0.14", optional = true } +tonic-prost-build = { workspace = true, optional = true } diff --git a/diagram-editor/Cargo.toml b/diagram-editor/Cargo.toml index 045ec5ee..44f29243 100644 --- a/diagram-editor/Cargo.toml +++ b/diagram-editor/Cargo.toml @@ -28,26 +28,26 @@ exclude = ["/build.rs"] path = "server/lib.rs" [dependencies] -axum = { version = "0.8.4", features = ["json"], default-features = false } -bevy_app = "0.12.1" -bevy_ecs = "0.12.1" +axum = { workspace = true, features = ["json"], default-features = false } +bevy_app = { workspace = true } +bevy_ecs = { workspace = true } bevy_impulse = { version = "0.0.2", path = "..", features = [ "diagram", "trace", ] } -clap = { version = "4.5.23", features = ["derive"], optional = true } +clap = { workspace = true, features = ["derive"], optional = true } flate2 = { version = "1.1.1", optional = true } -futures-util = "0.3.31" +futures-util = { workspace = true } indexmap = { version = "2.10.0", optional = true, features = ["serde"] } -mime_guess = "2.0.5" -schemars = { version = "0.9", optional = true } -serde = "1.0.219" -serde_json = "1.0.140" +mime_guess = { workspace = true } +schemars = { workspace = true, optional = true } +serde = { workspace = true } +serde_json = { workspace = true } tar = { version = "0.4.44", optional = true } thiserror = "2.0.16" -tokio = { version = "1.45.1", features = ["macros"] } -tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", optional = true } +tokio = { workspace = true, features = ["macros"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, optional = true } [build-dependencies] flate2 = { version = "1.1.1", optional = true } @@ -56,7 +56,7 @@ tar = { version = "0.4.44", optional = true } [dev-dependencies] futures-channel = "0.3.31" test-log = "0.2.18" -tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tower = "0.5.2" [features] diff --git a/diagram-editor/wasm/Cargo.toml b/diagram-editor/wasm/Cargo.toml index 269ba46a..3e9075a6 100644 --- a/diagram-editor/wasm/Cargo.toml +++ b/diagram-editor/wasm/Cargo.toml @@ -25,20 +25,20 @@ categories = [ ] [dependencies] -axum = { version = "0.8.4", features = ["json"], default-features = false } -bevy_app = "0.12.1" +axum = { workspace = true, features = ["json"], default-features = false } +bevy_app = { workspace = true } bevy_impulse = { version = "0.0.2", path = "../..", features = [ "diagram", "single_threaded_async", ] } bevy_impulse_diagram_editor = { version = "0.0.1", path = "..", default-features = false } -mime_guess = { version = "2.0.5" } -serde_json = "1.0.140" +mime_guess = { workspace = true } +serde_json = { workspace = true } serde-wasm-bindgen = "0.6.5" -wasm-bindgen = "0.2.100" +wasm-bindgen = { workspace = true } wasm-bindgen-futures = "0.4.50" -tokio = { version = "1.47.1", default-features = false, features = ["rt"] } -futures = "0.3.31" +tokio = { workspace = true, default-features = false, features = ["rt"] } +futures = { workspace = true } serde.workspace = true [build-dependencies] diff --git a/diagram-editor/wasm/src/executor.rs b/diagram-editor/wasm/src/executor.rs index 7421edd1..9f9370ed 100644 --- a/diagram-editor/wasm/src/executor.rs +++ b/diagram-editor/wasm/src/executor.rs @@ -58,6 +58,7 @@ pub async fn post_run(request: PostRunRequestWasm) -> Result { } #[cfg(test)] +#[cfg(target_arch = "wasm32")] mod tests { use std::{collections::HashMap, sync::Arc}; diff --git a/diagram-editor/wasm/src/globals.rs b/diagram-editor/wasm/src/globals.rs index b10f34f0..515c2a78 100644 --- a/diagram-editor/wasm/src/globals.rs +++ b/diagram-editor/wasm/src/globals.rs @@ -26,12 +26,6 @@ pub fn setup_wasm( BEVY_APP.lock().unwrap().replace(app); } -pub(super) fn with_bevy_app(f: impl FnOnce(&mut bevy_app::App) -> R) -> R { - let mut mg = BEVY_APP.lock().unwrap(); - let app = mg.as_mut().expect("`init_wasm` not called"); - f(app) -} - pub(super) async fn with_bevy_app_async(f: impl AsyncFnOnce(&mut bevy_app::App) -> R) -> R { let mut mg = BEVY_APP.lock().unwrap(); let app = mg.as_mut().expect("`init_wasm` not called"); diff --git a/diagram-editor/wasm/src/registry.rs b/diagram-editor/wasm/src/registry.rs index d17ff4f0..90c6c4bb 100644 --- a/diagram-editor/wasm/src/registry.rs +++ b/diagram-editor/wasm/src/registry.rs @@ -12,6 +12,7 @@ pub fn get_registry() -> JsValue { .expect("failed to serialize registry") } +#[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { use wasm_bindgen_test::wasm_bindgen_test; diff --git a/diagram-editor/wasm/src/test_utils.rs b/diagram-editor/wasm/src/test_utils.rs index a557ceb9..8540e9bf 100644 --- a/diagram-editor/wasm/src/test_utils.rs +++ b/diagram-editor/wasm/src/test_utils.rs @@ -17,6 +17,7 @@ init_wasm! { InitOptions{app, registry, executor_options} } +#[cfg(target_arch = "wasm32")] pub(crate) fn setup_test() { init_wasm(); } diff --git a/examples/diagram/calculator_ops_catalog/Cargo.toml b/examples/diagram/calculator_ops_catalog/Cargo.toml index 3cc76eff..1543b643 100644 --- a/examples/diagram/calculator_ops_catalog/Cargo.toml +++ b/examples/diagram/calculator_ops_catalog/Cargo.toml @@ -5,11 +5,11 @@ edition = "2021" description = "catalog of reusable calculator operations" [dependencies] -bevy_app = "0.12" -bevy_core = "0.12" +bevy_app = { workspace = true } +bevy_core = { workspace = true } bevy_impulse = { version = "0.0.2", path = "../../..", features = ["diagram"] } -bevy_time = "0.12" +bevy_time = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } schemars = { workspace = true } -tracing-subscriber = "0.3.19" +tracing-subscriber = { workspace = true } diff --git a/examples/diagram/calculator_wasm/Cargo.toml b/examples/diagram/calculator_wasm/Cargo.toml index d55d3e24..9b9c9d21 100644 --- a/examples/diagram/calculator_wasm/Cargo.toml +++ b/examples/diagram/calculator_wasm/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -bevy_app = "0.12" +bevy_app = { workspace = true } bevy_impulse = { version = "0.0.2", path = "../../.." } bevy_impulse_diagram_editor_wasm = { version = "0.0.1", path = "../../../diagram-editor/wasm" } calculator_ops_catalog = { version = "0.1.0", path = "../calculator_ops_catalog" } -wasm-bindgen = "0.2.100" +wasm-bindgen = { workspace = true } diff --git a/examples/zenoh-examples/Cargo.toml b/examples/zenoh-examples/Cargo.toml index 4762ffa9..5e5fd89d 100644 --- a/examples/zenoh-examples/Cargo.toml +++ b/examples/zenoh-examples/Cargo.toml @@ -12,21 +12,21 @@ name = "use-door" path = "src/use_door.rs" [dependencies] -bevy_app = "0.12" -bevy_core = "0.12" -bevy_ecs = "0.12" -bevy_time = "0.12" bevy_impulse = { version = "0.0.2", path = "../..", features = ["diagram"] } -futures = "0.3" -schemars = { version = "0.9.0" } -serde = { version = "1.0.210", features = ["derive", "rc"] } -serde_json = "1.0.128" -prost = "0.13.5" -tracing = "0.1.41" -clap = { version = "4.5.23", features = ["derive"] } -uuid = { version = "*", features = ["v4"] } -zenoh = { version = "1.3.2", features = ["unstable"] } -zenoh-ext = { version = "*", features = ["unstable"] } +bevy_app = { workspace = true } +bevy_core = { workspace = true } +bevy_ecs = { workspace = true } +bevy_time = { workspace = true } +futures = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, features = ["derive", "rc"] } +serde_json = { workspace = true } +prost = { workspace = true } +tracing = { workspace = true } +clap = { workspace = true, features = ["derive"] } +uuid = { workspace = true, features = ["v4"] } +zenoh = { workspace = true, features = ["unstable"] } +zenoh-ext = { workspace = true, features = ["unstable"] } [build-dependencies] -prost-build = "0.13.5" +prost-build = { workspace = true } diff --git a/examples/zenoh-examples/src/lib.rs b/examples/zenoh-examples/src/lib.rs index ec06793e..07f03110 100644 --- a/examples/zenoh-examples/src/lib.rs +++ b/examples/zenoh-examples/src/lib.rs @@ -109,7 +109,7 @@ pub fn zenoh_subscription_node( builder.create_node(callback.as_callback()) } -pub fn zenoh_publisher_node( +pub fn zenoh_publisher_node( topic_name: Arc, builder: &mut Builder, ) -> Node> { diff --git a/src/buffer/json_buffer.rs b/src/buffer/json_buffer.rs index 088a1f8c..5e4fa475 100644 --- a/src/buffer/json_buffer.rs +++ b/src/buffer/json_buffer.rs @@ -217,6 +217,11 @@ impl From for AnyBufferKey { /// Similar to [`BufferView`][crate::BufferView], but this can be unlocked with /// a [`JsonBufferKey`], so it can work for any buffer whose message types /// support serialization and deserialization. +/// +/// Obtain this from a [`World`] using the [`JsonBufferWorldAccess`] trait. Full +/// world access is needed to get this, because the underlaying buffer may be any +/// serializable data type, and only the [`JsonBufferKey`] will know the actual +/// data type. pub struct JsonBufferView<'a> { storage: Box, gate: &'a GateState, @@ -249,6 +254,14 @@ impl<'a> JsonBufferView<'a> { self.len() == 0 } + /// Iterate through the current elements of the buffer. + pub fn iter(&self) -> IterJsonBufferView<'a, '_> { + IterJsonBufferView { + index: 0, + view: self, + } + } + /// Check whether the gate of this buffer is open or closed. pub fn gate(&self) -> Gate { self.gate @@ -259,9 +272,28 @@ impl<'a> JsonBufferView<'a> { } } +pub struct IterJsonBufferView<'a, 'b> { + index: usize, + view: &'b JsonBufferView<'a>, +} + +impl<'a, 'b> Iterator for IterJsonBufferView<'a, 'b> { + type Item = Result; + fn next(&mut self) -> Option { + let next = self.index; + self.index += 1; + self.view.get(next).transpose() + } +} + /// Similar to [`BufferMut`][crate::BufferMut], but this can be unlocked with a /// [`JsonBufferKey`], so it can work for any buffer whose message types support /// serialization and deserialization. +/// +/// Obtain this from a [`World`] using the [`JsonBufferWorldAccess`] trait. Full +/// world access is needed to get this, because the underlaying buffer may be any +/// serializable data type, and only the [`JsonBufferKey`] will know the actual +/// data type. pub struct JsonBufferMut<'w, 's, 'a> { storage: Box, buffer: Entity, diff --git a/src/diagram.rs b/src/diagram.rs index 88cfae57..d7944117 100644 --- a/src/diagram.rs +++ b/src/diagram.rs @@ -64,16 +64,13 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, fmt::Display, - future::Future, io::Read, - pin::Pin, sync::Arc, - task::{Context, Poll}, }; pub use crate::type_info::TypeInfo; use crate::{ - Builder, IncompatibleLayout, IncrementalScopeError, JsonMessage, Scope, Service, + is_default, Builder, IncompatibleLayout, IncrementalScopeError, JsonMessage, Scope, Service, SpawnWorkflowExt, SplitConnectionError, StreamPack, }; @@ -85,8 +82,6 @@ use serde::{ Deserialize, Deserializer, Serialize, Serializer, }; -use futures::never::Never; - use thiserror::Error as ThisError; const CURRENT_DIAGRAM_VERSION: &str = "0.1.0"; @@ -1321,45 +1316,3 @@ mod tests { assert_eq!(result, 5.0); } } - -/// Used with `#[serde(default, skip_serializing_if = "is_default")]` for fields -/// that don't need to be serialized if they are a default value. -pub(crate) fn is_default(value: &T) -> bool { - let default = T::default(); - *value == default -} - -/// Used with `#[serde(default = "default_as_true", skip_serializing_if = "is_true")]` -/// for bools that should be true by default. -pub(crate) fn default_as_true() -> bool { - true -} - -/// Used with `#[serde(default = "default_as_true", skip_serializing_if = "is_true")]` -/// for bools that should be true by default. -pub(crate) fn is_true(value: &bool) -> bool { - *value -} - -/// This is used to block a future from ever returning. This should only be used -/// in a race to force one of the contesting futures to lose. Make sure that at -/// least one contesting future will finish or else this will lead to a deadlock. -#[allow(unused)] -struct NeverFinish; - -impl Future for NeverFinish { - type Output = Never; - fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Pending - } -} - -/// Used by some tests in the grpc and zenoh modules -#[allow(unused)] -pub(crate) fn clamp(val: f32, limit: f32) -> f32 { - if f32::abs(val) > limit { - return f32::signum(val) * limit; - } - - val -} diff --git a/src/diagram/grpc.rs b/src/diagram/grpc.rs index 4a5be063..422047d2 100644 --- a/src/diagram/grpc.rs +++ b/src/diagram/grpc.rs @@ -16,7 +16,7 @@ */ use super::*; -use crate::AsyncMap; +use crate::{AsyncMap, NeverFinish}; use std::{future::Future, sync::Arc, time::Duration}; @@ -401,7 +401,7 @@ impl Decoder for DynamicMessageCodec { #[cfg(test)] mod tests { use super::*; - use crate::{diagram::testing::*, prelude::*, testing::*}; + use crate::{diagram::testing::*, prelude::*, testing::*, utils::*}; use futures::channel::oneshot::{self, Sender as OneShotSender}; use prost_reflect::Kind; use protos::{ diff --git a/src/diagram/zenoh.rs b/src/diagram/zenoh.rs index 818960df..7d48a9da 100644 --- a/src/diagram/zenoh.rs +++ b/src/diagram/zenoh.rs @@ -16,7 +16,7 @@ */ use super::*; -use crate::prelude::*; +use crate::{prelude::*, utils::*}; use ::zenoh::{ bytes::{Encoding, ZBytes}, diff --git a/src/lib.rs b/src/lib.rs index a36ccbf2..bbd55221 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,9 @@ pub use workflow::*; pub mod testing; +pub(crate) mod utils; +pub(crate) use utils::*; + #[cfg(feature = "trace")] pub mod trace; #[cfg(feature = "trace")] @@ -412,11 +415,14 @@ pub mod prelude { ImpulseAppPlugin, ImpulsePlugin, }; - pub use bevy_ecs::prelude::In; + pub use bevy_ecs::prelude::{In, World}; #[cfg(feature = "diagram")] pub use crate::{ - buffer::{JsonBuffer, JsonBufferKey, JsonBufferMut, JsonBufferWorldAccess, JsonMessage}, + buffer::{ + JsonBuffer, JsonBufferKey, JsonBufferMut, JsonBufferView, JsonBufferWorldAccess, + JsonMessage, + }, diagram::{Diagram, DiagramElementRegistry, DiagramError, NodeBuilderOptions, Section}, }; diff --git a/src/service.rs b/src/service.rs index 731d2979..c170a702 100644 --- a/src/service.rs +++ b/src/service.rs @@ -224,7 +224,7 @@ define_label!( DELIVERY_LABEL_INTERNER ); -pub mod utils { +pub mod service_utils { /// Used by the procedural macro for DeliveryLabel pub use bevy_utils::label::DynEq; } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..90b1c15b --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +//! Miscellaneous utilities used internally +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +/// Used with `#[serde(default, skip_serializing_if = "is_default")]` for fields +/// that don't need to be serialized if they are a default value. +#[allow(unused)] +pub(crate) fn is_default(value: &T) -> bool { + let default = T::default(); + *value == default +} + +/// Used with `#[serde(default = "default_as_true", skip_serializing_if = "is_true")]` +/// for bools that should be true by default. +#[allow(unused)] +pub(crate) fn default_as_true() -> bool { + true +} + +/// Used with `#[serde(default = "default_as_true", skip_serializing_if = "is_true")]` +/// for bools that should be true by default. +#[allow(unused)] +pub(crate) fn is_true(value: &bool) -> bool { + *value +} + +/// This is used to block a future from ever returning. This should only be used +/// in a race to force one of the contesting futures to lose. Make sure that at +/// least one contesting future will finish or else this will lead to a deadlock. +#[allow(unused)] +pub(crate) struct NeverFinish; + +impl Future for NeverFinish { + type Output = Never; + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + Poll::Pending + } +} + +/// A data structure that can never be instantiated +pub(crate) enum Never {} + +/// Used by some tests in the grpc and zenoh modules +#[allow(unused)] +pub(crate) fn clamp(val: f32, limit: f32) -> f32 { + if f32::abs(val) > limit { + return f32::signum(val) * limit; + } + + val +}