WEBVTT

NOTE
This file was generated by Descript <www.descript.com>

00:00:13.384 --> 00:00:17.654
<v Amanda Majorowicz>This is Self-Directed Research,
where our hosts, James and Amos, give each

00:00:17.654 --> 00:00:21.084
other 20 minute presentations that usually
last longer than that about whatever

00:00:21.084 --> 00:00:22.584
they've been obsessing over lately.

00:00:22.804 --> 00:00:27.434
For more episodes, show notes, and
transcripts, visit sdr-podcast.com.

00:00:27.894 --> 00:00:29.894
New episodes drop every Wednesday.

00:00:30.514 --> 00:00:32.464
Today's episode is
sponsored by OneVariable.

00:00:32.754 --> 00:00:34.244
More information at
the end of the episode.

00:00:34.722 --> 00:00:39.002
This week, James presents "BBQueue:
going just far enough with generics."

00:00:46.024 --> 00:00:49.014
<v James Munns>My actual point for this week
is to talk about the complicated

00:00:49.014 --> 00:00:53.154
things that I'm doing in BBQueue and
to explain just enough of a background

00:00:53.654 --> 00:00:59.077
of BBQueue to make it understandable
why these options are useful.

00:00:59.287 --> 00:00:59.617
<v Amos Wenger>Sounds good.

00:00:59.917 --> 00:01:00.467
I'm listening.

00:01:00.999 --> 00:01:01.289
<v James Munns>Cool.

00:01:01.539 --> 00:01:04.959
So this is just going far enough
with generics in BBQueue, which

00:01:04.959 --> 00:01:08.959
is probably too far, but I haven't
thought of a better way, so I'm

00:01:08.959 --> 00:01:10.599
gonna call it 'just far enough.'

00:01:11.069 --> 00:01:16.324
So, I have this library, BBQueue,
which is a fancy single producer,

00:01:16.334 --> 00:01:19.234
single consumer-ish ring buffer.

00:01:19.654 --> 00:01:24.114
So, you could use it as more than a
single producer, single consumer, but

00:01:24.214 --> 00:01:27.694
it's one of those things where when
you put data in, the first one to

00:01:27.694 --> 00:01:29.044
take it out is the one that gets it.

00:01:29.044 --> 00:01:33.444
So, sort of like Rust's channels,
which are mostly MPSC, or multiple

00:01:33.444 --> 00:01:35.004
producer, single consumer.

00:01:35.034 --> 00:01:38.414
That's just so you don't have the
case where one consumer is stealing

00:01:38.414 --> 00:01:41.284
all the messages from the other
consumers, as opposed to like,

00:01:41.464 --> 00:01:43.484
a broadcast channel from Tokio.

00:01:45.184 --> 00:01:47.344
But, BBQueue is a weird ring buffer.

00:01:47.374 --> 00:01:51.944
It's actually more like something called
a Bip-Buffer, or a bipartite buffer.

00:01:52.474 --> 00:01:56.544
And the weird thing about it is that
pushing and popping data is two stage.

00:01:56.814 --> 00:01:59.044
So instead of just saying,
"Here's an item, push.

00:01:59.054 --> 00:02:00.124
Here's an item, push.

00:02:00.684 --> 00:02:04.154
Pop an item, pop an item," like a normal
ring buffer that you might expect.

00:02:04.434 --> 00:02:06.464
It's actually two stages here.

00:02:07.574 --> 00:02:09.274
The first one is to get a grant.

00:02:09.554 --> 00:02:12.504
So this is either on the pushing
side or the producing side.

00:02:13.024 --> 00:02:17.224
You say, "I want a chunk to be
able to put eight bytes in."

00:02:17.964 --> 00:02:20.254
So instead of putting eight
things in one at a time, you

00:02:20.254 --> 00:02:22.134
go, "Give me room for eight."

00:02:22.504 --> 00:02:25.054
And it goes, "Okay, here's
your room for eight."

00:02:25.054 --> 00:02:26.504
Or it says, "I have no room for eight."

00:02:26.524 --> 00:02:30.214
Or on the consuming side, "Give me
everything that you have available."

00:02:30.574 --> 00:02:32.494
Instead of just pop, pop, pop, pop.

00:02:33.884 --> 00:02:36.164
The second stage of this
is actually committing that

00:02:36.164 --> 00:02:37.354
grant or releasing the grant.

00:02:37.354 --> 00:02:39.034
So you might say, "Give
me room for eight."

00:02:39.884 --> 00:02:44.324
I actually put six items in and
then I say, "Okay, I only use six.

00:02:44.324 --> 00:02:46.504
Here are the six that
I'm actually giving you."

00:02:46.784 --> 00:02:51.114
Or in a release, I might say, "There are
700 bytes available," and you go, "Cool.

00:02:51.124 --> 00:02:52.294
I took 140."

00:02:52.654 --> 00:02:53.614
And it goes, "Okay."

00:02:53.934 --> 00:02:57.824
And so this two stage thing is sort
of like peeking into the buffer,

00:02:58.084 --> 00:03:01.494
either peeking into a write space
or peeking into a read space.

00:03:01.764 --> 00:03:03.944
But this is sort of the two stage process.

00:03:03.944 --> 00:03:08.124
You get a view of the internals
of the ring buffer that you

00:03:08.124 --> 00:03:09.444
can either write or read from.

00:03:09.794 --> 00:03:11.914
And then you say, "I'm done with that.

00:03:11.924 --> 00:03:15.024
And I did read, or I did
write this much data."

00:03:16.364 --> 00:03:17.944
So why is it weird like this?

00:03:18.254 --> 00:03:19.914
It's weird like this for two reasons.

00:03:20.154 --> 00:03:25.974
One, if you are putting a ton of data,
like 700 bytes into a queue, if you

00:03:25.974 --> 00:03:31.634
had to call push or pop 700 times,
you're spending more time in the, like,

00:03:31.664 --> 00:03:35.944
overhead of coordination than you are
actually pushing and popping data.

00:03:36.164 --> 00:03:39.934
So if you know that you're going to
be doing big chunks at a time, like

00:03:40.554 --> 00:03:47.284
a whole HTTP frame or a big line of
console data or something like that.

00:03:47.464 --> 00:03:50.494
You might want to do that one
chunk of at a time instead of

00:03:50.494 --> 00:03:52.544
paying that overhead on every step.

00:03:54.034 --> 00:03:57.524
The other thing is that these
grants are actually a stable view

00:03:58.034 --> 00:04:00.274
of the storage of the ring buffer.

00:04:00.734 --> 00:04:05.179
You are actually getting a mutable
slice to, let's say the ring

00:04:05.179 --> 00:04:09.249
buffer is 4 kilobytes large, you'd
actually be getting like a 512

00:04:09.269 --> 00:04:12.029
bytes in place of the ring buffer.

00:04:12.429 --> 00:04:16.259
Which means that if you were trying
to zero copy deserialize or something

00:04:16.259 --> 00:04:19.659
from that, you're not copying the
data out into a vector or something

00:04:19.669 --> 00:04:22.079
and then deserializing from that.

00:04:22.079 --> 00:04:25.349
The data was put into the ring buffer
and then you are viewing the data

00:04:25.349 --> 00:04:27.349
where it was put in the ring buffer.

00:04:28.264 --> 00:04:32.634
On embedded systems too, this is
awesome for DMA or direct memory access.

00:04:32.924 --> 00:04:38.804
When you say, "Hey, hardware peripheral,
put 512 bytes of serial data right here."

00:04:39.504 --> 00:04:42.854
To do that, you need to give it a
pointer and a length basically, and then

00:04:42.854 --> 00:04:48.124
promise that that memory is writable
by this hardware peripheral until it

00:04:48.124 --> 00:04:50.683
says, "I'm done, you can have it now."

00:04:51.033 --> 00:04:54.333
Which means that actually when you're
receiving serial data or ethernet

00:04:54.333 --> 00:04:58.588
data, you can have the hardware put it
exactly into the ring buffer without

00:04:58.598 --> 00:05:01.828
having to receive it into a buffer
and then copy it into the ring buffer

00:05:02.118 --> 00:05:03.978
to make it available somewhere else.

00:05:04.868 --> 00:05:07.998
So I've said before that I solve
a lot of problems with allocators.

00:05:08.468 --> 00:05:10.958
This is a way of turning a
ring buffer into sort of a

00:05:10.958 --> 00:05:13.558
self contained FIFO allocator.

00:05:13.568 --> 00:05:15.718
<v Amos Wenger>That's what I was gonna say,
it's not a ring buffer anymore,

00:05:15.718 --> 00:05:17.128
it's a full blown allocator.

00:05:17.485 --> 00:05:21.585
<v James Munns>Yeah, well, it's not full
because it is still first in first out.

00:05:21.825 --> 00:05:25.265
A really cool thing about allocators
is you could hold on to a message,

00:05:25.505 --> 00:05:28.445
process some other stuff, and
then later release this message.

00:05:28.445 --> 00:05:29.995
BBQueue says: no.

00:05:30.035 --> 00:05:32.805
It has to go in this
order and out this order.

00:05:33.015 --> 00:05:37.195
You can choose when you say I've
processed this data, but it has to be in

00:05:37.195 --> 00:05:40.285
and out in that order all of the time.

00:05:40.335 --> 00:05:40.965
Because it's a ring.

00:05:41.195 --> 00:05:43.425
There's no way to free data
in the middle of a ring.

00:05:43.740 --> 00:05:45.260
<v Amos Wenger>Right, there's
never any fragmentation.

00:05:45.645 --> 00:05:46.205
<v James Munns>Exactly.

00:05:46.275 --> 00:05:46.485
Yeah.

00:05:46.485 --> 00:05:49.565
And that's the benefit, is you never
have fragmentation, because you can

00:05:49.845 --> 00:05:54.215
get a big chunk of like, "Give me 512
bytes," but then if you say, "I only

00:05:54.215 --> 00:05:59.275
used 128," that essentially frees the
tail of the allocation, and that's

00:05:59.275 --> 00:06:00.575
what you're going to use next time.

00:06:00.585 --> 00:06:04.005
Which means there exactly
is no fragmentation, but the

00:06:04.005 --> 00:06:07.585
downside balance is you have to
process it first in, first out.

00:06:08.445 --> 00:06:13.025
So, the problem is, this is a
really useful data structure, and

00:06:13.025 --> 00:06:15.855
it's useful in a lot of different
places to a lot of different people

00:06:15.855 --> 00:06:17.065
in a lot of different setups.

00:06:17.547 --> 00:06:21.467
The problem in the past is, I wanted to
support all of those, and I didn't know

00:06:21.467 --> 00:06:25.397
how to do that without making however many
copies of the full library that sort of

00:06:25.397 --> 00:06:30.607
work the same, and have the same vibes,
but are tuned for different use cases.

00:06:31.057 --> 00:06:33.047
Until, very recently.

00:06:33.824 --> 00:06:36.864
So I have a bunch of different
ways that this can be used.

00:06:37.094 --> 00:06:38.974
And I'm going to go through each
of them and why they're complicated

00:06:38.974 --> 00:06:40.794
and how I fixed it with generics.

00:06:41.244 --> 00:06:44.924
So one is you might either want
inline storage or heap storage.

00:06:45.434 --> 00:06:48.144
If you're on an embedded systems and
you're statically allocating that

00:06:48.144 --> 00:06:51.654
4k buffer or whatever, it's very
easy to just say, "I want a static

00:06:51.654 --> 00:06:54.214
right here with 4k of storage there."

00:06:54.604 --> 00:06:58.294
And the type system can know: oh,
this is generic over `const N:

00:06:58.294 --> 00:07:03.164
usize` for the buffer size, and I
can make a fancy buffer that's 4k.

00:07:03.510 --> 00:07:08.090
<v Amos Wenger>Similar tricks are used for smart
strings, like small string crates in Rust.

00:07:08.649 --> 00:07:08.949
<v James Munns>Yeah.

00:07:09.025 --> 00:07:12.625
But at runtime, if you go, "Well, I'm
going to read a config file at startup,

00:07:12.965 --> 00:07:17.575
and I'm going to allocate a buffer
based on that size," you can't tell the

00:07:17.575 --> 00:07:21.315
compiler, "Hey, I'm not going to know
the actual type of this until runtime."

00:07:21.835 --> 00:07:25.325
So, you might want to put it in a
heap, where you say, "Give me a boxed

00:07:25.335 --> 00:07:31.235
slice," where I can say: box slice new
with, oh, this size that I read from

00:07:31.235 --> 00:07:33.855
the config file, which is 768 bytes.

00:07:34.155 --> 00:07:34.635
Cool.

00:07:34.705 --> 00:07:35.595
I can do that.

00:07:36.795 --> 00:07:39.575
Now it seems like you might always
want that, but the downside to that is

00:07:40.005 --> 00:07:42.075
you're forcing one layer of indirection.

00:07:42.375 --> 00:07:45.045
I'm going to the queue,
where the data is in line.

00:07:45.305 --> 00:07:49.325
The bytes are at the end of that queue
structure; versus in a box slice, I

00:07:49.335 --> 00:07:53.315
have to take a pointer to my queue, then
bounce one more to my storage, which

00:07:53.740 --> 00:07:58.480
isn't super expensive, but you'd prefer
not to pay for it if you didn't want to.

00:07:59.020 --> 00:08:01.200
And on embedded systems where
you don't have a heap, it might

00:08:01.200 --> 00:08:03.120
not even be an option to do that.

00:08:03.956 --> 00:08:05.996
<v Amos Wenger>And so that's your
first layer of generics...

00:08:06.341 --> 00:08:06.671
<v James Munns>Yep.

00:08:06.701 --> 00:08:09.501
So this is the first configurable
and we're going to play a fun

00:08:09.501 --> 00:08:12.191
game of binary combinatorics, but-

00:08:12.376 --> 00:08:13.876
<v Amos Wenger>How many combinations, yes?

00:08:14.221 --> 00:08:14.671
<v James Munns>Yeah.

00:08:14.711 --> 00:08:18.801
So since this is an allocator, I have
this trait 'storage,' where instead

00:08:18.801 --> 00:08:22.831
of giving you a slice, it gives
you back a pointer or a NonNull u8.

00:08:23.331 --> 00:08:25.211
And a len, or a usize.

00:08:25.561 --> 00:08:27.721
And this is because when we
get that storage buffer, we

00:08:27.721 --> 00:08:29.001
have to be really careful.

00:08:29.321 --> 00:08:32.041
Because there might be one thread
that can see one chunk of the

00:08:32.041 --> 00:08:35.451
data, and another thread that can
see a different chunk of the data.

00:08:35.861 --> 00:08:39.571
So we can't hand out a slice here,
because then we'd have aliasing slices.

00:08:39.721 --> 00:08:43.391
So we have to act like an allocator
and be really careful and go,

00:08:43.591 --> 00:08:44.791
"Here's the pointer in length.

00:08:44.801 --> 00:08:49.251
Do not make a slice of that entire
length unless you know that it's safe."

00:08:49.596 --> 00:08:53.526
<v Amos Wenger>Because if you did make a slice,
like if you did have aliasing slices,

00:08:53.526 --> 00:08:55.256
that would be instant undefined behavior?

00:08:55.876 --> 00:09:00.580
<v James Munns>Yep, because if the producer
has a view of 512 bytes of the 4k,

00:09:01.200 --> 00:09:05.760
and you made another slice of the
whole 4k: boom, undefined behavior.

00:09:05.760 --> 00:09:10.178
Because you have aliased slices- or
specifically aliased mutable slices

00:09:10.282 --> 00:09:10.918
to the same underlying buffer.

00:09:11.796 --> 00:09:16.791
So this is my storage trait, and
then I have sort of like an extension

00:09:16.921 --> 00:09:21.266
super trait, which is `ConstStorage`,
where if you can make this at const

00:09:21.276 --> 00:09:24.976
time, like if you were defining a
static, for example, because if it's

00:09:24.976 --> 00:09:28.746
just an inline buffer, you can define
that as a static, but if it's a heap

00:09:28.746 --> 00:09:30.606
allocation, you cannot make it static.

00:09:30.986 --> 00:09:36.566
So this is how I say- doesn't matter how
you store it: on the heap, as a static, in

00:09:36.566 --> 00:09:38.906
line, as a reference, even if you wanted.

00:09:39.356 --> 00:09:41.986
Here's the trait you have to fill,
you have to promise me that this

00:09:41.986 --> 00:09:45.266
buffer never changes, and that this
is the pointer to it, and this is

00:09:45.266 --> 00:09:48.316
the length of it, because I'm going
to use that as my backing storage.

00:09:48.621 --> 00:09:48.941
<v Amos Wenger>Right.

00:09:48.951 --> 00:09:53.131
So yeah, when you said inline, like
heap versus inline, I was thinking

00:09:53.131 --> 00:09:56.701
of inline as the stack, but I didn't
realize it could also be a static, as you

00:09:56.701 --> 00:10:01.208
say, which would then be, specifically
a reserved part of the executable

00:10:01.248 --> 00:10:03.188
image that's then mapped into memory.

00:10:03.543 --> 00:10:05.592
<v James Munns>Yeah, it's a static global
variable essentially, because if

00:10:05.592 --> 00:10:08.592
you- you could have a global variable
that is your queue, and let's say

00:10:08.592 --> 00:10:12.422
there's the header of the queue that's
40 bytes or something like that.

00:10:12.742 --> 00:10:16.602
If you made the storage inline, you'd
be literally putting the 4 kilobytes

00:10:16.602 --> 00:10:19.132
of buffer right next to that header.

00:10:19.182 --> 00:10:22.742
Like it is inline, they are both
contained within the same struct.

00:10:23.202 --> 00:10:26.382
Versus in the non inline one,
you have the 40 bytes of header.

00:10:26.712 --> 00:10:29.662
And then the storage that's
sitting next to it is actually a

00:10:29.662 --> 00:10:32.502
pointer to somewhere else instead.

00:10:32.682 --> 00:10:35.832
So it's actually generic of whether
the data is stored inside of the same

00:10:35.832 --> 00:10:41.462
struct, or whether the struct holds a
box or something of data somewhere else.

00:10:42.102 --> 00:10:45.322
<v Amos Wenger>The word intrusive data structures
come to mind, but I'm not even

00:10:45.322 --> 00:10:47.042
sure it applies here, to be honest.

00:10:47.752 --> 00:10:50.722
<v James Munns>No, intrusive data
structures are more link listy.

00:10:51.002 --> 00:10:55.211
This is almost, yeah, like I said,
it's generic over whether the data is

00:10:55.211 --> 00:11:00.961
stored in the queue, or whether the
queue holds a pointer to somewhere else.

00:11:01.191 --> 00:11:01.531
<v Amos Wenger>Mmm..

00:11:01.808 --> 00:11:03.688
<v James Munns>So that's the
first degree of freedom.

00:11:04.558 --> 00:11:09.124
The second option is async or not,
because not everyone wants to use async.

00:11:09.164 --> 00:11:12.174
In embedded you may not use it, or
if you're just using it for something

00:11:12.174 --> 00:11:15.654
blocking, or you have threads or something
like that, and you're coordinating between

00:11:15.654 --> 00:11:17.613
them, you may or may not want async.

00:11:17.873 --> 00:11:22.353
And async doesn't necessarily add a lot of
overhead, and I'll show you how it might.

00:11:22.813 --> 00:11:23.283
But...

00:11:24.378 --> 00:11:25.058
it's a choice.

00:11:25.318 --> 00:11:28.558
If you don't use async, you don't
necessarily want to pay any overhead

00:11:28.558 --> 00:11:32.378
for that, but if you do, you
really want that and it's useful.

00:11:32.652 --> 00:11:37.957
<v Amos Wenger>You mentioned an async
memory allocator previously to me.

00:11:39.050 --> 00:11:39.800
Is that related?

00:11:39.850 --> 00:11:44.585
Would you want the BBQueue to be async
because your memory allocator is async?

00:11:44.590 --> 00:11:48.620
Or what other asynchronous
things would BBQueue even do?

00:11:49.560 --> 00:11:49.730
<v James Munns>Yeah.

00:11:49.730 --> 00:11:55.065
BBQueue out of the box: when it's not
async, you can say, "Try give me a grant."

00:11:56.085 --> 00:11:58.105
Or, "Give me a grant and panic."

00:11:58.115 --> 00:12:01.935
Like, it's not like a channel in
Rust where you can make the operating

00:12:01.935 --> 00:12:03.575
system block until you're ready.

00:12:03.620 --> 00:12:04.220
<v Amos Wenger>Right.

00:12:04.875 --> 00:12:07.015
<v James Munns>You're saying
like, "Give me a grant."

00:12:07.465 --> 00:12:11.595
Basically the only API I provide
is: try give me a grant, and

00:12:11.605 --> 00:12:14.175
it'll either say, "Nope, no data
available," or it'll say "Yes, there

00:12:14.175 --> 00:12:15.645
was data available, here you go."

00:12:16.190 --> 00:12:21.300
<v Amos Wenger>It keeps surprising me that you
use async for asynchronous things instead

00:12:21.300 --> 00:12:25.330
of, like everyone else, just using it
for like timers and input output and-

00:12:25.330 --> 00:12:30.275
like, no you can just like, instead of
blocking or failing, you can just hand

00:12:30.275 --> 00:12:32.335
out a future that will eventually resolve.

00:12:32.585 --> 00:12:35.275
It's natural, but I never,
I'm always jump scared by it.

00:12:35.275 --> 00:12:37.755
I'm like, "Oh yeah, of course this is
asynchronous, if you think about it."

00:12:38.875 --> 00:12:42.285
<v James Munns>But it's like Tokio Channel, for
example, you could do 'try send' or 'try

00:12:42.285 --> 00:12:46.085
receive' and if for a bounded queue,
there either is room or there isn't.

00:12:46.345 --> 00:12:51.185
Or if you have async, you can call
`.send( ).await` or `.recv( ).await`,

00:12:51.395 --> 00:12:54.755
that way it will wait until there
either is data in the queue or the

00:12:54.755 --> 00:12:56.585
queue is empty enough to give you that.

00:12:57.055 --> 00:13:00.425
So sort of like the async allocator,
you would want to wait for the same

00:13:00.425 --> 00:13:02.105
reason: is there capacity or not?

00:13:02.375 --> 00:13:05.595
But this is, uh, yeah, so I guess
it is more similar than I thought.

00:13:05.795 --> 00:13:06.795
<v Amos Wenger>Yeah, but...

00:13:07.565 --> 00:13:12.265
I do want to point out that all the
'try send', 'try receive' methods are...

00:13:13.090 --> 00:13:17.970
pretty much just sugar over the regular
async versions and then `.now_or_never`

00:13:18.880 --> 00:13:22.700
, which is my favorite future combinator,
which is like: if we pull the future once

00:13:23.260 --> 00:13:28.360
and then if it returns ready, then return,
otherwise return option non or something.

00:13:28.667 --> 00:13:30.047
<v James Munns>It's funny, because
mine's the opposite.

00:13:30.257 --> 00:13:36.447
My async ones are sugar over the 'try'
ones, in that, if you- Let me just

00:13:36.447 --> 00:13:37.487
show you what the trait looks like.

00:13:37.487 --> 00:13:40.637
So I have a normal trait
called Notifier, which is just

00:13:40.677 --> 00:13:42.987
wake_one_consumer, or wake_one_producer.

00:13:43.257 --> 00:13:47.007
So if you put data in the queue,
you might want to wake_one_consumer.

00:13:47.023 --> 00:13:51.082
So if a producer puts data
in, it wakes_one_consumer.

00:13:51.382 --> 00:13:55.022
And if the consumer takes data out, it
might want to wake_one_producer, because

00:13:55.022 --> 00:13:57.302
now there might be room available for it.

00:13:58.249 --> 00:14:01.969
But, I have, sort of, again, one of
these extension traits that says:

00:14:01.989 --> 00:14:07.009
well, I have this async notifier trait,
which requires that the type also

00:14:07.019 --> 00:14:10.549
be a notifier, but there's two main
things that we're waiting on here.

00:14:10.579 --> 00:14:16.714
Either, I need a future that tells me when
the queue is not empty, or I need a future

00:14:16.714 --> 00:14:18.644
that tells me when the queue is not full.

00:14:18.974 --> 00:14:22.944
Cause if I want to put data in, I need
to wait until the queue is not full.

00:14:23.454 --> 00:14:26.974
And if I want to take data out, I need
to wait until the queue is not empty.

00:14:27.425 --> 00:14:30.695
So there's four futures I have
here, but this is because when

00:14:30.695 --> 00:14:33.745
it comes to like atomic wakers,
there's actually a two step process.

00:14:33.745 --> 00:14:39.695
You need to put your waker into the
space to be woken by the reactor.

00:14:40.770 --> 00:14:42.200
Then I check...

00:14:43.130 --> 00:14:46.814
so you want to say, like: okay,
notify me if there is a time

00:14:46.814 --> 00:14:48.164
where the queue is not empty.

00:14:49.004 --> 00:14:50.604
Then you check if it's empty.

00:14:50.994 --> 00:14:54.544
If it is, you yield and wait
to be woken, because you've

00:14:54.544 --> 00:14:55.724
already registered your waker.

00:14:56.214 --> 00:14:59.580
If it has what you want, you
just immediately return on that.

00:14:59.580 --> 00:15:04.290
So it's essentially a loop where it's
like: install waker, check, await.

00:15:04.540 --> 00:15:06.540
Install waker, check, await.

00:15:06.540 --> 00:15:10.290
So mine's actually the opposite of what
you described, where the native is a

00:15:10.310 --> 00:15:15.160
try, but by having this optional async
notifier, we can have a method that's

00:15:15.190 --> 00:15:19.950
only available when you have an async
notifier implementer that says, "Ah!

00:15:19.950 --> 00:15:22.120
But now I can also wait
for that to happen."

00:15:22.770 --> 00:15:24.530
<v Amos Wenger>It is also worth
noting that I was wrong.

00:15:25.670 --> 00:15:29.930
Just look at the try receive methods
for a UDP socket, for example.

00:15:29.930 --> 00:15:33.300
And they're just calling to the reactor.

00:15:33.300 --> 00:15:34.920
And then I actually know the difference.

00:15:35.260 --> 00:15:38.376
They're just calling it mio methods,
because  I knew it wasn't possible

00:15:38.376 --> 00:15:42.506
because to even pull the future
once, you need some sort of runtime

00:15:42.536 --> 00:15:44.576
that you need to pass an awaker.

00:15:44.746 --> 00:15:46.166
You can have a no-op waker, but...

00:15:46.587 --> 00:15:50.177
not a good idea if you're like-
otherwise using it in an async context.

00:15:50.247 --> 00:15:53.255
Anyway, my whole remark was wrong,
but it did prompt you to explain

00:15:53.255 --> 00:15:54.765
more about how your system works.

00:15:54.865 --> 00:15:55.915
So, still a win.

00:15:56.375 --> 00:15:56.675
<v James Munns>Yeah.

00:15:56.975 --> 00:15:57.335
Yeah...

00:15:58.115 --> 00:16:00.495
and the reason I have this two step
process is because if you check if

00:16:00.495 --> 00:16:04.185
it's empty and then install the waker,
because we have two threads that are

00:16:04.185 --> 00:16:08.335
coordinating, that's a race condition in
that, like, you could check, then data

00:16:08.335 --> 00:16:11.725
becomes available, then you install your
waker, which means you're too late for

00:16:11.725 --> 00:16:14.785
the notification, which means you have
to go around, which is why I have this

00:16:14.815 --> 00:16:22.275
two step process, which is: install the
waker, then check, then go around if not.

00:16:23.135 --> 00:16:26.015
<v Amos Wenger>That's my favorite class
of bug, which is called TOC/TOU.

00:16:26.635 --> 00:16:27.885
Time of check to time of use.

00:16:28.770 --> 00:16:29.420
<v James Munns>Exactly.

00:16:29.930 --> 00:16:34.525
And the reason that I have these
futures for those two steps, for each

00:16:34.535 --> 00:16:37.965
wait_not_empty and wait_not_full, the
reason that I make these associated types

00:16:37.995 --> 00:16:42.765
is because this allows you, on embedded
systems where we don't necessarily

00:16:42.765 --> 00:16:46.545
care if our futures are send, because
we don't have work stealing executors

00:16:46.545 --> 00:16:51.510
like Tokio, I can put a future here
that is not send, and use it just fine.

00:16:51.790 --> 00:16:55.780
And then if I'm using methods that use
something Tokio based as the notifier,

00:16:56.280 --> 00:17:00.560
then I can force that type to send,
which means it just sort of shakes out.

00:17:00.810 --> 00:17:04.910
So instead of using the regular async
function in a trait, I have this sort

00:17:04.910 --> 00:17:10.662
of complicated four associated types
of not empty register future, not empty

00:17:10.662 --> 00:17:14.392
waiter future, and not full register
future, and not full waiter future.

00:17:14.737 --> 00:17:17.457
I can make those associated types,
which means you can put different

00:17:17.457 --> 00:17:21.227
bounds on them, whether you're using
Embassy's synchronization primitives

00:17:21.257 --> 00:17:23.517
or Tokio's synchronization primitives.

00:17:24.122 --> 00:17:24.362
<v Amos Wenger>Right.

00:17:25.089 --> 00:17:27.439
<v James Munns>So that's the second variable.

00:17:27.449 --> 00:17:30.399
So, so far we have where the
storage of the buffer is and

00:17:30.399 --> 00:17:31.669
whether we're async or not.

00:17:32.239 --> 00:17:34.719
The third one is, do
we have atomics or not?

00:17:35.139 --> 00:17:38.517
Because I work on a lot of embedded
systems that don't necessarily

00:17:38.517 --> 00:17:42.157
have what are called atomic
compare and swap operations.

00:17:42.857 --> 00:17:47.973
So every CPU usually can load and store
to some memory address within the width

00:17:47.973 --> 00:17:51.260
of its architecture in a single cycle.

00:17:51.410 --> 00:17:56.200
So on a 32 bit processor, you
can store 32 bits or load 32

00:17:56.200 --> 00:17:59.070
bits as one operation atomically.

00:17:59.720 --> 00:18:05.350
But when we do things like fetch_add, or
swap, or compare and swap, those usually

00:18:05.350 --> 00:18:07.250
require some level of hardware support.

00:18:07.660 --> 00:18:11.986
On microcontrollers, these are usually
what are called LL/SC instructions,

00:18:11.986 --> 00:18:15.621
which I can't remember what they are,
but essentially, I can load and store

00:18:15.761 --> 00:18:17.511
atomically and expect that to work.

00:18:17.801 --> 00:18:22.321
And if I have those primitives, then
LLVM can write me a function for like

00:18:22.321 --> 00:18:24.541
fetch_add or swap or something like that.

00:18:24.761 --> 00:18:26.641
Yeah, load-link/store-conditional.

00:18:26.991 --> 00:18:31.681
On Cortex-M there's specifically
the LDREX and STREX instructions.

00:18:31.711 --> 00:18:35.717
Or on x86, you might have different
ones which can do them indirectly.

00:18:35.767 --> 00:18:38.217
There's a couple different ways
processors do them, but essentially

00:18:38.217 --> 00:18:42.919
you can do things like swap a variable
or fetch_add an integer without

00:18:43.153 --> 00:18:44.613
worrying about being preempted.

00:18:44.623 --> 00:18:48.043
You can do it as if you
did it in one operation.

00:18:48.053 --> 00:18:52.243
There might be some spinning to do
that, but as if it's transactional.

00:18:52.998 --> 00:18:53.278
<v Amos Wenger>Right.

00:18:53.368 --> 00:18:53.578
Yep.

00:18:54.018 --> 00:18:56.839
<v James Munns>BBQueue is an algorithm
that I wrote with some other folks

00:18:56.859 --> 00:19:01.031
of doing all of this, it's what's
called a lock free data structure.

00:19:01.221 --> 00:19:04.631
There's no mutex provided by the
system or operating system to do this.

00:19:04.841 --> 00:19:10.111
All this coordination on who has room
to write or read is done with atomics.

00:19:10.361 --> 00:19:13.291
Which means that you don't necessarily
need a blocking mutex or anything

00:19:13.291 --> 00:19:14.711
like that, that might play with it.

00:19:14.711 --> 00:19:18.971
You can do it totally uncoordinated or
only coordinating through these atomics.

00:19:19.621 --> 00:19:21.931
But there's a lot of cheap
microcontrollers that you might

00:19:21.931 --> 00:19:25.631
want to use, like the Raspberry
Pi 2040, which don't have support

00:19:25.631 --> 00:19:27.011
for those atomic instructions.

00:19:27.491 --> 00:19:30.281
Now in the embedded world, we
have some way of like polyfilling

00:19:30.301 --> 00:19:31.361
these or backfilling them.

00:19:31.631 --> 00:19:33.671
Essentially, you take what's
called a critical section,

00:19:33.931 --> 00:19:36.621
where you prevent yourself from
being preempted and just do it.

00:19:37.551 --> 00:19:40.451
But instead of emulating them, it
doesn't necessarily make sense to

00:19:40.451 --> 00:19:43.051
emulate a bunch of atomic instructions.

00:19:43.811 --> 00:19:47.501
You go: well, if I'm going to need
a mutex anyway, just take a mutex

00:19:47.511 --> 00:19:49.151
whenever I'm doing this coordination.

00:19:49.421 --> 00:19:53.441
And that way, it's only a couple
pieces of like: add some numbers,

00:19:53.441 --> 00:19:55.011
check if this is bigger than this.

00:19:55.141 --> 00:19:57.531
If it is, write this
data here, then do this.

00:19:57.721 --> 00:19:59.661
Like it's not a very
complicated algorithm.

00:19:59.901 --> 00:20:03.501
So you can just do it in a couple of
instructions inside of a critical section.

00:20:04.071 --> 00:20:07.801
This way, you don't have to get these
compile time errors where you go, "Oh, you

00:20:07.801 --> 00:20:12.726
tried to use the swap instruction, which
doesn't exist on this microcontroller."

00:20:12.911 --> 00:20:13.151
<v Amos Wenger>Yeah.

00:20:13.633 --> 00:20:17.773
<v James Munns>So essentially all of these like
primitives of what my queue needs to do,

00:20:17.773 --> 00:20:22.983
like negotiating between the two sides,
like: do I have room to get a write grant?

00:20:23.173 --> 00:20:25.353
Or: do I have room to get a read grant?

00:20:25.533 --> 00:20:29.013
Or: I have done some stuff with
my write grant, I am making

00:20:29.013 --> 00:20:30.593
it available to the reader.

00:20:31.083 --> 00:20:34.703
Or: I have done some stuff with my
read grant and I'm recycling that space

00:20:34.713 --> 00:20:36.653
so it's now available to the writer.

00:20:37.183 --> 00:20:40.883
I essentially have one function for
each of these coordination items.

00:20:40.918 --> 00:20:41.638
<v Amos Wenger>Right.

00:20:41.833 --> 00:20:45.303
<v James Munns>Now this trait is unsafe
because it's arbitrating access

00:20:45.303 --> 00:20:47.133
to shared buffer of memory.

00:20:47.493 --> 00:20:51.243
This is essentially, if you implement
this trait wrong, you could have

00:20:51.273 --> 00:20:55.503
undefined behavior because you could
accidentally give overlapping access.

00:20:55.923 --> 00:21:01.193
So this trait is unsafe because you
have to do this right or you've busted

00:21:01.193 --> 00:21:02.963
your allocator soundness basically.

00:21:03.128 --> 00:21:03.298
<v Amos Wenger>Yep.

00:21:03.348 --> 00:21:03.498
Yep.

00:21:03.498 --> 00:21:03.678
Yep.

00:21:04.249 --> 00:21:08.009
<v James Munns>So this is essentially
all of the primitive coordination

00:21:08.069 --> 00:21:09.829
operations that my queue needs.

00:21:10.079 --> 00:21:14.079
And I can either do them with lock
free atomics or I can do them with

00:21:14.079 --> 00:21:18.909
a more mutex-y critical section,
whatever works better for my platform.

00:21:19.114 --> 00:21:19.364
<v Amos Wenger>Mm hmm.

00:21:19.784 --> 00:21:23.124
And that's the third
dimension of customization?

00:21:23.195 --> 00:21:25.255
<v James Munns>This is the third dimension.

00:21:25.305 --> 00:21:25.605
Yep.

00:21:25.765 --> 00:21:26.493
And here's the fourth.

00:21:26.531 --> 00:21:26.921
<v Amos Wenger>Right.

00:21:27.251 --> 00:21:27.961
Here's the fourth.

00:21:27.991 --> 00:21:28.541
Oh boy.

00:21:28.659 --> 00:21:33.509
<v James Munns>So I talked before about whether
the storage- that buffer of four

00:21:33.509 --> 00:21:38.159
kilobytes that we're putting data into-
whether that is heap allocated or not.

00:21:38.769 --> 00:21:41.439
But I didn't mention where
are we putting the metadata.

00:21:42.129 --> 00:21:45.839
Because if I want to, let's say, use
this on the desktop, and I want to

00:21:45.849 --> 00:21:49.909
have the traditional, like, split
producer and consumer interface.

00:21:50.215 --> 00:21:55.014
We need to make sure that metadata
and the buffer that it owns lives

00:21:55.214 --> 00:21:58.474
until both the producer and consumer
have dropped their sides to it.

00:21:58.764 --> 00:22:00.984
That data needs to live
long enough for that.

00:22:01.274 --> 00:22:05.724
Now if I'm on embedded, and I place this
as a global static, I don't have to worry.

00:22:05.764 --> 00:22:08.154
That data's static, it lives
forever, it's just fine.

00:22:09.004 --> 00:22:12.775
But what if I'm on the desktop and I
say, "I want to make this buffer hand

00:22:12.775 --> 00:22:16.835
out handles to two threads," they work
for ten seconds or something like that.

00:22:16.928 --> 00:22:19.658
So if you hand out that producer to
one thread and the consumer to another

00:22:19.658 --> 00:22:22.638
thread, they live for a while and then
you drop them, you want to make sure that

00:22:22.638 --> 00:22:25.068
data gets recycled at the right time.

00:22:25.618 --> 00:22:29.338
But like I said, if you are either
using a heap allocation and just

00:22:29.338 --> 00:22:32.502
doing it locally, and you can borrow
that heap allocation, that's cool.

00:22:32.682 --> 00:22:35.584
Or if you make a static and you
borrow that to make my producer

00:22:35.584 --> 00:22:37.344
and consumer, that's cool too.

00:22:37.874 --> 00:22:42.854
So this is another layer of abstracting
of how the data is actually stored

00:22:43.124 --> 00:22:46.714
for the usage of this without
making copies of the data structure.

00:22:47.950 --> 00:22:52.310
So this trait is the most complicated
because it has to know about the

00:22:52.435 --> 00:22:57.165
BBQueue type itself, which means
it has to be generic over the other

00:22:57.165 --> 00:22:58.445
three things that we've talked about.

00:22:58.825 --> 00:23:03.335
So this is saying that we have a trait
called BbqHandle, which is generic over

00:23:03.335 --> 00:23:05.695
the storage, coordination and notifier.

00:23:06.344 --> 00:23:11.394
And it gives me something that
I can deref to a BBQueue so get

00:23:11.424 --> 00:23:13.544
a reference to this BBQueue.

00:23:14.514 --> 00:23:17.504
And I can get this reference
and that reference is clonable.

00:23:17.874 --> 00:23:20.144
So if it's an arc, I can
call clone on the arc.

00:23:20.164 --> 00:23:22.304
If it's a reference, I can just
clone a reference because a

00:23:22.304 --> 00:23:26.674
reference is a reference and it
means that I can clone that handle.

00:23:27.191 --> 00:23:28.921
So what does this get me?

00:23:29.661 --> 00:23:31.761
I end up with like a
producer and a consumer with

00:23:31.791 --> 00:23:32.891
generics that look like this.

00:23:32.891 --> 00:23:38.441
So I have `pub struct Producer`
generic over `Q, S, C, N` where `S` is

00:23:38.441 --> 00:23:44.006
`Storage`, `C` is `Coord`-ination, `N` is
`Notifier`, and `Q` is my, where is the

00:23:44.006 --> 00:23:47.896
queue stored, or how do I get my BBQueue
handle that is generic over `S, C, N`.

00:23:48.656 --> 00:23:52.736
And this producer and consumer
just holds on to the queue.

00:23:53.126 --> 00:23:55.846
Whether it's an arc to
it, or a reference to it.

00:23:56.076 --> 00:24:00.326
And I end up with this producer
and consumer API that doesn't

00:24:00.326 --> 00:24:03.086
care how it's implemented, it
just knows how to do these things.

00:24:04.363 --> 00:24:07.233
Now, I said that I have four
options here, with basically

00:24:07.233 --> 00:24:08.553
each of them are binary options.

00:24:08.813 --> 00:24:11.633
Now, when you have two to the
fourth options, it means that I

00:24:11.633 --> 00:24:13.533
have sixteen versions of this.

00:24:13.863 --> 00:24:17.033
And because I love puns, usually
whenever I talk about different

00:24:17.043 --> 00:24:21.473
versions in Postcard or whatever,
I like calling them flavors.

00:24:21.503 --> 00:24:26.683
Like, it's just an easier way for people
to think of: I prefer this version of

00:24:26.683 --> 00:24:29.783
something, in the same way that you
have preferences for different flavors.

00:24:30.033 --> 00:24:33.983
And because it's called BBQueue, I needed
a theme for all 16 different flavors,

00:24:34.343 --> 00:24:38.723
which means that I had to find a list of
16 different global varieties of barbecue.

00:24:39.413 --> 00:24:42.703
And I've given them type aliases because
when you go through these combinatorics,

00:24:42.713 --> 00:24:43.873
you have to remember which ones.

00:24:44.223 --> 00:24:47.133
And there's going to be some gimme's, like
if you're on desktop with a heap, you're

00:24:47.143 --> 00:24:48.503
probably going to want a certain one.

00:24:48.513 --> 00:24:50.653
If you're on embedded with
no atomics, you're probably

00:24:50.653 --> 00:24:51.433
going to want a different one.

00:24:51.443 --> 00:24:55.783
Like, for example, if you're on embedded
with no atomics and no heap, you

00:24:55.783 --> 00:24:58.233
probably want `Jerk`, like jerk barbecue.

00:24:58.693 --> 00:25:03.233
If you're on desktop and you have a
heap allocator, but you want your data

00:25:03.233 --> 00:25:06.363
to be stored in line, you probably
want Kansas City style barbecue.

00:25:06.668 --> 00:25:07.468
All these kind of things.

00:25:07.488 --> 00:25:12.868
And I do actually have a division where
all the inline storage versions are North

00:25:12.868 --> 00:25:15.668
and South American styles of barbecue.

00:25:16.098 --> 00:25:20.338
And all the ones that have external
storage are the rest of the world.

00:25:20.338 --> 00:25:24.188
So we've got Europe, Asia, Africa;
other countries that have barbecue

00:25:24.188 --> 00:25:25.338
styles and things like that.

00:25:25.668 --> 00:25:26.158
So...

00:25:27.008 --> 00:25:29.718
I don't know if everyone will
love these combinations, but-

00:25:30.033 --> 00:25:33.233
<v Amos Wenger>Important question: are you
documenting those design choices

00:25:33.283 --> 00:25:37.103
in the Rust docs, or do you just
hope people will pick up on them?

00:25:37.733 --> 00:25:42.373
<v James Munns>I've mentioned it, I do want to
have Like, and this is the same thing

00:25:42.373 --> 00:25:45.813
with a menu when you have 16 choices,
people will be overwhelmed by the choices.

00:25:46.013 --> 00:25:49.513
I plan to have Chef's Choice in
there of: Hey, you're on desktop?

00:25:49.513 --> 00:25:50.463
You probably want this one.

00:25:50.463 --> 00:25:51.263
Hey, you're on embedded?

00:25:51.263 --> 00:25:52.003
You probably want this one.

00:25:52.003 --> 00:25:53.203
You're async and embedded?

00:25:53.383 --> 00:25:54.843
You probably want this one.

00:25:55.093 --> 00:25:57.310
Cause there's probably like three
or four that everyone's going

00:25:57.310 --> 00:26:00.650
to use, and the rest of them are
kind of weird niche combinations.

00:26:00.650 --> 00:26:02.660
But there's probably
some reason to use them.

00:26:02.660 --> 00:26:05.940
But there's probably three or four
that you're gonna want to pick and it's

00:26:05.940 --> 00:26:09.300
going to be pretty clear which one you
want to use in these different cases.

00:26:09.700 --> 00:26:12.410
<v Amos Wenger>Do you run unit tests
for all 16 combinations?

00:26:13.250 --> 00:26:15.920
<v James Munns>I mean, I'm just barely
implementing this at this point.

00:26:15.920 --> 00:26:17.320
So the answer is no.

00:26:17.700 --> 00:26:21.809
But it's interesting because the core
of it is actually fairly independent.

00:26:22.169 --> 00:26:26.985
I don't necessarily know if I
need 16 unit tests for this.

00:26:27.265 --> 00:26:31.895
I think you could unit test the different
implementations of each of these traits.

00:26:32.175 --> 00:26:36.775
And as long as they follow the interface
guaranteed by the trait, then the

00:26:36.785 --> 00:26:38.735
queue itself is guaranteed to work.

00:26:39.975 --> 00:26:43.165
That's maybe a bold assumption,
but it's one of those things

00:26:43.165 --> 00:26:44.835
where they are independent.

00:26:44.915 --> 00:26:45.285
<v Amos Wenger>Sure.

00:26:45.525 --> 00:26:45.785
Right.

00:26:45.915 --> 00:26:47.745
Let's call them integration tests, then.

00:26:47.785 --> 00:26:48.135
<v James Munns>Yeah.

00:26:48.495 --> 00:26:48.805
Yeah.

00:26:49.295 --> 00:26:52.365
So right now I have some basic like
smoke tests that are running with Miri

00:26:52.645 --> 00:26:55.775
where I do check some combinations
of these, basically like the inline

00:26:55.785 --> 00:26:57.264
ones and the not inline ones.

00:26:57.284 --> 00:27:00.054
And then the async ones
and the not blocking ones.

00:27:00.242 --> 00:27:01.900
But I think it makes sense
to have integration tests

00:27:01.910 --> 00:27:03.110
for all 16 of these, but...

00:27:03.345 --> 00:27:08.225
right now this was mostly: how far
can I push generics to be able to

00:27:08.225 --> 00:27:13.765
have 16 versions of this library that
I don't have to write 16 times with

00:27:13.971 --> 00:27:15.591
subtle copy and paste differences.

00:27:15.621 --> 00:27:18.931
Because that's what kept me from doing
this for nearly two or three years since

00:27:18.931 --> 00:27:23.131
I last touched BBQueue is: I knew I
wanted to support all these use cases

00:27:23.391 --> 00:27:27.631
but I couldn't figure out how to flex
the type system in the right way that

00:27:27.631 --> 00:27:30.359
wasn't more complicated than it was worth.

00:27:30.879 --> 00:27:34.609
And I think I've come up with a pattern
where the amount of complication

00:27:34.619 --> 00:27:36.069
gets you something worth it.

00:27:36.759 --> 00:27:40.869
And by having named varieties like this,
that there's a chance that I can hide

00:27:40.869 --> 00:27:44.219
generics from people who don't need to
worry about them, and just go: I just

00:27:44.219 --> 00:27:46.049
always use the Kansas City version of it.

00:27:46.049 --> 00:27:48.269
And I don't think about those
four generic parameters.

00:27:48.519 --> 00:27:51.829
I just get a Kansas City queue
and a Kansas City producer

00:27:52.019 --> 00:27:53.199
and a Kansas City consumer.

00:27:53.199 --> 00:27:57.209
And I might have one macro that
makes, you know, queue producer and

00:27:57.209 --> 00:27:59.019
consumer for all of these varieties.

00:27:59.259 --> 00:28:01.009
So that you get like
real types out of them.

00:28:01.299 --> 00:28:04.279
But I'm okay with using a macro for
that, versus having a macro that

00:28:04.289 --> 00:28:08.419
gets instantiated for 16 different
full implementations of the queue.

00:28:08.644 --> 00:28:11.544
<v Amos Wenger>Yeah, yeah, So this
isn't on crates.io yet.

00:28:11.614 --> 00:28:13.904
And I've tried to find the source code.

00:28:13.904 --> 00:28:15.154
I haven't even been able to.

00:28:15.154 --> 00:28:16.514
When can people play with this, James?

00:28:17.209 --> 00:28:19.404
<v James Munns>It's on GitHub right now.

00:28:19.404 --> 00:28:22.634
So in my GitHub, it's BBQ2.

00:28:22.654 --> 00:28:25.284
So B-B-Q- the letters- 2.

00:28:25.764 --> 00:28:28.834
So not like BBQueueue,
not like the pun, but-

00:28:29.504 --> 00:28:30.104
<v Amos Wenger>I see.

00:28:30.501 --> 00:28:32.901
So would that actually
be a separate crates?

00:28:32.911 --> 00:28:34.921
Cause it's not even a major version bump.

00:28:34.921 --> 00:28:38.641
It's like a mega version bump,
which SemVer does not account for?

00:28:38.959 --> 00:28:41.199
<v James Munns>Yeah, and this is another thing
that I'm actually playing around with.

00:28:41.259 --> 00:28:43.909
I'm running into this same thing with
Postcard of: how do you hack on a

00:28:43.919 --> 00:28:48.019
crate, or maybe iterate on it, where
you know that the design is going to

00:28:48.019 --> 00:28:49.999
need to change fairly fundamentally.

00:28:50.209 --> 00:28:53.199
You know you are going to make a
breaking change, but how do you

00:28:53.199 --> 00:28:56.569
make the two or three breaking
changes up to that breaking change?

00:28:56.609 --> 00:28:56.989
<v Amos Wenger>Right, yep.

00:28:57.079 --> 00:29:00.839
<v James Munns>I think within the world of Rust,
I'm starting to think that maybe the

00:29:00.839 --> 00:29:06.420
best way to do that is to make a BBQ2
repo or a Postcard2 repo, iterate on

00:29:06.420 --> 00:29:09.930
there, make some breaking changes in that
repo and then when the design is fairly

00:29:09.930 --> 00:29:16.140
clean, merge that into the upstream
BBQueue or Postcard repo and release

00:29:16.140 --> 00:29:18.080
it as a breaking change in that repo.

00:29:18.280 --> 00:29:21.030
But that way if I need to make
six or seven breaking changes

00:29:21.030 --> 00:29:24.810
while i'm still shaking out this
design, it doesn't inhibit the

00:29:24.810 --> 00:29:26.060
development of the other crate.

00:29:26.290 --> 00:29:29.530
It does mean I'm going to have to kind
of come in one day and just drop almost

00:29:29.560 --> 00:29:32.170
unrelated code on top of it but I mean...

00:29:32.540 --> 00:29:36.650
I can't think of a better way to do that
incrementally, because there's no concept

00:29:36.650 --> 00:29:40.570
where I can have breaking changes other
than maybe those like .alpha releases

00:29:40.570 --> 00:29:43.010
which don't work super well in cargo.

00:29:43.060 --> 00:29:45.890
Like, they work how they're documented
to work, but that's not the way

00:29:45.890 --> 00:29:47.550
you want them to work necessarily.

00:29:47.680 --> 00:29:50.380
<v Amos Wenger>I like that you have a separate
repository, because sometimes you see

00:29:50.380 --> 00:29:51.800
projects doing that with branches.

00:29:51.800 --> 00:29:54.720
And the thing is that all branches
except the default one are really

00:29:54.720 --> 00:29:56.310
obscured in the UI in GitHub.

00:29:56.410 --> 00:30:00.690
So, sometimes, you have to go to
the branches tab and then you see:

00:30:00.690 --> 00:30:03.920
Oh, this one is 135 commits ahead
of the main branch or something,

00:30:03.920 --> 00:30:04.920
then you backport everything.

00:30:05.030 --> 00:30:08.150
<v James Munns>It's like a versioned
ng, like if it was BBQueue ng

00:30:08.150 --> 00:30:09.770
or something like that, so...

00:30:10.365 --> 00:30:11.645
But yeah, I'm still
playing around with this.

00:30:11.655 --> 00:30:15.245
The whole reason that I want this is for
a network stack, and I'll get to that

00:30:15.245 --> 00:30:17.695
at some point in the research, but I
realized part of my network stack could

00:30:17.695 --> 00:30:21.575
really use a queue shaped like BBQueue,
because I want to be able to do hardware

00:30:21.575 --> 00:30:23.345
accelerated sending and receiving.

00:30:23.695 --> 00:30:27.095
And I realized that BBQueue wasn't going
to cut it in all the places that I wanted

00:30:27.105 --> 00:30:30.905
to use it, so it became time to come back
and look at BBQueue, and it turns out with

00:30:30.955 --> 00:30:34.945
two or three years of learning how to do
Rust better, I figured out how to solve

00:30:34.945 --> 00:30:39.965
that problem that had been frustrating me
for a longer time than I'd like to admit.

00:30:45.965 --> 00:30:47.725
Sponsor: This episode is
sponsored by OneVariable.

00:30:48.125 --> 00:30:51.765
OneVariable is a consultancy focused
on advising and development services

00:30:51.785 --> 00:30:55.585
in the areas of systems engineering,
embedded systems, and software development

00:30:55.595 --> 00:30:56.905
in the Rust programming language.

00:30:57.675 --> 00:30:59.305
Do you need help building
something in Rust?

00:30:59.515 --> 00:31:02.595
Check out onevariable.com/work
to see if one of the specialties

00:31:02.605 --> 00:31:03.415
speaks to your needs.

00:31:03.675 --> 00:31:06.545
And get in touch by emailing
contact@onevariable.com.

00:31:06.565 --> 00:31:09.045
That's contact@onevariable.com.

