-
-
Notifications
You must be signed in to change notification settings - Fork 85
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
RFC: Simplify user-experience of trait Logger
#612
Comments
Another though I just had, since we currently hand static ENCODER: defmt::Encoder = Encoder::new(|bytes: &[u8]| {
// write to the host...
});
impl defmt::Logger for Logger {
fn acquire() {
// acquire lock...
unsafe { ENCODER.start_frame() }
}
unsafe fn flush() { /* ... */ }
unsafe fn release() {
ENCODER.end_frame();
// release lock...
}
unsafe fn write(bytes: &[u8]) {
ENCODER.write(bytes);
}
} |
let me preamble this with: any change to the I think this proposal is a step in the right direction: the implementer shouldn't need to care about the To accommodate that I think the trait would end like this: (As I have stated before I'm not a fan of the current pub unsafe trait Logger {
// CONTRACT CHANGED: does not need to call the encoder
fn acquire();
unsafe fn release();
// contract unchanged
unsafe fn flush();
// CONTRACT CHANGED: receives post-Encoder bytes
unsafe fn write(bytes: &[u8]);
// GUARANTEE: called after succesful `acquire` and before `release`
// CONTRACT: returns a pointer to an allocated Encoder
unsafe fn encoder() -> NonNull<Encoder>; // (+)
} (+) why not references? Then for a disable-all-interrupts logger you would have: struct SomeLogger;
// the constructor `new` is the only public method Encoder has
static mut ENCODER: Encoder = Encoder::new();
unsafe impl Logger for SomeLogger {
fn acquire() {
// disable interrupts
// guard aganst nested-acquire
}
unsafe fn release() {
// re-enable interrupts
}
unsafe fn flush() {
// do flush
}
unsafe fn write(bytes: &[u8]) {
// write bytes VERBATIM
}
unsafe fn encoder() -> NonNull<Encoder> {
NonNull::from(&ENCODER)
}
} For a lock-free logger you would have many encoders, stored in static variables. Then on the framework side you would have something like this: SomeLogger::acquire();
let encoder = SomeLogger::encoder();
encoder.as_mut().start_frame(SomeLogger::write);
// maybe called several times
encoder.as_mut().write(bytes, SomeLogger::write);
encoder.as_mut().end_frame(SomeLogger::write);
SomeLogger::release(); |
I think getting the pointer to Generated code could get the pointer every time instead, but this will cause bloat too: SomeLogger::acquire();
SomeLogger::encoder().as_mut().start_frame(SomeLogger::write);
// maybe called several times
SomeLogger::encoder().as_mut().write(bytes, SomeLogger::write);
SomeLogger::encoder().as_mut().end_frame(SomeLogger::write);
SomeLogger::release(); To combat the bloat, wrapper funcs could be made. This'll make it slower though, as SomeLogger funcs are noinline (#257, the compiler was inlining them everywhere causing giant bloat.) fn do_start_frame() { SomeLogger::encoder().as_mut().start_frame(SomeLogger::write); }
fn do_end_frame() { SomeLogger::encoder().as_mut().end_frame(SomeLogger::write); }
fn do_write(data: &[u8]) { SomeLogger::encoder().as_mut().write(data, SomeLogger::write); }
SomeLogger::acquire();
do_start_frame();
// maybe called several times
do_write(bytes);
do_end_frame()
SomeLogger::release(); I'm pretty sure the current trait generates the smallest+fastest code... Also, the Logger trait already has a pretty complicated safety contract. It's already quite hard to implement, adding the |
In the current state, a user who implements our
trait Logger
needs to care about "How to correctly use the encoder?".This is sub-optimal, since the user should only need to care about their transport-specific question and not also how the encoding specific questions, mainly "When to start and end a frame?".
While it is relatively trivial to infer this from the existing implementations and is "just" a bit of boilerplate code, I'd consider it better to not have the need for this boilerplate at all.
To simplify this, the proposal is to add "wrapper methods" with default implementation for
.acquire
,.release
and.write
, as well as a method.ENCODER
to give access to the encoder:Note that a user could still implement
.outer_*
themselves, if they'd like to handle framing differently.The adapted implementation would look like:
Pro / Con
(cc @Dirbaio: What do you think, since the current design is from you)
The text was updated successfully, but these errors were encountered: