@@ -153,3 +153,92 @@ impl Spawn for TokioSpawner {
153153 while tasks. join_next ( ) . await . is_some ( ) { }
154154 }
155155}
156+
157+ /// The embassy runtime adapter. Host-runnable through `arch-std` so it can stand in for
158+ /// tokio, and the same impl drives the MCU once an esp PHY backs it.
159+ #[ cfg( feature = "embassy" ) ]
160+ pub use embassy_impl:: { EmbassyRuntime , EmbassySpawner } ;
161+
162+ #[ cfg( feature = "embassy" ) ]
163+ mod embassy_impl {
164+ use super :: { RtInstant , Runtime , Spawn , SpawnedTask } ;
165+ use core:: future:: Future ;
166+ use core:: ops:: Add ;
167+ use core:: time:: Duration ;
168+
169+ const fn to_embassy ( duration : Duration ) -> embassy_time:: Duration {
170+ embassy_time:: Duration :: from_micros ( duration. as_micros ( ) as u64 )
171+ }
172+
173+ const fn from_embassy ( duration : embassy_time:: Duration ) -> Duration {
174+ Duration :: from_micros ( duration. as_micros ( ) )
175+ }
176+
177+ /// Wraps `embassy_time::Instant` so the trait's `core::time::Duration` arithmetic
178+ /// works against embassy's own `Duration` type.
179+ #[ derive( Copy , Clone ) ]
180+ pub struct EmbassyInstant ( embassy_time:: Instant ) ;
181+
182+ impl Add < Duration > for EmbassyInstant {
183+ type Output = Self ;
184+
185+ fn add ( self , rhs : Duration ) -> Self {
186+ Self ( self . 0 + to_embassy ( rhs) )
187+ }
188+ }
189+
190+ impl RtInstant for EmbassyInstant {
191+ fn saturating_duration_since ( self , earlier : Self ) -> Duration {
192+ from_embassy ( self . 0 . saturating_duration_since ( earlier. 0 ) )
193+ }
194+ }
195+
196+ pub struct EmbassyRuntime ;
197+
198+ impl Runtime for EmbassyRuntime {
199+ type Instant = EmbassyInstant ;
200+ type Spawner = EmbassySpawner ;
201+
202+ fn now ( ) -> Self :: Instant {
203+ EmbassyInstant ( embassy_time:: Instant :: now ( ) )
204+ }
205+
206+ fn sleep ( duration : Duration ) -> impl Future < Output = ( ) > + Send {
207+ embassy_time:: Timer :: after ( to_embassy ( duration) )
208+ }
209+
210+ fn sleep_until ( deadline : Self :: Instant ) -> impl Future < Output = ( ) > + Send {
211+ embassy_time:: Timer :: at ( deadline. 0 )
212+ }
213+ }
214+
215+ /// Each detached task runs in one slot of this fixed pool — embassy has no dynamic
216+ /// spawn, so the size bounds the stack's concurrent background tasks (long-lived
217+ /// reactors plus the transient ZDP/indirect/route-request ones).
218+ #[ embassy_executor:: task( pool_size = 24 ) ]
219+ async fn task_runner ( task : SpawnedTask ) {
220+ task. await ;
221+ }
222+
223+ /// Spawns into the embassy executor. Holds a [`SendSpawner`](embassy_executor::SendSpawner)
224+ /// so it is `Send + Sync`; obtained from the executor at startup.
225+ pub struct EmbassySpawner ( embassy_executor:: SendSpawner ) ;
226+
227+ impl EmbassySpawner {
228+ pub const fn new ( spawner : embassy_executor:: SendSpawner ) -> Self {
229+ Self ( spawner)
230+ }
231+ }
232+
233+ impl Spawn for EmbassySpawner {
234+ fn spawn ( & self , task : SpawnedTask ) {
235+ if self . 0 . spawn ( task_runner ( task) ) . is_err ( ) {
236+ tracing:: error!( "embassy task pool exhausted; background task dropped" ) ;
237+ }
238+ }
239+
240+ // Embassy cannot cancel spawned tasks; the MCU stack is never replaced, so there
241+ // is nothing to stop.
242+ async fn shutdown ( & self ) { }
243+ }
244+ }
0 commit comments