-
Notifications
You must be signed in to change notification settings - Fork 83
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
Discussion: A better Message
trait
#156
Comments
P.S. The |
As far as I can tell, The problem with arrays is that the PS: Did you mean |
The You can not Const |
Oh I see, I will try to make a PR for them. As far as I can tell they only use What I mean is generating the #[derive(Default, Clone)]
pub struct MY_SPECIAL_MESSAGE_DATA {
pub my_field: [u8; 128],
} instead, generate #[derive(Clone)]
pub struct MY_SPECIAL_MESSAGE_DATA {
pub my_field: [u8; 128],
}
impl MY_SPECIAL_MESSAGE_DATA {
const DEFAULT: Self = MY_SPECIAL_MESSAGE_DATA {
my_field: [0; 128],
};
}
impl Default for MY_SPECIAL_MESSAGE_DATA {
fn default() -> Self {
Self::DEFAULT.clone()
}
} this works on stable Rust and is already implemented here if you'd like to take a look. |
It is good idea. You can make small PR, which remove Need to think about MessageMeta and MessageData, I think that it can simplify. |
Will do. My only concerns with
|
I think Traits supports associated constants in stable rust (1.20). pub trait MessageData {
const ID: u32;
const NAME: &'static str;
const EXTRA_CRC: u8;
const SERIALIZED_LEN: u8;
fn serialize_payload(&self, version: MavlinkVersion, payload: &mut [u8]) -> usize;
fn deserialize_payload(version: MavlinkVersion, payload: &[u8]) -> Result<Self, ParserError>;
}
impl MAVLinkV[1|2]MessageRaw {
...
pub fn serialize_message_data(&mut self, header: MavHeader, message_data: &impl MessageData) {
...
}
}
pub fn read_v[1|2]_msg_data<D: MessageData, R: Read>(
read: &mut R,
) -> Result<(MavHeader, D), error::MessageReadError> {
...
} It allows read and write |
I thing this is better than what we have now, however it doesn't really solve the issues of how to get message metadata from the message id or name. In this case I think using a concrete type is better, since we can also pass it around without needing |
Your API requires ~16 bytes on each My suggestion is to separate the convenient |
I don't understand how this can increase memory usage in comparison to the current API. Currently code needs to be generated for the If you want to only use a few messages, just use the |
I got interested and ran some tests. I used the embedded example provided by this crate in order to get some binary size measurements. Note that in the example no metadata information is used, so it should be eliminated as dead code. (The implementation of this proposal is also using the From the results bellow, its clear that the compiler can eliminate unreachable Embedded example
Embedded example (using
|
Impl | Binary size | .text size |
.data size |
.bss size |
---|---|---|---|---|
proposed* | 88K | 80592 | 0 | 4 |
baseline | 100K | 92068 | 0 | 4 |
(*): Without the MessageData::default
field.
How to reproduce
- Go to
examples/embedded
directory - Edit
Cargo.toml
and addstrip = true
below[profile.release]
. - Build with
cargo build --release
- Measure total binary size with
du -h target/thumbv7em-none-eabihf/release/mavlink-embedded
- Measure
text
,data
andbss
size withsize target/thumbv7em-none-eabihf/release/mavlink-embedded
Changes to the example in order to use metadata
diff --git a/examples/embedded/src/main.rs b/examples/embedded/src/main.rs
index 4a71ca8..b3adc56 100644
--- a/examples/embedded/src/main.rs
+++ b/examples/embedded/src/main.rs
@@ -9,7 +9,7 @@ use panic_halt as _;
use cortex_m_rt::entry;
use hal::pac;
use hal::prelude::*;
-use mavlink;
+use mavlink::Message;
use stm32f3xx_hal as hal;
#[entry]
@@ -57,7 +57,7 @@ fn main() -> ! {
);
// Break serial in TX and RX (not used)
- let (mut tx, _) = serial.split();
+ let (mut tx, mut rx) = serial.split();
// Create our mavlink header and heartbeat message
let header = mavlink_header();
@@ -72,6 +72,10 @@ fn main() -> ! {
mavlink::write_versioned_msg(&mut tx, mavlink::MavlinkVersion::V2, header, &heartbeat)
.unwrap();
+ let (_, msg) = mavlink::read_versioned_msg::<mavlink::common::MavMessage, _>(&mut rx, mavlink::MavlinkVersion::V2).unwrap();
+ let id = msg.meta().id.to_be_bytes();
+ tx.write(id[0]).unwrap();
+
// Toggle the LED
led.toggle().unwrap();
for the version on master
I just used msg.message_id()
instead of msg.meta().id
.
Embedded example is very simple. In real code I use RTOS, allocators, lock-free queues, et.al.
then initialize message
put message to lock-free queue and call interrupt
get message from lock-free queue within DMA1_STREAM5 interrupt,
Compiler generates full If put aside the |
Yes, it can be made constant. In fact, the entire trait MessageData {
const META: MessageMeta;
/*...*/
} But this has nothing to do with the reason your usecase generates a lot of code. I can see how using For code generation this would mean we use #[repr(u32)]
pub enum MavMessage {
VARIANT(VARIANT_DATA) = <id>,
/*...*/
}
impl Message for MavMessage {
fn message_id(&self) -> u32 {
// SAFETY: Because `Self` is marked `repr(u32)`, its layout is a `repr(C)` `union`
// between `repr(C)` structs, each of which has the `u8` discriminant as its first
// field, so we can read the discriminant without offsetting the pointer.
unsafe { *<*const _>::from(self).cast::<u32>() }
}
} the code and safety comment were taken from the With the current proposal this would mean we actually have two ways of accessing the message id:
But I think this is still very acceptable since |
At first it seems like a good idea. But I checked the generated assembler code on Cortex-M4 (ARM).
It is long
compiler generates one
because discriminant is 1, 2, 3, ... For SummaryThe |
Interesting finds! |
Oh, interesting... But I guess this example isn't large enough to represent a real usecase. In particular, pretty much all message ids are in range Summary
|
You example with With |
Great, so can we agree that this optimization is indeed better overall? Since although without |
With Thus FLASH consumption is approximately the same. |
Well, yes. But that's only if you actually use |
Introduction
Currently the
Message
trait is defined like thisThen types implementing
Message
are structs of several*_DATA
types that store the actual data for the message. Each of those types hasser
anddeser
methods but no unifying trait to encompass this behaviour. They also have an associated constantENCODED_LEN
and implementDefault
.Current limitations and issues
Result<T, &'static str>
isn't very helpfull in this case. There is a single reason for this to fail: the massage with the given name/id doesn't exist! However, the type indicates that many different error messages could be emitted. A better type would beResult<T, MessageDoesNotExist>
or simplyOption<T>
.message_id_from_name
,default_message_from_id
,extra_crc
,message_id
andmessage_name
are all trying to get some metadata about the message, though some do that with an instance of the message and others are static. We could instead return a&'static MessageMeta
type that stores all the metadata in a single place. This would allow stuff like getting the message name from an id without having to construct the message. We would also avoid having to do multiple calls if we want multiple pieces of metadata. The metadata struct can also be passed around which is pretty usefull.MessageMeta
struct could also store the default value for the message. Then we would have oneMessageMeta
static for every message*_DATA
.heapless
, but very little of the library is actually used (onlyVec
) and those uses could just be standard Rust arrays.Bytes
andBytesMut
, use thebytes
crate. It's a tiny crate that does pretty much the same as this crate's implementation, but uses a trait instead of a type, and implements the trait for slices.A better interface
Additionaly, every message could implement another trait
this trait is parameterized on
M: Message
since a single*_DATA
type can be used in multiple message enums. Issues 4 and 5 are about the internal message implementation and not the external interface, but switching to use them is pretty trivial. For serde, we can useserde_arrays
which is also tiny, at least untilserde
supports const generics.Also, using
bytes
might make it easier to integrate with other libraries in the future, such as tokio_util::codec::Decoder.Final notes
I have implemented pretty much all of these changes already here, but I would like some feedback if these are things you would be interested in changing in the library. It's a big change, but I think the API would be much better with them. Anyway, let me know what you think!
The text was updated successfully, but these errors were encountered: