diff --git a/Cargo.toml b/Cargo.toml index e3cb010b5c..ed0312df1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ cortex-m = "0.7.0" cortex-m-rtic-macros = { path = "macros", version = "0.6.0-alpha.5" } rtic-monotonic = "0.1.0-alpha.2" rtic-core = "0.3.1" -heapless = "0.7.1" +heapless = "0.7.5" bare-metal = "1.0.0" [dependencies.dwt-systick-monotonic] diff --git a/README_ru.md b/README_ru.md index 918d03e3d3..49e66d1d71 100644 --- a/README_ru.md +++ b/README_ru.md @@ -50,10 +50,16 @@ - Приложения должны быть написаны в редакции 2018. +## [Руководство пользователя](https://rtic.rs) - [(Версия в разработке)](https://rtic.rs/dev) + ## [Документация пользователя](https://rtic.rs) ## [Справочник по API](https://rtic.rs/stable/api/) +## [Сборник примеров, предоставляемы сообществом][examples] + +[examples]: https://github.com/rtic-rs/rtic-examples + ## Чат Присоединяйтесь к нам, чтобы говорить о RTIC [в Matrix-комнате][matrix-room]. diff --git a/book/ru/src/by-example/app.md b/book/ru/src/by-example/app.md index 628819adcd..5beca23930 100644 --- a/book/ru/src/by-example/app.md +++ b/book/ru/src/by-example/app.md @@ -23,7 +23,7 @@ Внутри модуля `app` атрибут ожидает найти функцию инициализации, помеченную атрибутом `init`. Эта функция должна иметь сигнатуру -`fn(init::Context) [-> init::LateResources]` (возвращаемый тип нужен не всегда). +`fn(init::Context) (-> init::LateResources, init::Monotonics)`. Эта функция инициализации будет первой частью программы, выполняемой при запуске. Функция `init` будет запущена *с отключенными прерываниями* и будет иметь эксклюзивный доступ @@ -54,6 +54,12 @@ $ cargo run --example init {{#include ../../../../ci/expected/init.run}} ``` +> **ПРИМЕЧАНИЕ**: Не забывайте указывать выбранное вами целевое устройство, передавая параметр target +> в cargo (например `cargo run --example init --target thumbv7m-none-eabi`) или +> настроив устройство, используемое по умолчанию для сборки примеров в `.cargo/config.toml`. +> В нашем случае используется Cortex M3, эмулируемый с помощью QEMU, поэтому пишем `thumbv7m-none-eabi`. +> Смотрите [`Создание нового проекта`](./new.md) для большей информации. + ## `idle` Функцию, помеченную атрибутом `idle` может опционально добавить в модуль. diff --git a/book/ru/src/by-example/resources.md b/book/ru/src/by-example/resources.md index 70f798d25e..ed8904ba93 100644 --- a/book/ru/src/by-example/resources.md +++ b/book/ru/src/by-example/resources.md @@ -7,21 +7,26 @@ Фреймворк дает пользователю полный контроль за тем, какой контекст может получить доступ к какому ресурсу. -Все ресурсы определены в одной структуре внутри модуля `#[app]`. -Каждое поле структуры соответствует отдельному ресурсу. -`struct`-ура должна быть аннотирована следующим атрибутом: `#[resources]`. - -Ресурсам могут быть опционально даны начальные значения с помощью атрибута `#[init]`. -Ресурсы, которым не передано начально значение, называются -*поздними* ресурсами, более детально они описаны в одном из разделов на этой странице. +Все ресурсы определены в *двух* структурах внутри модуля `#[app]`. +Каждое поле этих структур соответствует отдельному ресурсу. +Одна `struct`-ура должна быть аннотирована атрибутом `#[local]`. +Другая `struct`-ура должна быть аннотирована атрибутом `#[shared]`. +Разница между этими двумя множествами ресурсов будет описана познее. Каждый контекс (задача-обработчик, `init` или `idle`) должен указать ресурсы, к которым он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя -аргумент `resources`. Этот аргумент принимает список имен ресурсов в качестве значения. -Перечисленные ресурсы становятся доступны в контексте через поле `resources` структуры `Context`. +либо аргумент `local`, либо `shared`. Этот аргумент принимает список имен ресурсов в качестве значения. +Перечисленные ресурсы становятся доступны в контексте через поля `local` и `shared` структуры `Context`. -Пример программы, показанной ниже содержит два обработчика прерывания, которые разделяют -доступ к ресурсу под названием `shared`. +Во время выполнения при выходе из функции `#[init]` все ресурсы инициализированы. +Функция `#[init]` должна возвращать начальные значения для всех ресурсов; +отсюда следует, что тип возвращаемого ею значения включает типы +структур `#[shared]` и `#[local]`. +Поскольку ресурсы инициализированы в ходе функции `#[init]`, к ним нельзя +получить доступ внетри функции `#[init]`. + +Пример программы, показанной ниже содержит два обработчика прерывания. +Каждый обработчик имеет доступ к его собственному `#[local]` ресурсу. ``` rust {{#include ../../../../examples/resource.rs}} @@ -32,15 +37,17 @@ $ cargo run --example resource {{#include ../../../../ci/expected/resource.run}} ``` -Заметьте, что к ресурсу `shared` нельзя получить доступ из `idle`. Попытка сделать это -приведет к ошибке компиляции. +К ресурсу `#[local]` нельзя получить доступ извне задачи к которой он +привязан атрибутом `#[task]`. +Попытка обращения к одному и тому же ресурсу `#[local]` из более чем одной +задачи - ошибка компиляции. ## `lock` -Критические секции необходимы для разделения изменяемых данных таким образом, +Критические секции необходимы для доступа к ресурсам `#[shared]` таким образом, чтобы избежать гонок данных. -Поле `resources`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого +Поле `shared`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого ресурса, доступного задаче. Единственный метод этого трейта, [`lock`], запускает свой аргумент-замыкание в критической секции. @@ -81,33 +88,7 @@ $ cargo run --example lock {{#include ../../../../examples/multilock.rs}} ``` -## Поздние ресурсы - -Поздние ресурсы - такие ресурсы, которым не передано начальное значение во время компиляции -с помощью атрибута `#[init]`, но которые вместо этого инициализируются во время выполнения -с помощью значений из структуры `init::LateResources`, возвращаемой функцией `init`. - -Поздние ресурсы полезны, например, для *move* (передача владения) периферии, -инициализированной в `init`, в задачи. - -Пример ниже использует поздние ресурсы, чтобы установить неблокируемый односторонний канал -между обработчиком прерывания `UART0` и задачей `idle`. Для канала использована очередь типа -один производитель-один потребитель [`Queue`]. Структура очереди разделяется на потребителя -и производителя в `init`, а затем каждая из частей располагается в отдельном ресурсу; -`UART0` владеет ресурсом производителя, а `idle` владеет ресурсом потребителя. - -[`Queue`]: ../../../api/heapless/spsc/struct.Queue.html - -``` rust -{{#include ../../../../examples/late.rs}} -``` - -``` console -$ cargo run --example late -{{#include ../../../../ci/expected/late.run}} -``` - -## Только разделяемый доступ +## Только разделяемый (`&-`) доступ По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (`&mut-`) к ресурсам, но возможно указать, что задаче достаточен разделяемый доступ (`&-`) к ресурсы с помощью синтакисиса @@ -139,11 +120,21 @@ $ cargo run --example only-shared-access ## Неблокируемый доступ к изменяемым ресурсам -Есть две других возможности доступа к ресурсам +Критическая секция *не* требуется для доступа к ресурсу `#[shared]`, +к которому обращаются только из задач с *одинаковым* приоритетом. +В этом случае вы можете избежать `lock` API, добавив атрибут поля `#[lock_free]` при объявдении ресурса (смотреть пример ниже). +Заметьте, что это лишь для удобства: даже если вы используете `lock` API, +во время выполнения фреймворк *не* создаст критическую секцию. +Еще одно ценное замечание: использование `#[lock_free]` на ресурсах, +разделяемых задачами, запускаемыми с разными приоритетами +приведет к ошибке *компиляции* -- не импользование `lock` API может +привести к гонке данных в этом случае. -* `#[lock_free]`: могут быть несколько задач с одинаковым приоритетом, - получающие доступ к ресурсу без критических секций. Так как задачи с - одинаковым приоритетом никогда не могут вытеснить друг друга, это безопасно. -* `#[task_local]`: в этом случае должна быть только одна задача, использующая - этот ресурс, так же как локальный `static mut` ресурс задачи, но (опционально) устанавливаемая с в init. +``` rust +{{#include ../../../../examples/lock-free.rs}} +``` +``` console +$ cargo run --example lock-free +{{#include ../../../../ci/expected/lock-free.run}} +``` \ No newline at end of file diff --git a/book/ru/src/by-example/tips.md b/book/ru/src/by-example/tips.md index cf66c4b741..f19cfee9d8 100644 --- a/book/ru/src/by-example/tips.md +++ b/book/ru/src/by-example/tips.md @@ -1,5 +1,9 @@ # Советы и хитрости +Полные примеры для RTIC смотрите в репозитарии [rtic-examples][rtic-examples]. + +[rtic-examples]: https://github.com/rtic-rs/rtic-examples + ## Обобщенное программирование (Generics) Все объекты, предоставляющие ресурысы реализуют трейт `rtic::Mutex`. diff --git a/book/ru/src/internals/late-resources.md b/book/ru/src/internals/late-resources.md index 0fad0aecf8..146c438d70 100644 --- a/book/ru/src/internals/late-resources.md +++ b/book/ru/src/internals/late-resources.md @@ -103,8 +103,7 @@ mod app { } ``` -Важная деталь здесь то, что `interrupt::enable` ведет себя как like a *compiler -fence*, которое не дает компилятору пореставить запись в `X` *после* +Важная деталь здесь то, что `interrupt::enable` ведет себя как *барьер компиляции*, который не дает компилятору переставить запись в `X` *после* `interrupt::enable`. Если бы компилятор мог делать такие перестановки появились бы гонки данных между этой записью и любой операцией `foo`, взаимодействующей с `X`. diff --git a/book/ru/src/internals/tasks.md b/book/ru/src/internals/tasks.md index 665032515c..01380ba907 100644 --- a/book/ru/src/internals/tasks.md +++ b/book/ru/src/internals/tasks.md @@ -79,8 +79,8 @@ mod app { } // очередь готовности диспетчера задач - // `U4` - целое число, представляющее собой емкость этой очереди - static mut RQ1: Queue, U4> = Queue::new(); + // `5-1=4` - представляет собой емкость этой очереди + static mut RQ1: Queue, 5> = Queue::new(); // обработчик прерывания, выбранный для диспетчеризации задач с приоритетом `1` #[no_mangle] @@ -151,9 +151,9 @@ mod app { const RQ1_CEILING: u8 = 2; // используется, чтобы отследить сколько еще сообщений для `bar` можно поставить в очередь - // `U2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь + // `3-1=2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь // эта очередь заполняется фреймворком до того, как запустится `init` - static mut bar_FQ: Queue<(), U2> = Queue::new(); + static mut bar_FQ: Queue<(), 3> = Queue::new(); // Поиск максимального приоритета для конечного потребителя `bar_FQ` const bar_FQ_CEILING: u8 = 2; @@ -227,7 +227,7 @@ mod app { // список свободной памяти: используется для отслеживания свободных ячеек в массиве `baz_INPUTS` // эта очередь инициализируется значениями `0` и `1` перед запуском `init` - static mut baz_FQ: Queue = Queue::new(); + static mut baz_FQ: Queue = Queue::new(); // Поиск максимального приоритета для конечного потребителя `baz_FQ` const baz_FQ_CEILING: u8 = 2; diff --git a/book/ru/src/migration/migration_v5.md b/book/ru/src/migration/migration_v5.md index 04aedc5f81..870c20fd62 100644 --- a/book/ru/src/migration/migration_v5.md +++ b/book/ru/src/migration/migration_v5.md @@ -30,7 +30,46 @@ mod app { Так как теперь используется обычный модуль Rust, это значит, что можно использовать обычный пользовательский код в этом модуле. -Также жто значит, что `use`-выражения для ресурсов (и т.п.) могут понадобиться. +Также это значит, что `use`-выражения для ресурсов, используемые +в пользовательском коде должны быть перемещены внутрь `mod app`, +либо на них можно сослаться с помощью `super`. Например, измените: + +```rust +use some_crate::some_func; + +#[rtic::app(/* .. */)] +const APP: () = { + fn func() { + some_crate::some_func(); + } +}; +``` + +на + +```rust +#[rtic::app(/* .. */)] +mod app { + use some_crate::some_func; + + fn func() { + some_crate::some_func(); + } +} +``` + +или + +```rust +use some_crate::some_func; + +#[rtic::app(/* .. */)] +mod app { + fn func() { + super::some_crate::some_func(); + } +} +``` ## Перенос диспетчеров из `extern "C"` в аргументы app. @@ -63,6 +102,182 @@ mod app { Это работает и для ОЗУ-функций, см. examples/ramfunc.rs +## Структуры ресурсов - `#[shared]`, `#[local]` + +Ранее ресурсы RTIC должны были размещаться в структуре с именем "Resources": + +``` rust +struct Resources { + // Ресурсы определяются здесь +} +``` + +Начиная с RTIC v0.6.0 структуры ресурсов аннотируются подобно +`#[task]`, `#[init]`, `#[idle]`: аттрибутами `#[shared]` и `#[local]` + +``` rust +#[shared] +struct MySharedResources { + // Разделяемые задачами ресурсы определены здесь +} + +#[local] +struct MyLocalResources { + // Ресурсы, определенные здесь нельзя передавать между задачами; каждый из них локальный для единственной задачи +} +``` + +Эти структуры разработчик может называть по своему желанию. + +## `shared` и `local` аргументы в `#[task]`'ах + +В v0.6.0 ресурсы разделены на `shared` ресурсы и `local` ресурсы. +`#[task]`, `#[init]` и `#[idle]` больше не имеют аргумента `resources`; +они должны использовать аргументы `shared` и `local`. + +В v0.5.x: + +``` rust +struct Resources { + local_to_b: i64, + shared_by_a_and_b: i64, +} + +#[task(resources = [shared_by_a_and_b])] +fn a(_: a::Context) {} + +#[task(resources = [shared_by_a_and_b, local_to_b])] +fn b(_: b::Context) {} +``` + +В v0.6.0: + +``` rust +#[shared] +struct Shared { + shared_by_a_and_b: i64, +} + +#[local] +struct Local { + local_to_b: i64, +} + +#[task(shared = [shared_by_a_and_b])] +fn a(_: a::Context) {} + +#[task(shared = [shared_by_a_and_b], local = [local_to_b])] +fn b(_: b::Context) {} +``` + +## Симметричные блокировки + +Теперь RTIC использует симметричные блокировки, это значит, что метод `lock` нужно использовать для +всех доступов к `shared` ресурсам. Поскольку высокоприоритетные задачи имеют эксклюзивный доступ к ресурсу, +в старом коде можно было следующее: + +``` rust +#[task(priority = 2, resources = [r])] +fn foo(cx: foo::Context) { + cx.resources.r = /* ... */; +} + +#[task(resources = [r])] +fn bar(cx: bar::Context) { + cx.resources.r.lock(|r| r = /* ... */); +} +``` + +С симметричными блокировками нужно вызывать `lock` для обоих задач: + +``` rust +#[task(priority = 2, shared = [r])] +fn foo(cx: foo::Context) { + cx.shared.r.lock(|r| r = /* ... */); +} + +#[task(shared = [r])] +fn bar(cx: bar::Context) { + cx.shared.r.lock(|r| r = /* ... */); +} +``` + +Заметьте, что скорость работы не изменяется благодаря оптимизациям LLVM, которые убирают ненужные блокировки. + +## Неблокирующий доступ к ресурсам + +В RTIC 0.5 к ресурсам разделяемым задачами, запускаемыми с одинаковым +приоритетом, можно получить доступ *без* `lock` API. +Это все еще возможно в 0.6: ресурс `#[shared]` должен быть аннотирован +аттрибутом поля `#[lock_free]`. + +v0.5 код: + +``` rust +struct Resources { + counter: u64, +} + +#[task(resources = [counter])] +fn a(cx: a::Context) { + *cx.resources.counter += 1; +} + +#[task(resources = [counter])] +fn b(cx: b::Context) { + *cx.resources.counter += 1; +} +``` + +v0.6 код: + +``` rust +#[shared] +struct Shared { + #[lock_free] + counter: u64, +} + +#[task(shared = [counter])] +fn a(cx: a::Context) { + *cx.shared.counter += 1; +} + +#[task(shared = [counter])] +fn b(cx: b::Context) { + *cx.shared.counter += 1; +} +``` + +## нет преобразования `static mut` + +`static mut` переменные больше не преобразуются в безопасные `&'static mut` ссылки. +Вместо этого синтаксиса используйте аргумент `local` в `#[init]`. + +v0.5.x code: + +``` rust +#[init] +fn init(_: init::Context) { + static mut BUFFER: [u8; 1024] = [0; 1024]; + let buffer: &'static mut [u8; 1024] = BUFFER; +} +``` + +v0.6.0 code: + +``` rust +#[init(local = [ + buffer: [u8; 1024] = [0; 1024] +// type ^^^^^^^^^^^^ ^^^^^^^^^ initial value +])] +fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let buffer: &'static mut [u8; 1024] = cx.local.buffer; + + (Shared {}, Local {}, init::Monotonics()) +} +``` + ## Init всегда возвращает поздние ресурсы С целью сделать API более симметричным задача #[init] всегда возвращает поздние ресурсы. @@ -83,51 +298,27 @@ mod app { на это: + ``` rust #[rtic::app(device = lm3s6965)] mod app { + #[shared] + struct MySharedResources {} + + #[local] + struct MyLocalResources {} + #[init] - fn init(_: init::Context) -> init::LateResources { + fn init(_: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) { rtic::pend(Interrupt::UART0); - init::LateResources {} + (MySharedResources, MyLocalResources, init::Monotonics()) } - // [еще код] + // [more code] } ``` -## Структура Resources - `#[resources]` - -Ранее ресурсы RTIC должны были располагаться в структуре с именем "Resources": - -``` rust -struct Resources { - // Ресурсы определены здесь -} -``` - -В RTIC v0.6.0 структура ресурсов аннотируется также, как и -`#[task]`, `#[init]`, `#[idle]`: атрибутом `#[resources]` - -``` rust -#[resources] -struct Resources { - // Ресурсы определены здесь -} -``` - -На самом деле, имя структуры предоставлено на усмотрение разработчика: - -``` rust -#[resources] -struct Whateveryouwant { - // Ресурсы определены здесь -} -``` - -будет работать так же хороршо. - ## Вызов/планирование откуда угодно С этой новой возвожностью, старый код, такой как: @@ -161,40 +352,6 @@ fn bar(_c: bar::Context) { Заметьте, что атрибуты `spawn` и `schedule` больше не нужны. -## Симметричные блокировки - -Теперь RTIC использует симметричные блокировки, это значит, что метод `lock` нужно использовать для -всех доступов к ресурсам. Поскольку высокоприоритетные задачи имеют эксклюзивный доступ к ресурсу, -в старом коде можно было следующее: - -``` rust -#[task(priority = 2, resources = [r])] -fn foo(cx: foo::Context) { - cx.resources.r = /* ... */; -} - -#[task(resources = [r])] -fn bar(cx: bar::Context) { - cx.resources.r.lock(|r| r = /* ... */); -} -``` - -С симметричными блокировками нужно вызывать `lock` для обоих задач: - -``` rust -#[task(priority = 2, resources = [r])] -fn foo(cx: foo::Context) { - cx.resources.r.lock(|r| r = /* ... */); -} - -#[task(resources = [r])] -fn bar(cx: bar::Context) { - cx.resources.r.lock(|r| r = /* ... */); -} -``` - -Заметьте, что скорость работы не изменяется благодаря оптимизациям LLVM, которые убирают ненужные блокировки. - --- ## Дополнительно diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 6920031add..63a4e3cadc 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -108,7 +108,6 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { let name = &monotonic.ident; let name_str = &name.to_string(); let ident = util::monotonic_ident(&name_str); - let ident = util::mark_internal_ident(&ident); let panic_str = &format!( "Use of monotonic '{}' before it was passed to the runtime", name_str diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs index c239b0f828..57103acd0b 100644 --- a/macros/src/codegen/dispatchers.rs +++ b/macros/src/codegen/dispatchers.rs @@ -33,6 +33,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec Vec), @@ -59,6 +59,8 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec = rtic::RacyCell::new(#rq_expr); )); @@ -69,9 +71,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec (TokenStream2, }; let mangled_name = if matches!(task_local, TaskLocal::External) { - util::mark_internal_ident(&util::static_local_resource_ident(name)) + util::static_local_resource_ident(name) } else { - util::mark_internal_ident(&util::declared_static_local_resource_ident( - name, &task_name, - )) + util::declared_static_local_resource_ident(name, &task_name) }; fields.push(quote!( @@ -86,9 +84,9 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); let ident = util::local_resources_ident(ctxt, app); - let ident = util::mark_internal_ident(&ident); let item = quote!( #[allow(non_snake_case)] + #[allow(non_camel_case_types)] #[doc = #doc] pub struct #ident<#lt> { #(#fields,)* diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index c7092bd370..4db2c0c2f4 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -67,7 +67,6 @@ pub fn codegen( if ctxt.has_local_resources(app) { let ident = util::local_resources_ident(ctxt, app); - let ident = util::mark_internal_ident(&ident); let lt = if local_resources_tick { lt = Some(quote!('a)); Some(quote!('a)) @@ -90,7 +89,6 @@ pub fn codegen( if ctxt.has_shared_resources(app) { let ident = util::shared_resources_ident(ctxt, app); - let ident = util::mark_internal_ident(&ident); let lt = if shared_resources_tick { lt = Some(quote!('a)); Some(quote!('a)) @@ -131,6 +129,7 @@ pub fn codegen( items.push(quote!( /// Monotonics used by the system #[allow(non_snake_case)] + #[allow(non_camel_case_types)] pub struct #internal_monotonics_ident( #(pub #monotonic_types),* ); @@ -178,6 +177,8 @@ pub fn codegen( items.push(quote!( #(#cfgs)* /// Execution context + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] pub struct #internal_context_name<#lt> { #(#fields,)* } @@ -209,11 +210,8 @@ pub fn codegen( let args = &args; let tupled = &tupled; let fq = util::fq_ident(name); - let fq = util::mark_internal_ident(&fq); let rq = util::rq_ident(priority); - let rq = util::mark_internal_ident(&rq); let inputs = util::inputs_ident(name); - let inputs = util::mark_internal_ident(&inputs); let device = &extra.device; let enum_ = util::interrupt_ident(); @@ -263,16 +261,13 @@ pub fn codegen( // Schedule caller for (_, monotonic) in &app.monotonics { let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let instants = util::mark_internal_ident(&instants); let monotonic_name = monotonic.ident.to_string(); let tq = util::tq_ident(&monotonic.ident.to_string()); - let tq = util::mark_internal_ident(&tq); let t = util::schedule_t_ident(); let m = &monotonic.ident; let mono_type = &monotonic.ident; let m_ident = util::monotonic_ident(&monotonic_name); - let m_ident = util::mark_internal_ident(&m_ident); let m_isr = &monotonic.args.binds; let enum_ = util::interrupt_ident(); @@ -290,7 +285,7 @@ pub fn codegen( ) }; - let tq_marker = util::mark_internal_ident(&util::timer_queue_marker_ident()); + let tq_marker = &util::timer_queue_marker_ident(); // For future use // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); @@ -318,6 +313,8 @@ pub fn codegen( items.push(quote!( #(#cfgs)* + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] pub struct #internal_spawn_handle_ident { #[doc(hidden)] marker: u32, @@ -327,7 +324,7 @@ pub fn codegen( impl #internal_spawn_handle_ident { pub fn cancel(self) -> Result<#ty, ()> { rtic::export::interrupt::free(|_| unsafe { - let tq = &mut *#tq.get_mut_unchecked().as_mut_ptr(); + let tq = #tq.get_mut_unchecked(); if let Some((_task, index)) = tq.cancel_marker(self.marker) { // Get the message let msg = #inputs @@ -359,7 +356,7 @@ pub fn codegen( let marker = *#tq_marker.get_mut_unchecked(); *#tq_marker.get_mut_unchecked() = #tq_marker.get_mut_unchecked().wrapping_add(1); - let tq = &mut *#tq.get_mut_unchecked().as_mut_ptr(); + let tq = #tq.get_mut_unchecked(); tq.update_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker }) }) @@ -371,6 +368,7 @@ pub fn codegen( /// /// This will use the time `Instant::new(0)` as baseline if called in `#[init]`, /// so if you use a non-resetable timer use `spawn_at` when in `#[init]` + #[allow(non_snake_case)] pub fn #internal_spawn_after_ident( duration: D #(,#args)* @@ -390,6 +388,7 @@ pub fn codegen( #(#cfgs)* /// Spawns the task at a fixed time instant + #[allow(non_snake_case)] pub fn #internal_spawn_at_ident( instant: rtic::time::Instant<#mono_type> #(,#args)* @@ -420,7 +419,7 @@ pub fn codegen( *#tq_marker.get_mut_unchecked() = #tq_marker.get_mut_unchecked().wrapping_add(1); - let tq = &mut *#tq.get_mut_unchecked().as_mut_ptr(); + let tq = #tq.get_mut_unchecked(); tq.enqueue_unchecked( nr, diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index 161068d2bd..5624b20a72 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -11,7 +11,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { // Initialize shared resources for (name, res) in &app.shared_resources { - let mangled_name = util::mark_internal_ident(&util::static_shared_resource_ident(name)); + let mangled_name = util::static_shared_resource_ident(name); // If it's live let cfgs = res.cfgs.clone(); if analysis.shared_resource_locations.get(name).is_some() { @@ -29,7 +29,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { // Initialize local resources for (name, res) in &app.local_resources { - let mangled_name = util::mark_internal_ident(&util::static_local_resource_ident(name)); + let mangled_name = util::static_local_resource_ident(name); // If it's live let cfgs = res.cfgs.clone(); if analysis.local_resource_locations.get(name).is_some() { @@ -58,7 +58,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { // Store the monotonic let name = util::monotonic_ident(&monotonic.to_string()); - let name = util::mark_internal_ident(&name); stmts.push(quote!(*#name.get_mut_unchecked() = Some(monotonics.#idx);)); } diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index ae628f6eb9..d3c4f54d9e 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -17,7 +17,6 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Vec (TokenStream2, None }; let ty = &res.ty; - let mangled_name = util::mark_internal_ident(&util::static_shared_resource_ident(&name)); + let mangled_name = util::static_shared_resource_ident(&name); if !res.properties.lock_free { if access.is_shared() { @@ -102,9 +102,9 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); let ident = util::shared_resources_ident(ctxt, app); - let ident = util::mark_internal_ident(&ident); let item = quote!( #[allow(non_snake_case)] + #[allow(non_camel_case_types)] #[doc = #doc] pub struct #ident<#lt> { #(#fields,)* diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 0b07335946..1669a3fb1e 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -37,7 +37,6 @@ pub fn codegen( // Create free queues and inputs / instants buffers let fq = util::fq_ident(name); - let fq = util::mark_internal_ident(&fq); let (fq_ty, fq_expr, mk_uninit): (_, _, Box Option<_>>) = { ( @@ -49,6 +48,8 @@ pub fn codegen( mod_app.push(quote!( // /// Queue version of a free-list that keeps track of empty slots in // /// the following buffers + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] #[doc(hidden)] static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr); )); @@ -59,7 +60,6 @@ pub fn codegen( for (_, monotonic) in &app.monotonics { let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let instants = util::mark_internal_ident(&instants); let mono_type = &monotonic.ty; let uninit = mk_uninit(); @@ -69,6 +69,8 @@ pub fn codegen( #uninit // /// Buffer that holds the instants associated to the inputs of a task // #[doc = #doc] + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] #[doc(hidden)] static #instants: rtic::RacyCell<[core::mem::MaybeUninit>; #cap_lit]> = @@ -78,10 +80,11 @@ pub fn codegen( let uninit = mk_uninit(); let inputs_ident = util::inputs_ident(name); - let inputs_ident = util::mark_internal_ident(&inputs_ident); mod_app.push(quote!( #uninit // /// Buffer that holds the inputs of a task + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] #[doc(hidden)] static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> = rtic::RacyCell::new([#(#elems,)*]); diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs index abddbadcc0..fdfa6381a7 100644 --- a/macros/src/codegen/timer_queue.rs +++ b/macros/src/codegen/timer_queue.rs @@ -10,11 +10,12 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec = rtic::RacyCell::new(0); )); @@ -52,40 +53,40 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec>); + let n = util::capacity_literal(cap); + let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n>); // For future use // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); items.push(quote!( #[doc(hidden)] + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] static #tq: rtic::RacyCell<#tq_ty> = - rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + rtic::RacyCell::new(rtic::export::TimerQueue(rtic::export::SortedLinkedList::new_u16())); )); let mono = util::monotonic_ident(&monotonic_name); - let mono = util::mark_internal_ident(&mono); // For future use // let doc = &format!("Storage for {}", monotonic_name); items.push(quote!( #[doc(hidden)] + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] static #mono: rtic::RacyCell> = rtic::RacyCell::new(None); )); } @@ -102,7 +103,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec Vec LitInt { LitInt::new(&capacity.to_string(), Span::call_site()) @@ -14,7 +16,7 @@ pub fn capacity_literal(capacity: usize) -> LitInt { /// Identifier for the free queue pub fn fq_ident(task: &Ident) -> Ident { - Ident::new(&format!("{}_FQ", task.to_string()), Span::call_site()) + mark_internal_name(&format!("{}_FQ", task.to_string())) } /// Generates a `Mutex` implementation @@ -60,15 +62,12 @@ pub fn impl_mutex( /// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) pub fn inputs_ident(task: &Ident) -> Ident { - Ident::new(&format!("{}_INPUTS", task), Span::call_site()) + mark_internal_name(&format!("{}_INPUTS", task)) } /// Generates an identifier for the `INSTANTS` buffer (`schedule` API) pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident { - Ident::new( - &format!("{}_{}_INSTANTS", task, monotonic), - Span::call_site(), - ) + mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic)) } pub fn interrupt_ident() -> Ident { @@ -77,8 +76,7 @@ pub fn interrupt_ident() -> Ident { } pub fn timer_queue_marker_ident() -> Ident { - let span = Span::call_site(); - Ident::new("TIMER_QUEUE_MARKER", span) + mark_internal_name(&"TIMER_QUEUE_MARKER") } /// Whether `name` is an exception with configurable priority @@ -98,38 +96,24 @@ pub fn is_exception(name: &Ident) -> bool { ) } +/// Mark a name as internal +pub fn mark_internal_name(name: &str) -> Ident { + Ident::new(&format!("{}_{}", RTIC_INTERNAL, name), Span::call_site()) +} + /// Generate an internal identifier for monotonics pub fn internal_monotonics_ident(task: &Ident, monotonic: &Ident, ident_name: &str) -> Ident { - Ident::new( - &format!( - "__rtic_internal_{}_{}_{}", - task.to_string(), - monotonic.to_string(), - ident_name, - ), - Span::call_site(), - ) + mark_internal_name(&format!( + "{}_{}_{}", + task.to_string(), + monotonic.to_string(), + ident_name, + )) } /// Generate an internal identifier for tasks pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { - Ident::new( - &format!("__rtic_internal_{}_{}", task.to_string(), ident_name,), - Span::call_site(), - ) -} - -/// Mark an ident as internal -pub fn mark_internal_ident(ident: &Ident) -> Ident { - Ident::new( - &format!("__rtic_internal_{}", ident.to_string()), - Span::call_site(), - ) -} - -/// Mark an ident as internal -pub fn mark_internal_name(name: &str) -> Ident { - Ident::new(&format!("__rtic_internal_{}", name), Span::call_site()) + mark_internal_name(&format!("{}_{}", task.to_string(), ident_name)) } fn link_section_index() -> usize { @@ -215,7 +199,7 @@ pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { s.push_str("SharedResources"); - Ident::new(&s, Span::call_site()) + mark_internal_name(&s) } /// Generates a pre-reexport identifier for the "local resources" struct @@ -228,7 +212,7 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { s.push_str("LocalResources"); - Ident::new(&s, Span::call_site()) + mark_internal_name(&s) } /// Generates an identifier for a ready queue @@ -236,7 +220,7 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { /// There may be several task dispatchers, one for each priority level. /// The ready queues are SPSC queues pub fn rq_ident(priority: u8) -> Ident { - Ident::new(&format!("P{}_RQ", priority), Span::call_site()) + mark_internal_name(&format!("P{}_RQ", priority)) } /// Generates an identifier for the `enum` of `schedule`-able tasks @@ -260,33 +244,28 @@ pub fn suffixed(name: &str) -> Ident { /// Generates an identifier for a timer queue pub fn tq_ident(name: &str) -> Ident { - Ident::new(&format!("TQ_{}", name), Span::call_site()) + mark_internal_name(&format!("TQ_{}", name)) } /// Generates an identifier for monotonic timer storage pub fn monotonic_ident(name: &str) -> Ident { - Ident::new(&format!("MONOTONIC_STORAGE_{}", name), Span::call_site()) + mark_internal_name(&format!("MONOTONIC_STORAGE_{}", name)) } pub fn static_shared_resource_ident(name: &Ident) -> Ident { - Ident::new( - &format!("shared_resource_{}", name.to_string()), - Span::call_site(), - ) + mark_internal_name(&format!("shared_resource_{}", name.to_string())) } pub fn static_local_resource_ident(name: &Ident) -> Ident { - Ident::new( - &format!("local_resource_{}", name.to_string()), - Span::call_site(), - ) + mark_internal_name(&format!("local_resource_{}", name.to_string())) } pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { - Ident::new( - &format!("local_{}_{}", task_name.to_string(), name.to_string()), - Span::call_site(), - ) + mark_internal_name(&format!( + "local_{}_{}", + task_name.to_string(), + name.to_string() + )) } /// The name to get better RT flag errors diff --git a/src/export.rs b/src/export.rs index e449ef41d5..927e951e0a 100644 --- a/src/export.rs +++ b/src/export.rs @@ -13,6 +13,7 @@ pub use cortex_m::{ peripheral::{scb::SystemHandler, syst::SystClkSource, DWT, NVIC}, Peripherals, }; +pub use heapless::sorted_linked_list::SortedLinkedList; pub use heapless::spsc::Queue; pub use heapless::BinaryHeap; pub use rtic_monotonic as monotonic; diff --git a/src/lib.rs b/src/lib.rs index cd51199613..ca52ec1e3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,6 @@ pub use rtic_monotonic::{self, embedded_time as time, Monotonic}; #[doc(hidden)] pub mod export; #[doc(hidden)] -mod linked_list; -#[doc(hidden)] mod tq; /// Sets the given `interrupt` as pending diff --git a/src/linked_list.rs b/src/linked_list.rs deleted file mode 100644 index bbb935f82b..0000000000 --- a/src/linked_list.rs +++ /dev/null @@ -1,597 +0,0 @@ -use core::fmt; -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::ops::{Deref, DerefMut}; -use core::ptr; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -struct LinkedIndex(u16); - -impl LinkedIndex { - #[inline] - const unsafe fn new_unchecked(value: u16) -> Self { - LinkedIndex(value) - } - - #[inline] - const fn none() -> Self { - LinkedIndex(u16::MAX) - } - - #[inline] - const fn option(self) -> Option { - if self.0 == u16::MAX { - None - } else { - Some(self.0) - } - } -} - -/// A node in the linked list. -pub struct Node { - val: MaybeUninit, - next: LinkedIndex, -} - -/// Iterator for the linked list. -pub struct Iter<'a, T, Kind, const N: usize> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - list: &'a LinkedList, - index: LinkedIndex, -} - -impl<'a, T, Kind, const N: usize> Iterator for Iter<'a, T, Kind, N> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let index = self.index.option()?; - - let node = self.list.node_at(index as usize); - self.index = node.next; - - Some(self.list.read_data_in_node_at(index as usize)) - } -} - -/// Comes from [`LinkedList::find_mut`]. -pub struct FindMut<'a, T, Kind, const N: usize> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - list: &'a mut LinkedList, - is_head: bool, - prev_index: LinkedIndex, - index: LinkedIndex, - maybe_changed: bool, -} - -impl<'a, T, Kind, const N: usize> FindMut<'a, T, Kind, N> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - fn pop_internal(&mut self) -> T { - if self.is_head { - // If it is the head element, we can do a normal pop - unsafe { self.list.pop_unchecked() } - } else { - // Somewhere in the list - - // Re-point the previous index - self.list.node_at_mut(self.prev_index.0 as usize).next = - self.list.node_at_mut(self.index.0 as usize).next; - - // Release the index into the free queue - self.list.node_at_mut(self.index.0 as usize).next = self.list.free; - self.list.free = self.index; - - self.list.extract_data_in_node_at(self.index.0 as usize) - } - } - - /// This will pop the element from the list. - /// - /// Complexity is O(1). - #[inline] - pub fn pop(mut self) -> T { - self.pop_internal() - } - - /// This will resort the element into the correct position in the list in needed. - /// Same as calling `drop`. - /// - /// Complexity is worst-case O(N). - #[inline] - pub fn finish(self) { - drop(self) - } -} - -impl Drop for FindMut<'_, T, Kind, N> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - fn drop(&mut self) { - // Only resort the list if the element has changed - if self.maybe_changed { - let val = self.pop_internal(); - unsafe { self.list.push_unchecked(val) }; - } - } -} - -impl Deref for FindMut<'_, T, Kind, N> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - self.list.read_data_in_node_at(self.index.0 as usize) - } -} - -impl DerefMut for FindMut<'_, T, Kind, N> -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.maybe_changed = true; - self.list.read_mut_data_in_node_at(self.index.0 as usize) - } -} - -impl fmt::Debug for FindMut<'_, T, Kind, N> -where - T: PartialEq + PartialOrd + core::fmt::Debug, - Kind: kind::Kind, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FindMut") - .field("prev_index", &self.prev_index) - .field("index", &self.index) - .field( - "prev_value", - &self - .list - .read_data_in_node_at(self.prev_index.option().unwrap() as usize), - ) - .field( - "value", - &self - .list - .read_data_in_node_at(self.index.option().unwrap() as usize), - ) - .finish() - } -} - -/// The linked list. -pub struct LinkedList -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - list: MaybeUninit<[Node; N]>, - head: LinkedIndex, - free: LinkedIndex, - _kind: PhantomData, -} - -impl LinkedList -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn node_at(&self, index: usize) -> &Node { - // Safety: The entire `self.list` is initialized in `new`, which makes this safe. - unsafe { &*(self.list.as_ptr() as *const Node).add(index) } - } - - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn node_at_mut(&mut self, index: usize) -> &mut Node { - // Safety: The entire `self.list` is initialized in `new`, which makes this safe. - unsafe { &mut *(self.list.as_mut_ptr() as *mut Node).add(index) } - } - - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn write_data_in_node_at(&mut self, index: usize, data: T) { - unsafe { - self.node_at_mut(index).val.as_mut_ptr().write(data); - } - } - - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn read_data_in_node_at(&self, index: usize) -> &T { - unsafe { &*self.node_at(index).val.as_ptr() } - } - - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn read_mut_data_in_node_at(&mut self, index: usize) -> &mut T { - unsafe { &mut *self.node_at_mut(index).val.as_mut_ptr() } - } - - /// Internal helper to not do pointer arithmetic all over the place. - #[inline] - fn extract_data_in_node_at(&mut self, index: usize) -> T { - unsafe { self.node_at(index).val.as_ptr().read() } - } - - /// Internal helper to not do pointer arithmetic all over the place. - /// Safety: This can overwrite existing allocated nodes if used improperly, meaning their - /// `Drop` methods won't run. - #[inline] - unsafe fn write_node_at(&mut self, index: usize, node: Node) { - (self.list.as_mut_ptr() as *mut Node) - .add(index) - .write(node) - } - - /// Create a new linked list. - pub fn new() -> Self { - let mut list = LinkedList { - list: MaybeUninit::uninit(), - head: LinkedIndex::none(), - free: unsafe { LinkedIndex::new_unchecked(0) }, - _kind: PhantomData, - }; - - let len = N as u16; - let mut free = 0; - - if len == 0 { - list.free = LinkedIndex::none(); - return list; - } - - // Initialize indexes - while free < len - 1 { - unsafe { - list.write_node_at( - free as usize, - Node { - val: MaybeUninit::uninit(), - next: LinkedIndex::new_unchecked(free + 1), - }, - ); - } - free += 1; - } - - // Initialize final index - unsafe { - list.write_node_at( - free as usize, - Node { - val: MaybeUninit::uninit(), - next: LinkedIndex::none(), - }, - ); - } - - list - } - - /// Push unchecked - /// - /// Complexity is O(N). - /// - /// # Safety - /// - /// Assumes that the list is not full. - pub unsafe fn push_unchecked(&mut self, value: T) { - let new = self.free.0; - // Store the data and update the next free spot - self.write_data_in_node_at(new as usize, value); - self.free = self.node_at(new as usize).next; - - if let Some(head) = self.head.option() { - // Check if we need to replace head - if self - .read_data_in_node_at(head as usize) - .partial_cmp(self.read_data_in_node_at(new as usize)) - != Kind::ordering() - { - self.node_at_mut(new as usize).next = self.head; - self.head = LinkedIndex::new_unchecked(new); - } else { - // It's not head, search the list for the correct placement - let mut current = head; - - while let Some(next) = self.node_at(current as usize).next.option() { - if self - .read_data_in_node_at(next as usize) - .partial_cmp(self.read_data_in_node_at(new as usize)) - != Kind::ordering() - { - break; - } - - current = next; - } - - self.node_at_mut(new as usize).next = self.node_at(current as usize).next; - self.node_at_mut(current as usize).next = LinkedIndex::new_unchecked(new); - } - } else { - self.node_at_mut(new as usize).next = self.head; - self.head = LinkedIndex::new_unchecked(new); - } - } - - /// Pushes an element to the linked list and sorts it into place. - /// - /// Complexity is O(N). - pub fn push(&mut self, value: T) -> Result<(), T> { - if !self.is_full() { - Ok(unsafe { self.push_unchecked(value) }) - } else { - Err(value) - } - } - - /// Get an iterator over the sorted list. - pub fn iter(&self) -> Iter<'_, T, Kind, N> { - Iter { - list: self, - index: self.head, - } - } - - /// Find an element in the list. - pub fn find_mut(&mut self, mut f: F) -> Option> - where - F: FnMut(&T) -> bool, - { - let head = self.head.option()?; - - // Special-case, first element - if f(self.read_data_in_node_at(head as usize)) { - return Some(FindMut { - is_head: true, - prev_index: LinkedIndex::none(), - index: self.head, - list: self, - maybe_changed: false, - }); - } - - let mut current = head; - - while let Some(next) = self.node_at(current as usize).next.option() { - if f(self.read_data_in_node_at(next as usize)) { - return Some(FindMut { - is_head: false, - prev_index: unsafe { LinkedIndex::new_unchecked(current) }, - index: unsafe { LinkedIndex::new_unchecked(next) }, - list: self, - maybe_changed: false, - }); - } - - current = next; - } - - None - } - - /// Peek at the first element. - pub fn peek(&self) -> Option<&T> { - self.head - .option() - .map(|head| self.read_data_in_node_at(head as usize)) - } - - /// Pop unchecked - /// - /// # Safety - /// - /// Assumes that the list is not empty. - pub unsafe fn pop_unchecked(&mut self) -> T { - let head = self.head.0; - let current = head; - self.head = self.node_at(head as usize).next; - self.node_at_mut(current as usize).next = self.free; - self.free = LinkedIndex::new_unchecked(current); - - self.extract_data_in_node_at(current as usize) - } - - /// Pops the first element in the list. - /// - /// Complexity is O(1). - pub fn pop(&mut self) -> Result { - if !self.is_empty() { - Ok(unsafe { self.pop_unchecked() }) - } else { - Err(()) - } - } - - /// Checks if the linked list is full. - #[inline] - pub fn is_full(&self) -> bool { - self.free.option().is_none() - } - - /// Checks if the linked list is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.head.option().is_none() - } -} - -impl Drop for LinkedList -where - T: PartialEq + PartialOrd, - Kind: kind::Kind, -{ - fn drop(&mut self) { - let mut index = self.head; - - while let Some(i) = index.option() { - let node = self.node_at_mut(i as usize); - index = node.next; - - unsafe { - ptr::drop_in_place(node.val.as_mut_ptr()); - } - } - } -} - -impl fmt::Debug for LinkedList -where - T: PartialEq + PartialOrd + core::fmt::Debug, - Kind: kind::Kind, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.iter()).finish() - } -} - -/// Min sorted linked list. -pub struct Min; - -/// Max sorted linked list. -pub struct Max; - -/// Sealed traits and implementations for `linked_list` -pub mod kind { - use super::{Max, Min}; - use core::cmp::Ordering; - - /// The linked list kind: min first or max first - pub unsafe trait Kind { - #[doc(hidden)] - fn ordering() -> Option; - } - - unsafe impl Kind for Min { - #[inline] - fn ordering() -> Option { - Some(Ordering::Less) - } - } - - unsafe impl Kind for Max { - #[inline] - fn ordering() -> Option { - Some(Ordering::Greater) - } - } -} - -#[cfg(test)] -mod tests { - // Note this useful idiom: importing names from outer (for mod tests) scope. - use super::*; - - #[test] - fn test_peek() { - let mut ll: LinkedList = LinkedList::new(); - - ll.push(1).unwrap(); - assert_eq!(ll.peek().unwrap(), &1); - - ll.push(2).unwrap(); - assert_eq!(ll.peek().unwrap(), &2); - - ll.push(3).unwrap(); - assert_eq!(ll.peek().unwrap(), &3); - - let mut ll: LinkedList = LinkedList::new(); - - ll.push(2).unwrap(); - assert_eq!(ll.peek().unwrap(), &2); - - ll.push(1).unwrap(); - assert_eq!(ll.peek().unwrap(), &1); - - ll.push(3).unwrap(); - assert_eq!(ll.peek().unwrap(), &1); - } - - #[test] - fn test_full() { - let mut ll: LinkedList = LinkedList::new(); - ll.push(1).unwrap(); - ll.push(2).unwrap(); - ll.push(3).unwrap(); - - assert!(ll.is_full()) - } - - #[test] - fn test_empty() { - let ll: LinkedList = LinkedList::new(); - - assert!(ll.is_empty()) - } - - #[test] - fn test_zero_size() { - let ll: LinkedList = LinkedList::new(); - - assert!(ll.is_empty()); - assert!(ll.is_full()); - } - - #[test] - fn test_rejected_push() { - let mut ll: LinkedList = LinkedList::new(); - ll.push(1).unwrap(); - ll.push(2).unwrap(); - ll.push(3).unwrap(); - - // This won't fit - let r = ll.push(4); - - assert_eq!(r, Err(4)); - } - - #[test] - fn test_updating() { - let mut ll: LinkedList = LinkedList::new(); - ll.push(1).unwrap(); - ll.push(2).unwrap(); - ll.push(3).unwrap(); - - let mut find = ll.find_mut(|v| *v == 2).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1002); - - let mut find = ll.find_mut(|v| *v == 3).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1003); - - // Remove largest element - ll.find_mut(|v| *v == 1003).unwrap().pop(); - - assert_eq!(ll.peek().unwrap(), &1002); - } -} diff --git a/src/tq.rs b/src/tq.rs index cd44abe2bc..dcaccc9e7d 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -1,9 +1,9 @@ use crate::{ - linked_list::{LinkedList, Min}, time::{Clock, Instant}, Monotonic, }; use core::cmp::Ordering; +use heapless::sorted_linked_list::{LinkedIndexU16, Min, SortedLinkedList}; #[inline(always)] fn unwrapper(val: Result) -> T { @@ -14,7 +14,9 @@ fn unwrapper(val: Result) -> T { } } -pub struct TimerQueue(pub LinkedList, Min, N>) +pub struct TimerQueue( + pub SortedLinkedList, LinkedIndexU16, Min, N>, +) where Mono: Monotonic, Task: Copy; @@ -24,10 +26,6 @@ where Mono: Monotonic, Task: Copy, { - pub fn new() -> Self { - TimerQueue(LinkedList::new()) - } - /// # Safety /// /// Writing to memory with a transmute in order to enable