-
Notifications
You must be signed in to change notification settings - Fork 48
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
Implement Observable POC #157
base: staged-pac
Are you sure you want to change the base?
Changes from 1 commit
312436d
845448e
d1c6c22
46d43fe
1e332f9
fa03e3b
97fd4e4
c9c5695
34aa3c6
595723c
8fa58c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
use crate::Sealed; | ||
use core::marker::PhantomData; | ||
|
||
pub trait IntoObservationToken: Sized + crate::Sealed { | ||
type Peripheral; | ||
fn into_ot(self) -> ObservationToken<Self::Peripheral>; | ||
} | ||
|
||
/// A struct to hold peripherals which are to be observed. | ||
/// | ||
/// This prevents the observed peripheral from being consumed. Thus | ||
/// preventing things like a an observed gpio pin changing mode or an opamp from | ||
/// being disabled. This makes sure the underlaying peripheral will not | ||
/// change mode into something that is not compatible with what ever may be observing it. | ||
pub struct Observed<P, const OBSERVER_COUNT: usize> { | ||
peripheral: P, | ||
} | ||
|
||
impl<P, const OBSERVER_COUNT: usize> Observed<P, OBSERVER_COUNT> { | ||
/// Release the observation of this peripheral | ||
/// | ||
/// This returns the underlaying perpheral type. Since it is no longer | ||
/// observed, you are once again free to do what you want with it. | ||
pub fn release(self, _data: [ObservationToken<P>; OBSERVER_COUNT]) -> P { | ||
self.peripheral | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should definitely constrain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for dumb question, but why? The only way to construct an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not dumb at all. In all of these cases we want Up to you, but I prefer erring on the side of caution at the expense of additional verbosity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Makes sense |
||
|
||
/// A struct to represent a registered observation of a peripheral of type `P` | ||
/// | ||
/// The existence of this type guarantees that the observed peripheral will not | ||
/// change mode into something that is not compatibe with what ever is observing it | ||
pub struct ObservationToken<P> { | ||
_p: PhantomData<P>, | ||
} | ||
|
||
/// A trait providing an interface to make peripherals observed | ||
/// | ||
/// See [`Observed`] and [`ObservationToken`] | ||
pub trait Observable: Sized { | ||
fn observe<const N: usize>(self) -> (Observed<Self, N>, [ObservationToken<Self>; N]) { | ||
( | ||
Observed { peripheral: self }, | ||
core::array::from_fn(|_| ObservationToken { _p: PhantomData }), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did some testing and determined this does not introduce any overhead. I recall that being a concern. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, thanks! |
||
) | ||
} | ||
} | ||
|
||
impl<P: Observable + Sealed> IntoObservationToken for P { | ||
type Peripheral = P; | ||
fn into_ot(self) -> ObservationToken<Self::Peripheral> { | ||
let (_peripheral, [ot]) = self.observe(); | ||
ot | ||
} | ||
} | ||
|
||
impl<P: Observable + Sealed> Sealed for ObservationToken<P> {} | ||
impl<P: Observable + Sealed> IntoObservationToken for ObservationToken<P> { | ||
type Peripheral = P; | ||
fn into_ot(self) -> Self { | ||
self | ||
} | ||
} | ||
|
||
impl<P, const N: usize> AsRef<P> for Observed<P, N> { | ||
fn as_ref(&self) -> &P { | ||
&self.peripheral | ||
} | ||
} | ||
|
||
impl<P, const N: usize> AsMut<P> for Observed<P, N> { | ||
fn as_mut(&mut self) -> &mut P { | ||
&mut self.peripheral | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to do this since we can not do this the trait impl rules
do you know if there is a better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The most recent version of this concept (with a few name changes and being used in a brand new context) is here. The simple solution to this is to implement
From
rather thanInto
. This is a good idea in general (save some rare edge cases).Another option - which eliminates the need for calling
.into()
to directly pass a peripheral as an observation token - is to create a new trait calledObservationLock
which is implemented by allP: Observable
andObservationToken
s. Interfaces would thus holdimpl ObservationLock
s rather than concreteObservationToken
s. This is described in this gist.The former is a solution I am putting to use right now in proto-hal, the latter I suspect is a better solution, but have not implemented quite yet.
Hope this helps!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the
ObservationLock
trait is unneeded since the interfaces can accept anInto<ObservationToken<P>>
. Clever!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want the user to be able to destruct the consumer of the
ObservationToken
how does this affect things?If the user passed an actual owned peripheral than would it not be best to just not convert it and let the consumer store the peripheral as is until it is destructed?
In that case would
ObservationLock
make more sense since that better signals what we are doing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes! That was why
ObservationLock
was good. Since the peripherals would store animpl ObservationLock
, therelease()
would return back whatever was originally passed in.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting!