Merge "Add current control loop python"
diff --git a/aos/events/event_loop_runtime.rs b/aos/events/event_loop_runtime.rs
index 7f58dbb..76c1e2e 100644
--- a/aos/events/event_loop_runtime.rs
+++ b/aos/events/event_loop_runtime.rs
@@ -34,11 +34,9 @@
//! https://github.com/google/autocxx/issues/1146 for details.
//! * For various reasons autocxx can't directly wrap APIs using types ergonomic for C++. This and
//! the previous point mean we wrap all of the C++ objects specifically for this class.
-//! * Keeping track of all the lifetimes and creating appropriate references for the callbacks is
-//! really hard in Rust. Even doing it for the library implementation turned out to be hard
-//! enough to look for alternatives. I think you'd have to make extensive use of pointers, but
-//! Rust makes that hard, and it's easy to create references in ways that violate Rust's
-//! aliasing rules.
+//! * Rust's lifetimes are only flexible enough to track everything with a single big lifetime.
+//! All the callbacks can store references to things tied to the event loop's lifetime, but no
+//! other lifetimes.
//! * We can't use [`futures::stream::Stream`] and all of its nice [`futures::stream::StreamExt`]
//! helpers for watchers because we need lifetime-generic `Item` types. Effectively we're making
//! a lending stream. This is very close to lending iterators, which is one of the motivating
@@ -48,6 +46,7 @@
fmt,
future::Future,
marker::PhantomData,
+ mem::ManuallyDrop,
panic::{catch_unwind, AssertUnwindSafe},
pin::Pin,
slice,
@@ -56,7 +55,7 @@
};
use autocxx::{
- subclass::{is_subclass, CppSubclass},
+ subclass::{subclass, CppSubclass},
WithinBox,
};
use cxx::UniquePtr;
@@ -69,6 +68,7 @@
use aos_configuration::{ChannelLookupError, ConfigurationExt};
pub use aos_uuid::UUID;
+pub use ffi::aos::EventLoopRuntime as CppEventLoopRuntime;
pub use ffi::aos::ExitHandle as CppExitHandle;
autocxx::include_cpp! (
@@ -95,6 +95,13 @@
pub type EventLoop = ffi::aos::EventLoop;
+/// A marker type which is invariant with respect to the given lifetime.
+///
+/// When interacting with functions that take and return things with a given lifetime, the lifetime
+/// becomes invariant. Because we don't store these functions as Rust types, we need a type like
+/// this to tell the Rust compiler that it can't substitute a shorter _or_ longer lifetime.
+pub type InvariantLifetime<'a> = PhantomData<fn(&'a ()) -> &'a ()>;
+
/// # Safety
///
/// This should have a `'event_loop` lifetime and `future` should include that in its type, but
@@ -102,7 +109,7 @@
/// enforcing the lifetime: it destroys this object along with the C++ `EventLoopRuntime`, which
/// must be outlived by the EventLoop.
#[doc(hidden)]
-#[is_subclass(superclass("aos::ApplicationFuture"))]
+#[subclass]
pub struct RustApplicationFuture {
/// This logically has a `'event_loop` bound, see the class comment for details.
future: Pin<Box<dyn Future<Output = Never>>>,
@@ -145,17 +152,138 @@
}
}
+/// An abstraction for objects which hold an `aos::EventLoop` from Rust code.
+///
+/// If you have an `aos::EventLoop` provided from C++ code, don't use this, just call
+/// [`EventLoopRuntime.new`] directly.
+///
+/// # Safety
+///
+/// Objects implementing this trait *must* have mostly-exclusive (except for running it) ownership
+/// of the `aos::EventLoop` *for its entire lifetime*, which *must* be dropped when this object is.
+/// See [`EventLoopRuntime.new`]'s safety requirements for why this can be important and details of
+/// mostly-exclusive. In other words, nothing else may mutate it in any way except processing events
+/// (including dropping, because this object has to be the one to drop it).
+///
+/// This also implies semantics similar to `Pin<&mut ffi::aos::EventLoop>` for the underlying object.
+/// Implementations of this trait must have exclusive ownership of it, and the underlying object
+/// must not be moved.
+pub unsafe trait EventLoopHolder {
+ /// Converts this holder into a raw C++ pointer. This may be fed through other Rust and C++
+ /// code, and eventually passed back to [`from_raw`].
+ fn into_raw(self) -> *mut ffi::aos::EventLoop;
+
+ /// Converts a raw C++ pointer back to a holder object.
+ ///
+ /// # Safety
+ ///
+ /// `raw` must be the result of [`into_raw`] on an instance of this same type. These raw
+ /// pointers *are not* interchangeable between implementations of this trait.
+ unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self;
+}
+
+/// Owns an [`EventLoopRuntime`] and its underlying `aos::EventLoop`, with safe management of the
+/// associated Rust lifetimes.
+pub struct EventLoopRuntimeHolder<T: EventLoopHolder>(
+ ManuallyDrop<Pin<Box<CppEventLoopRuntime>>>,
+ PhantomData<T>,
+);
+
+impl<T: EventLoopHolder> EventLoopRuntimeHolder<T> {
+ /// Creates a new [`EventLoopRuntime`] and runs an initialization function on it. This is a
+ /// safe wrapper around [`EventLoopRuntime.new`] (although see [`EventLoopHolder`]'s safety
+ /// requirements, part of them are just delegated there).
+ ///
+ /// If you have an `aos::EventLoop` provided from C++ code, don't use this, just call
+ /// [`EventLoopRuntime.new`] directly.
+ ///
+ /// All setup of the runtime must be performed with `fun`, which is called before this function
+ /// returns. `fun` may create further objects to use in async functions via [`EventLoop.spawn`]
+ /// etc, but it is the only place to set things up before the EventLoop is run.
+ ///
+ /// `fun` cannot capture things outside of the event loop, because the event loop might outlive
+ /// them:
+ /// ```compile_fail
+ /// # use aos_events_event_loop_runtime::*;
+ /// # fn bad(event_loop: impl EventLoopHolder) {
+ /// let mut x = 0;
+ /// EventLoopRuntimeHolder::new(event_loop, |runtime| {
+ /// runtime.spawn(async {
+ /// x = 1;
+ /// loop {}
+ /// });
+ /// });
+ /// # }
+ /// ```
+ ///
+ /// But it can capture `'event_loop` references:
+ /// ```
+ /// # use aos_events_event_loop_runtime::*;
+ /// # use aos_configuration::ChannelExt;
+ /// # fn good(event_loop: impl EventLoopHolder) {
+ /// EventLoopRuntimeHolder::new(event_loop, |runtime| {
+ /// let channel = runtime.get_raw_channel("/test", "aos.examples.Ping").unwrap();
+ /// runtime.spawn(async {
+ /// loop {
+ /// eprintln!("{:?}", channel.type_());
+ /// }
+ /// });
+ /// });
+ /// # }
+ /// ```
+ pub fn new<F>(event_loop: T, fun: F) -> Self
+ where
+ F: for<'event_loop> FnOnce(&mut EventLoopRuntime<'event_loop>),
+ {
+ // SAFETY: The EventLoopRuntime never escapes this function, which means the only code that
+ // observes its lifetime is `fun`. `fun` must be generic across any value of its
+ // `'event_loop` lifetime parameter, which means we can choose any lifetime here, which
+ // satisfies the safety requirements.
+ //
+ // This is a similar pattern as `std::thread::scope`, `ghost-cell`, etc. Note that unlike
+ // `std::thread::scope`, our inner functions (the async ones) are definitely not allowed to
+ // capture things from the calling scope of this function, so there's no `'env` equivalent.
+ // `ghost-cell` ends up looking very similar despite doing different things with the
+ // pattern, while `std::thread::scope` has a lot of additional complexity to achieve a
+ // similar result.
+ //
+ // `EventLoopHolder`s safety requirements prevent anybody else from touching the underlying
+ // `aos::EventLoop`.
+ let mut runtime = unsafe { EventLoopRuntime::new(event_loop.into_raw()) };
+ fun(&mut runtime);
+ Self(ManuallyDrop::new(runtime.into_cpp()), PhantomData)
+ }
+}
+
+impl<T: EventLoopHolder> Drop for EventLoopRuntimeHolder<T> {
+ fn drop(&mut self) {
+ let event_loop = self.0.as_mut().event_loop();
+ // SAFETY: We're not going to touch this field again. The underlying EventLoop will not be
+ // run again because we're going to drop it next.
+ unsafe { ManuallyDrop::drop(&mut self.0) };
+ // SAFETY: We took this from `into_raw`, and we just dropped the runtime which may contain
+ // Rust references to it.
+ unsafe { drop(T::from_raw(event_loop)) };
+ }
+}
+
pub struct EventLoopRuntime<'event_loop>(
Pin<Box<ffi::aos::EventLoopRuntime>>,
- // This is the lifetime of the underlying EventLoop, which is held in C++ via `.0`.
- PhantomData<&'event_loop mut ()>,
+ // See documentation of [`new`] for details.
+ InvariantLifetime<'event_loop>,
);
/// Manages the Rust interface to a *single* `aos::EventLoop`. This is intended to be used by a
/// single application.
impl<'event_loop> EventLoopRuntime<'event_loop> {
- /// Creates a new runtime. This must be the only user of the underlying `aos::EventLoop`, or
- /// things may panic unexpectedly.
+ /// Creates a new runtime. This must be the only user of the underlying `aos::EventLoop`.
+ ///
+ /// Consider using [`EventLoopRuntimeHolder.new`] instead, if you're working with an
+ /// `aos::EventLoop` owned (indirectly) by Rust code.
+ ///
+ /// One common pattern is calling this in the constructor of an object whose lifetime is managed
+ /// by C++; C++ doesn't inherit the Rust lifetime but we do have a lot of C++ code that obeys
+ /// these rules implicitly.
///
/// Call [`spawn`] to respond to events. The non-event-driven APIs may be used without calling
/// this.
@@ -165,27 +293,106 @@
///
/// # Safety
///
- /// `event_loop` must be valid for `'event_loop`. Effectively we want the argument to be
- /// `&'event_loop mut EventLoop`, but we can't do that (see the module-level documentation for
- /// details).
+ /// This function is where all the tricky lifetime guarantees to ensure soundness come
+ /// together. It all boils down to choosing `'event_loop` correctly, which is very complicated.
+ /// Here are the rules:
///
- /// This is a tricky thing to guarantee, be very cautious calling this function. It's an unbound
- /// lifetime so you should probably wrap it in a function that directly attaches a known
- /// lifetime. One common pattern is calling this in the constructor of an object whose lifetime
- /// is managed by C++; C++ doesn't inherit the Rust lifetime but we do have a lot of C++ code
- /// that obeys the rule of destroying the object before the EventLoop, which is equivalent to
- /// this restriction.
+ /// 1. The `aos::EventLoop` APIs, and any other consumer-facing APIs, of the underlying
+ /// `aos::EventLoop` *must* be exclusively used by this object, and things it calls, for
+ /// `'event_loop`.
+ /// 2. `'event_loop` extends until after the last time the underlying `aos::EventLoop` is run.
+ /// This is often beyond the lifetime of this Rust `EventLoopRuntime` object.
+ /// 3. `'event_loop` must outlive this object, because this object stores references to the
+ /// underlying `aos::EventLoop`.
+ /// 4. Any other references stored in the underlying `aos::EventLoop` must be valid for
+ /// `'event_loop`. The easiest way to ensure this is by not using the `aos::EventLoop` before
+ /// passing it to this object.
///
- /// In Rust terms, this is equivalent to storing `event_loop` in the returned object, which
- /// will dereference it throughout its lifetime, and the caller must guarantee this is sound.
+ /// Here are some corollaries:
+ ///
+ /// 1. The underlying `aos::EventLoop` must be dropped after this object.
+ /// 2. This object will store various references valid for `'event_loop` with a duration of
+ /// `'event_loop`, which is safe as long as they're both the same `'event_loop`. Note that
+ /// this requires this type to be invariant with respect to `'event_loop`.
+ /// 3. `event_loop` (the pointer being passed in) is effectively `Pin`, which is also implied
+ /// by the underlying `aos::EventLoop` C++ type.
+ /// 4. You cannot create multiple `EventLoopRuntime`s from the same underlying `aos::EventLoop`
+ /// or otherwise use it from a different application. The first one may create
+ /// mutable Rust references while the second one expects exclusive ownership, for example.
+ ///
+ /// `aos::EventLoop`'s public API is exclusively for consumers of the event loop. Some
+ /// subclasses extend this API. Additionally, all useful implementations of `aos::EventLoop`
+ /// must have some way to process events. Sometimes this is additional API surface (such as
+ /// `aos::ShmEventLoop`), in other cases comes via other objects holding references to the
+ /// `aos::EventLoop` (such as `aos::SimulatedEventLoopFactory`). This access to run the event
+ /// loop functions independently of the consuming functions in every way except lifetime of the
+ /// `aos::EventLoop`, and may be used independently of `'event_loop`.
+ ///
+ /// ## Discussion of the rules
+ ///
+ /// Rule 1 is similar to rule 3 (they're both similar to mutable borrowing), but rule 1 extends
+ /// for the entire lifetime of the object instead of being limited to the lifetime of an
+ /// individual borrow by an instance of this type. This is similar to the way [`Pin`]'s
+ /// estrictions extend for the entire lifetime of the object, until it is dropped.
+ ///
+ /// Rule 2 and corollaries 2 and 3 go together, and are essential for making [`spawn`]ed tasks
+ /// useful. The `aos::EventLoop` is full of indirect circular references, both within itself
+ /// and via all of the callbacks. This is sound if all of these references have the *exact
+ /// same* Rust lifetime, which is `'event_loop`.
+ ///
+ /// ## Alternatives and why they don't work
+ ///
+ /// Making the argument `Pin<&'event_loop mut EventLoop>` would express some (but not all) of
+ /// these restrictions within the Rust type system. However, having an actual Rust mutable
+ /// reference like that prevents anything else from creating one via other pointers to the
+ /// same object from C++, which is a common operation. See the module-level documentation for
+ /// details.
+ ///
+ /// [`spawn`]ed tasks need to hold `&'event_loop` references to things like channels. Using a
+ /// separate `'config` lifetime wouldn't change much; the tasks still need to do things which
+ /// require them to not outlive something they don't control. This is fundamental to
+ /// self-referential objects, which `aos::EventLoop` is based around, but Rust requires unsafe
+ /// code to manage manually.
+ ///
+ /// ## Final cautions
+ ///
+ /// Following these rules is very tricky. Be very cautious calling this function. It exposes an
+ /// unbound lifetime, which means you should wrap it directly in a function that attaches a
+ /// correct lifetime.
pub unsafe fn new(event_loop: *mut ffi::aos::EventLoop) -> Self {
Self(
// SAFETY: We push all the validity requirements for this up to our caller.
unsafe { ffi::aos::EventLoopRuntime::new(event_loop) }.within_box(),
- PhantomData,
+ InvariantLifetime::default(),
)
}
+ /// Creates a Rust wrapper from the underlying C++ object, with an unbound lifetime.
+ ///
+ /// This may never be useful, but it's here for this big scary comment to explain why it's not
+ /// useful.
+ ///
+ /// # Safety
+ ///
+ /// See [`new`] for safety restrictions on `'event_loop` when calling this. In particular, see
+ /// the note about how tricky doing this correctly is, and remember that for this function the
+ /// event loop in question isn't even an argument to this function so it's even trickier. Also
+ /// note that you cannot call this on the result of [`into_cpp`] without violating those
+ /// restrictions.
+ pub unsafe fn from_cpp(cpp: Pin<Box<ffi::aos::EventLoopRuntime>>) -> Self {
+ Self(cpp, InvariantLifetime::default())
+ }
+
+ /// Extracts the underlying C++ object, without the corresponding Rust lifetime. This is useful
+ /// to stop the propagation of Rust lifetimes without destroying the underlying object which
+ /// contains all the state.
+ ///
+ /// Note that you *cannot* call [`from_cpp`] on the result of this, because that will violate
+ /// [`from_cpp`]'s safety requirements.
+ pub fn into_cpp(self) -> Pin<Box<ffi::aos::EventLoopRuntime>> {
+ self.0
+ }
+
/// Returns the pointer passed into the constructor.
///
/// The returned value should only be used for destroying it (_after_ `self` is dropped) or
diff --git a/aos/events/simulated_event_loop.rs b/aos/events/simulated_event_loop.rs
index e0be773..ea3a154 100644
--- a/aos/events/simulated_event_loop.rs
+++ b/aos/events/simulated_event_loop.rs
@@ -1,18 +1,12 @@
-use std::{
- marker::PhantomData,
- mem::ManuallyDrop,
- ops::{Deref, DerefMut},
- pin::Pin,
- ptr,
-};
+use std::{marker::PhantomData, pin::Pin, ptr};
use autocxx::WithinBox;
use cxx::UniquePtr;
pub use aos_configuration::{Channel, Configuration, ConfigurationExt, Node};
use aos_configuration_fbs::aos::Configuration as RustConfiguration;
-use aos_events_event_loop_runtime::EventLoopRuntime;
pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoop, ExitHandle};
+use aos_events_event_loop_runtime::{EventLoopHolder, EventLoopRuntime, EventLoopRuntimeHolder};
use aos_flatbuffers::{transmute_table_to, Flatbuffer};
autocxx::include_cpp! (
@@ -44,9 +38,7 @@
}
impl<'config> SimulatedEventLoopFactory<'config> {
- pub fn new<'new_config: 'config>(
- config: &'new_config impl Flatbuffer<RustConfiguration<'static>>,
- ) -> Self {
+ pub fn new(config: &'config impl Flatbuffer<RustConfiguration<'static>>) -> Self {
// SAFETY: `_marker` represents the lifetime of this pointer we're handing off to C++ to
// store.
let event_loop_factory = unsafe {
@@ -75,7 +67,7 @@
// * `self` has a valid C++ object.
// * C++ doesn't need the lifetimes of `name` or `node` to last any longer than this method
// call.
- // * The return value is `'static` because it's wrapped in `unique_ptr`.
+ // * The return value manages its lifetime via `unique_ptr`.
//
// Note that dropping `self` before the return value will abort from C++, but this is still
// sound.
@@ -85,9 +77,26 @@
}
}
- /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying EventLoop.
- pub fn make_runtime(&mut self, name: &str, node: Option<&Node>) -> SimulatedEventLoopRuntime {
- SimulatedEventLoopRuntime::new(self.make_event_loop(name, node))
+ /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying [`EventLoop`].
+ ///
+ /// All setup must be performed with `fun`, which is called before this function returns. `fun`
+ /// may create further objects to use in async functions via [`EventLoop.spawn`] etc, but it is
+ /// the only place to set things up before the EventLoop is run.
+ ///
+ /// Note that dropping the return value will drop this EventLoop.
+ pub fn make_runtime<F>(
+ &mut self,
+ name: &str,
+ node: Option<&Node>,
+ fun: F,
+ ) -> EventLoopRuntimeHolder<SimulatedEventLoopHolder>
+ where
+ F: for<'event_loop> FnOnce(&mut EventLoopRuntime<'event_loop>),
+ {
+ let event_loop = self.make_event_loop(name, node);
+ // SAFETY: We just created this EventLoop, so we are the exclusive owner of it.
+ let holder = unsafe { SimulatedEventLoopHolder::new(event_loop) };
+ EventLoopRuntimeHolder::new(holder, fun)
}
pub fn make_exit_handle(&mut self) -> ExitHandle {
@@ -103,39 +112,24 @@
// pub fn spawn_on_startup(&mut self, spawner: impl FnMut());
}
-pub struct SimulatedEventLoopRuntime(ManuallyDrop<EventLoopRuntime<'static>>);
+pub struct SimulatedEventLoopHolder(UniquePtr<EventLoop>);
-impl SimulatedEventLoopRuntime {
- pub fn new(event_loop: UniquePtr<EventLoop>) -> Self {
- // SAFETY: We own the underlying EventLoop, so `'static` is the correct lifetime. Anything
- // using this `EventLoopRuntime` will need to borrow the object we're returning, which will
- // ensure it stays alive.
- let runtime = unsafe { EventLoopRuntime::<'static>::new(event_loop.into_raw()) };
- Self(ManuallyDrop::new(runtime))
+impl SimulatedEventLoopHolder {
+ /// SAFETY: `event_loop` must be the exclusive owner of the underlying EventLoop.
+ pub unsafe fn new(event_loop: UniquePtr<EventLoop>) -> Self {
+ Self(event_loop)
}
}
-impl Drop for SimulatedEventLoopRuntime {
- fn drop(&mut self) {
- let event_loop = self.raw_event_loop();
- // SAFETY: We're not going to touch this field again.
- unsafe { ManuallyDrop::drop(&mut self.0) };
- // SAFETY: `new` created this from `into_raw`. We just dropped the only Rust reference to
- // it.
- unsafe { UniquePtr::from_raw(event_loop) };
+// SAFETY: The UniquePtr functions we're using here mirror most of the EventLoopHolder requirements
+// exactly. Safety requirements on [`SimulatedEventLoopHolder.new`] take care of the rest.
+unsafe impl EventLoopHolder for SimulatedEventLoopHolder {
+ fn into_raw(self) -> *mut ffi::aos::EventLoop {
+ self.0.into_raw()
}
-}
-impl Deref for SimulatedEventLoopRuntime {
- type Target = EventLoopRuntime<'static>;
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl DerefMut for SimulatedEventLoopRuntime {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.0
+ unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self {
+ Self(UniquePtr::from_raw(raw))
}
}
@@ -172,41 +166,49 @@
let mut event_loop_factory = SimulatedEventLoopFactory::new(&config);
{
let pi1 = Some(config.message().get_node("pi1").unwrap());
- let mut runtime1 = event_loop_factory.make_runtime("runtime1", pi1);
- let channel = runtime1
- .configuration()
- .get_channel("/test", "aos.examples.Ping", "test", pi1)
- .unwrap();
- let mut runtime2 = event_loop_factory.make_runtime("runtime2", pi1);
- {
- let mut watcher = runtime1.make_raw_watcher(channel);
- let exit_handle = event_loop_factory.make_exit_handle();
- runtime1.spawn(async move {
- watcher.next().await;
- GLOBAL_STATE.with(|g| {
- let g = &mut *g.borrow_mut();
- g.watcher_count = g.watcher_count + 1;
+ let exit_handle = event_loop_factory.make_exit_handle();
+ let _runtime1 = {
+ let pi1_ref = &pi1;
+ event_loop_factory.make_runtime("runtime1", pi1, move |runtime1| {
+ assert!(pi1_ref.unwrap().has_name());
+ let channel = runtime1
+ .get_raw_channel("/test", "aos.examples.Ping")
+ .unwrap();
+ let mut watcher = runtime1.make_raw_watcher(channel);
+ runtime1.spawn(async move {
+ watcher.next().await;
+ GLOBAL_STATE.with(|g| {
+ let g = &mut *g.borrow_mut();
+ g.watcher_count = g.watcher_count + 1;
+ });
+ exit_handle.exit().await
});
- exit_handle.exit().await
- });
- }
+ })
+ };
- {
- let mut sender = runtime2.make_raw_sender(channel);
- runtime2.spawn(async move {
- GLOBAL_STATE.with(|g| {
- let g = &mut *g.borrow_mut();
- g.startup_count = g.startup_count + 1;
+ let _runtime2 = {
+ let pi1_ref = &pi1;
+ event_loop_factory.make_runtime("runtime2", pi1, |runtime2| {
+ assert!(pi1_ref.unwrap().has_name());
+ let channel = runtime2
+ .get_raw_channel("/test", "aos.examples.Ping")
+ .unwrap();
+ let mut sender = runtime2.make_raw_sender(channel);
+ runtime2.spawn(async move {
+ GLOBAL_STATE.with(|g| {
+ let g = &mut *g.borrow_mut();
+ g.startup_count = g.startup_count + 1;
+ });
+
+ let mut builder = sender.make_builder();
+ let ping = PingBuilder::new(builder.fbb()).finish();
+ // SAFETY: We're using the correct message type.
+ unsafe { builder.send(ping) }.expect("send should succeed");
+ pending().await
});
-
- let mut builder = sender.make_builder();
- let ping = PingBuilder::new(builder.fbb()).finish();
- // SAFETY: We're using the correct message type.
- unsafe { builder.send(ping) }.expect("send should succeed");
- pending().await
- });
- }
+ })
+ };
GLOBAL_STATE.with(|g| {
let g = g.borrow();
diff --git a/frc971/control_loops/BUILD b/frc971/control_loops/BUILD
index a62b45f..3cdb6ab 100644
--- a/frc971/control_loops/BUILD
+++ b/frc971/control_loops/BUILD
@@ -199,6 +199,13 @@
],
)
+flatbuffer_cc_library(
+ name = "can_falcon_fbs",
+ srcs = [
+ "can_falcon.fbs",
+ ],
+)
+
cc_test(
name = "position_sensor_sim_test",
srcs = [
diff --git a/frc971/control_loops/can_falcon.fbs b/frc971/control_loops/can_falcon.fbs
new file mode 100644
index 0000000..2adbae0
--- /dev/null
+++ b/frc971/control_loops/can_falcon.fbs
@@ -0,0 +1,26 @@
+namespace frc971.control_loops;
+
+table CANFalcon {
+ // The CAN id of the falcon
+ id:int (id: 0);
+
+ // In Amps
+ supply_current:float (id: 1);
+
+ // In Amps
+ // Stator current where positive current means torque is applied in
+ // the motor's forward direction as determined by its Inverted setting.
+ torque_current:float (id: 2);
+
+ // In Volts
+ supply_voltage:float (id: 3);
+
+ // In degrees Celsius
+ device_temp:float (id: 4);
+
+ // In meters traveled on the drivetrain
+ position:float (id: 5);
+
+ // Nominal range is -1 to 1, but can be -2 to +2
+ duty_cycle: float (id: 6);
+}
diff --git a/frc971/control_loops/drivetrain/BUILD b/frc971/control_loops/drivetrain/BUILD
index 86c1ec0..29a0e1d 100644
--- a/frc971/control_loops/drivetrain/BUILD
+++ b/frc971/control_loops/drivetrain/BUILD
@@ -8,6 +8,13 @@
package(default_visibility = ["//visibility:public"])
flatbuffer_cc_library(
+ name = "drivetrain_can_position_fbs",
+ srcs = ["drivetrain_can_position.fbs"],
+ gen_reflections = 1,
+ deps = ["//frc971/control_loops:can_falcon_fbs"],
+)
+
+flatbuffer_cc_library(
name = "spline_goal_fbs",
srcs = ["spline_goal.fbs"],
gen_reflections = 1,
@@ -352,11 +359,11 @@
":drivetrain_config",
":drivetrain_states",
":gear",
+ ":spline_goal_fbs",
"//aos:math",
- "//frc971/control_loops:polytope",
"//frc971/control_loops:coerce_goal",
"//frc971/control_loops:control_loops_fbs",
- ":spline_goal_fbs",
+ "//frc971/control_loops:polytope",
"//frc971/control_loops:state_feedback_loop",
] + select({
"@platforms//os:linux": [
@@ -364,8 +371,8 @@
":drivetrain_output_fbs",
":drivetrain_position_fbs",
":drivetrain_status_fbs",
- "//frc971/input:robot_state_fbs",
"//aos/util:log_interval",
+ "//frc971/input:robot_state_fbs",
],
"@platforms//os:none": [
":drivetrain_goal_float_fbs",
@@ -469,8 +476,8 @@
"//frc971/control_loops:control_loops_fbs",
"//frc971/control_loops:state_feedback_loop",
"//frc971/queues:gyro_fbs",
- "//frc971/wpilib:imu_fbs",
"//frc971/wpilib:imu_batch_fbs",
+ "//frc971/wpilib:imu_fbs",
"//y2016:constants",
"//y2016/control_loops/drivetrain:polydrivetrain_plants",
] + cpu_select({
@@ -498,17 +505,17 @@
target_compatible_with = ["@platforms//os:linux"],
deps = [
":drivetrain_config",
- ":trajectory_generator",
- ":drivetrain_lib",
- ":localizer_fbs",
":drivetrain_goal_fbs",
- ":drivetrain_status_fbs",
- ":drivetrain_position_fbs",
+ ":drivetrain_lib",
":drivetrain_output_fbs",
+ ":drivetrain_position_fbs",
+ ":drivetrain_status_fbs",
":drivetrain_test_lib",
- "//frc971/control_loops:control_loop_test",
+ ":localizer_fbs",
+ ":trajectory_generator",
"//aos/events/logging:log_writer",
"//aos/testing:googletest",
+ "//frc971/control_loops:control_loop_test",
"//frc971/queues:gyro_fbs",
"//frc971/wpilib:imu_fbs",
] + cpu_select({
@@ -633,8 +640,8 @@
target_compatible_with = ["@platforms//os:linux"],
deps = [
":distance_spline",
- "//aos/testing:googletest",
"//aos:flatbuffers",
+ "//aos/testing:googletest",
"//aos/testing:test_shm",
"@com_github_gflags_gflags//:gflags",
] + cpu_select({
@@ -712,8 +719,8 @@
linkstatic = True,
target_compatible_with = ["@platforms//os:linux"],
deps = [
- ":trajectory",
":drivetrain_test_lib",
+ ":trajectory",
"//aos/testing:googletest",
"//aos/testing:test_shm",
"//y2016:constants",
diff --git a/frc971/control_loops/drivetrain/drivetrain_can_position.fbs b/frc971/control_loops/drivetrain/drivetrain_can_position.fbs
new file mode 100644
index 0000000..192f23d
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_can_position.fbs
@@ -0,0 +1,18 @@
+include "frc971/control_loops/can_falcon.fbs";
+namespace frc971.control_loops.drivetrain;
+
+// CAN readings from the CAN sensor reader loop
+table CANPosition {
+ falcons: [CANFalcon] (id: 0);
+
+ // The timestamp of the measurement on the canivore clock in nanoseconds
+ // This will have less jitter than the
+ // timestamp of the message being sent out.
+ timestamp:int64 (id: 1);
+
+ // The ctre::phoenix::StatusCode of the measurement
+ // Should be OK = 0
+ status:int (id: 2);
+}
+
+root_type CANPosition;
diff --git a/frc971/control_loops/drivetrain/swerve/BUILD b/frc971/control_loops/drivetrain/swerve/BUILD
new file mode 100644
index 0000000..06ba36a
--- /dev/null
+++ b/frc971/control_loops/drivetrain/swerve/BUILD
@@ -0,0 +1,15 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
+package(default_visibility = ["//visibility:public"])
+
+flatbuffer_cc_library(
+ name = "swerve_drivetrain_output_fbs",
+ srcs = ["swerve_drivetrain_output.fbs"],
+ gen_reflections = 1,
+)
+
+flatbuffer_cc_library(
+ name = "swerve_drivetrain_position_fbs",
+ srcs = ["swerve_drivetrain_position.fbs"],
+ gen_reflections = 1,
+)
diff --git a/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_output.fbs b/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_output.fbs
new file mode 100644
index 0000000..43ba0ed
--- /dev/null
+++ b/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_output.fbs
@@ -0,0 +1,16 @@
+namespace frc971.control_loops.drivetrain.swerve;
+
+table SwerveModuleOutput {
+ // Current in Amps.
+ rotation_current:double (id: 0);
+ translation_current:double (id: 1);
+}
+
+table Output {
+ front_left_output:SwerveModuleOutput (id: 0);
+ front_right_output:SwerveModuleOutput (id: 1);
+ back_left_output:SwerveModuleOutput (id: 2);
+ back_right_output:SwerveModuleOutput (id: 3);
+}
+
+root_type Output;
diff --git a/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_position.fbs b/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_position.fbs
new file mode 100644
index 0000000..e0f5571
--- /dev/null
+++ b/frc971/control_loops/drivetrain/swerve/swerve_drivetrain_position.fbs
@@ -0,0 +1,22 @@
+namespace frc971.control_loops.drivetrain.swerve;
+
+table SwerveModulePosition {
+ // Rotation in radians
+ rotation_encoder:double (id: 0);
+ // Translation in meters
+ translation_encoder:double (id: 1);
+
+ // Speed in radians/s
+ rotation_speed:double (id: 2);
+ // Speed in m/s
+ translation_speed:double (id: 3);
+}
+
+table Position {
+ front_left_position:SwerveModulePosition (id: 0);
+ front_right_position:SwerveModulePosition (id: 1);
+ back_left_position:SwerveModulePosition (id: 2);
+ back_right_position:SwerveModulePosition (id: 3);
+}
+
+root_type Position;
diff --git a/y2023/BUILD b/y2023/BUILD
index fa39d3c..ed8f347 100644
--- a/y2023/BUILD
+++ b/y2023/BUILD
@@ -213,7 +213,7 @@
"//y2023/control_loops/superstructure:superstructure_goal_fbs",
"//y2023/control_loops/drivetrain:target_selector_hint_fbs",
"//y2023/control_loops/drivetrain:target_selector_status_fbs",
- "//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
+ "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
"//y2023/control_loops/superstructure:superstructure_output_fbs",
"//y2023/control_loops/superstructure:superstructure_position_fbs",
"//y2023/control_loops/superstructure:superstructure_status_fbs",
@@ -286,6 +286,7 @@
"//frc971/autonomous:auto_mode_fbs",
"//frc971/control_loops:control_loop",
"//frc971/control_loops:control_loops_fbs",
+ "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
"//frc971/control_loops/drivetrain:drivetrain_position_fbs",
"//frc971/input:robot_state_fbs",
"//frc971/queues:gyro_fbs",
@@ -304,7 +305,6 @@
"//third_party:phoenix",
"//third_party:phoenixpro",
"//third_party:wpilib",
- "//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
"//y2023/control_loops/superstructure:led_indicator_lib",
"//y2023/control_loops/superstructure:superstructure_output_fbs",
"//y2023/control_loops/superstructure:superstructure_position_fbs",
diff --git a/y2023/control_loops/drivetrain/BUILD b/y2023/control_loops/drivetrain/BUILD
index 063e972..f6b5655 100644
--- a/y2023/control_loops/drivetrain/BUILD
+++ b/y2023/control_loops/drivetrain/BUILD
@@ -116,15 +116,6 @@
)
flatbuffer_cc_library(
- name = "drivetrain_can_position_fbs",
- srcs = [
- "drivetrain_can_position.fbs",
- ],
- gen_reflections = 1,
- visibility = ["//visibility:public"],
-)
-
-flatbuffer_cc_library(
name = "target_selector_status_fbs",
srcs = [
":target_selector_status.fbs",
diff --git a/y2023/control_loops/drivetrain/drivetrain_can_position.fbs b/y2023/control_loops/drivetrain/drivetrain_can_position.fbs
deleted file mode 100644
index 34fbf12..0000000
--- a/y2023/control_loops/drivetrain/drivetrain_can_position.fbs
+++ /dev/null
@@ -1,42 +0,0 @@
-namespace y2023.control_loops.drivetrain;
-
-table CANFalcon {
- // The CAN id of the falcon
- id:int (id: 0);
-
- // In Amps
- supply_current:float (id: 1);
-
- // In Amps
- // Stator current where positive current means torque is applied in
- // the motor's forward direction as determined by its Inverted setting.
- torque_current:float (id: 2);
-
- // In Volts
- supply_voltage:float (id: 3);
-
- // In degrees Celsius
- device_temp:float (id: 4);
-
- // In meters traveled on the drivetrain
- position:float (id: 5);
-
- // Nominal range is -1 to 1, but can be -2 to +2
- duty_cycle: float (id: 6);
-}
-
-// CAN readings from the CAN sensor reader loop
-table CANPosition {
- falcons: [CANFalcon] (id: 0);
-
- // The timestamp of the measurement on the canivore clock in nanoseconds
- // This will have less jitter than the
- // timestamp of the message being sent out.
- timestamp:int64 (id: 1);
-
- // The ctre::phoenix::StatusCode of the measurement
- // Should be OK = 0
- status:int (id: 2);
-}
-
-root_type CANPosition;
diff --git a/y2023/control_loops/superstructure/BUILD b/y2023/control_loops/superstructure/BUILD
index afca18b..861bbf8 100644
--- a/y2023/control_loops/superstructure/BUILD
+++ b/y2023/control_loops/superstructure/BUILD
@@ -58,8 +58,8 @@
deps = [
"//frc971/control_loops:control_loops_fbs",
"//frc971/control_loops:profiled_subsystem_fbs",
+ "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
"//frc971/vision:calibration_fbs",
- "//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
],
)
@@ -103,12 +103,12 @@
"//aos/events:event_loop",
"//frc971/constants:constants_sender_lib",
"//frc971/control_loops:control_loop",
+ "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
"//frc971/control_loops/drivetrain:drivetrain_status_fbs",
"//frc971/shooter_interpolation:interpolation",
"//y2023:constants",
"//y2023/constants:constants_fbs",
"//y2023/constants:simulated_constants_sender",
- "//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
"//y2023/control_loops/superstructure/arm",
"//y2023/control_loops/superstructure/arm:arm_trajectories_fbs",
],
diff --git a/y2023/control_loops/superstructure/arm/BUILD b/y2023/control_loops/superstructure/arm/BUILD
index 39e549b..a742c50 100644
--- a/y2023/control_loops/superstructure/arm/BUILD
+++ b/y2023/control_loops/superstructure/arm/BUILD
@@ -14,9 +14,9 @@
":generated_graph",
"//frc971/control_loops/double_jointed_arm:ekf",
"//frc971/control_loops/double_jointed_arm:graph",
+ "//frc971/control_loops/drivetrain:drivetrain_can_position_fbs",
"//frc971/zeroing",
"//y2023:constants",
- "//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
"//y2023/control_loops/superstructure:superstructure_position_fbs",
"//y2023/control_loops/superstructure:superstructure_status_fbs",
"//y2023/control_loops/superstructure/arm:arm_constants",
diff --git a/y2023/control_loops/superstructure/superstructure.h b/y2023/control_loops/superstructure/superstructure.h
index 1100b86..fdfef4e 100644
--- a/y2023/control_loops/superstructure/superstructure.h
+++ b/y2023/control_loops/superstructure/superstructure.h
@@ -5,10 +5,10 @@
#include "aos/json_to_flatbuffer.h"
#include "frc971/constants/constants_sender_lib.h"
#include "frc971/control_loops/control_loop.h"
+#include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
#include "y2023/constants.h"
#include "y2023/constants/constants_generated.h"
-#include "y2023/control_loops/drivetrain/drivetrain_can_position_generated.h"
#include "y2023/control_loops/superstructure/arm/arm.h"
#include "y2023/control_loops/superstructure/arm/arm_trajectories_generated.h"
#include "y2023/control_loops/superstructure/end_effector.h"
diff --git a/y2023/wpilib_interface.cc b/y2023/wpilib_interface.cc
index 978d6e5..d043e6f 100644
--- a/y2023/wpilib_interface.cc
+++ b/y2023/wpilib_interface.cc
@@ -40,6 +40,7 @@
#include "aos/util/phased_loop.h"
#include "aos/util/wrapping_counter.h"
#include "frc971/autonomous/auto_mode_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_can_position_generated.h"
#include "frc971/control_loops/drivetrain/drivetrain_position_generated.h"
#include "frc971/input/robot_state_generated.h"
#include "frc971/queues/gyro_generated.h"
@@ -57,7 +58,6 @@
#include "frc971/wpilib/wpilib_robot_base.h"
#include "y2023/can_configuration_generated.h"
#include "y2023/constants.h"
-#include "y2023/control_loops/drivetrain/drivetrain_can_position_generated.h"
#include "y2023/control_loops/superstructure/led_indicator.h"
#include "y2023/control_loops/superstructure/superstructure_output_generated.h"
#include "y2023/control_loops/superstructure/superstructure_position_generated.h"
@@ -224,9 +224,9 @@
ctre::phoenixpro::hardware::TalonFX *talon() { return &talon_; }
- flatbuffers::Offset<drivetrain::CANFalcon> WritePosition(
+ flatbuffers::Offset<frc971::control_loops::CANFalcon> WritePosition(
flatbuffers::FlatBufferBuilder *fbb) {
- drivetrain::CANFalcon::Builder builder(*fbb);
+ frc971::control_loops::CANFalcon::Builder builder(*fbb);
builder.add_id(device_id_);
builder.add_device_temp(device_temp());
builder.add_supply_voltage(supply_voltage());
@@ -289,7 +289,9 @@
: event_loop_(event_loop),
signals_(signals_registry.begin(), signals_registry.end()),
can_position_sender_(
- event_loop->MakeSender<drivetrain::CANPosition>("/drivetrain")),
+ event_loop
+ ->MakeSender<frc971::control_loops::drivetrain::CANPosition>(
+ "/drivetrain")),
roller_falcon_data_(std::nullopt) {
event_loop->SetRuntimeRealtimePriority(40);
event_loop->SetRuntimeAffinity(aos::MakeCpusetFromCpus({1}));
@@ -340,7 +342,8 @@
falcon->RefreshNontimesyncedSignals();
}
- aos::SizedArray<flatbuffers::Offset<drivetrain::CANFalcon>, kCANFalconCount>
+ aos::SizedArray<flatbuffers::Offset<frc971::control_loops::CANFalcon>,
+ kCANFalconCount>
falcons;
for (auto falcon : {right_front_, right_back_, right_under_, left_front_,
@@ -349,11 +352,14 @@
}
auto falcons_list =
- builder.fbb()->CreateVector<flatbuffers::Offset<drivetrain::CANFalcon>>(
- falcons);
+ builder.fbb()
+ ->CreateVector<
+ flatbuffers::Offset<frc971::control_loops::CANFalcon>>(falcons);
- drivetrain::CANPosition::Builder can_position_builder =
- builder.MakeBuilder<drivetrain::CANPosition>();
+ frc971::control_loops::drivetrain::CANPosition::Builder
+ can_position_builder =
+ builder
+ .MakeBuilder<frc971::control_loops::drivetrain::CANPosition>();
can_position_builder.add_falcons(falcons_list);
can_position_builder.add_timestamp(right_front_->GetTimestamp());
@@ -379,7 +385,8 @@
aos::EventLoop *event_loop_;
const std::vector<ctre::phoenixpro::BaseStatusSignalValue *> signals_;
- aos::Sender<drivetrain::CANPosition> can_position_sender_;
+ aos::Sender<frc971::control_loops::drivetrain::CANPosition>
+ can_position_sender_;
std::shared_ptr<Falcon> right_front_, right_back_, right_under_, left_front_,
left_back_, left_under_, roller_falcon_;
diff --git a/y2023/y2023_roborio.json b/y2023/y2023_roborio.json
index 4af3d34..33d9904 100644
--- a/y2023/y2023_roborio.json
+++ b/y2023/y2023_roborio.json
@@ -154,7 +154,7 @@
},
{
"name": "/drivetrain",
- "type": "y2023.control_loops.drivetrain.CANPosition",
+ "type": "frc971.control_loops.drivetrain.CANPosition",
"source_node": "roborio",
"frequency": 220,
"num_senders": 2,