veecle_os_runtime/
execute.rs

1#![expect(
2    private_bounds,
3    private_interfaces,
4    reason = "
5        everything defined in here except the macro are internal helpers,
6        they often mention private types
7    "
8)]
9
10use crate::actor::{Actor, Datastore, StoreRequest};
11use crate::cons::{Cons, Nil, TupleConsToCons};
12use crate::datastore::{
13    ExclusiveReader, InitializedReader, Reader, Slot, Storable, Writer, generational,
14};
15use core::any::TypeId;
16use core::pin::Pin;
17
18/// Internal helper to implement [`Datastore::slot`] recursively for a cons-list of slots.
19trait Slots {
20    /// See [`Datastore::slot`].
21    fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
22    where
23        T: Storable + 'static;
24
25    /// Returns the [`TypeId`] and type names for all the slots stored in this type.
26    fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)>;
27}
28
29impl Slots for Nil {
30    fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
31    where
32        T: Storable + 'static,
33    {
34        panic!("no slot available for `{}`", core::any::type_name::<T>())
35    }
36
37    fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)> {
38        core::iter::empty()
39    }
40}
41
42impl<U, R> Slots for Cons<Slot<U>, R>
43where
44    U: Storable + 'static,
45    R: Slots,
46{
47    fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
48    where
49        T: Storable + 'static,
50    {
51        let this = self.project_ref();
52        if TypeId::of::<U>() == TypeId::of::<T>() {
53            this.0.assert_is_type()
54        } else {
55            this.1.slot::<T>()
56        }
57    }
58
59    fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)> {
60        R::all_slots().chain(core::iter::once((
61            TypeId::of::<U>(),
62            core::any::type_name::<U>(),
63        )))
64    }
65}
66
67/// Internal helper to take a cons-list of `Storable` types and return a cons-list of slots for them.
68trait IntoSlots {
69    /// A cons-list that contains a slot for every type in this cons-list.
70    type Slots: Slots;
71
72    /// Creates a new instance of the slot cons-list with all slots empty.
73    fn make_slots() -> Self::Slots;
74}
75
76impl IntoSlots for Nil {
77    type Slots = Nil;
78
79    fn make_slots() -> Self::Slots {
80        Nil
81    }
82}
83
84impl<T, R> IntoSlots for Cons<T, R>
85where
86    T: Storable + 'static,
87    R: IntoSlots,
88{
89    type Slots = Cons<Slot<T>, R::Slots>;
90
91    fn make_slots() -> Self::Slots {
92        Cons(Slot::<T>::new(), R::make_slots())
93    }
94}
95
96#[allow(
97    rustdoc::private_intra_doc_links,
98    reason = "
99        This lint is hit when documenting with `--document-private-items`.
100        If we use `expect`, a warning is emitted when not using `--document-private-items`.
101        If we remove the lint, a warning is emitted when using `--document-private-items`.
102        To be able to deny warning, we need to allow the lint here.
103        https://github.com/rust-lang/rust/issues/145449
104    "
105)]
106/// Given a slot cons-list, combines it with a [`generational::Source`] to implement [`Datastore`].
107impl<S: Slots> Datastore for Cons<generational::Source, S>
108where
109    S: Slots,
110{
111    fn source(self: Pin<&Self>) -> Pin<&generational::Source> {
112        let this = self.project_ref();
113        this.0
114    }
115
116    fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
117    where
118        T: Storable + 'static,
119    {
120        let this = self.project_ref();
121        this.1.slot::<T>()
122    }
123}
124
125/// Given a cons-list of [`Storable`] types, returns a complete [`Datastore`] that contains a slot for each type.
126pub fn make_store<T>() -> impl Datastore
127where
128    T: IntoSlots,
129{
130    Cons(generational::Source::new(), T::make_slots())
131}
132
133/// Internal helper to query how a [`StoreRequest`] type will use a specific type.
134pub trait AccessKind {
135    /// Returns whether this is a writer.
136    fn writer(_type_id: TypeId) -> bool {
137        false
138    }
139
140    /// Returns whether this is a reader (both exclusive and non-exclusive).
141    fn reader(_type_id: TypeId) -> bool {
142        false
143    }
144
145    /// Returns whether this is an exclusive reader.
146    fn exclusive_reader(_type_id: TypeId) -> bool {
147        false
148    }
149}
150
151impl<T> AccessKind for Writer<'_, T>
152where
153    T: Storable + 'static,
154{
155    fn writer(type_id: TypeId) -> bool {
156        type_id == TypeId::of::<T>()
157    }
158}
159
160impl<T> AccessKind for Reader<'_, T>
161where
162    T: Storable + 'static,
163{
164    fn reader(type_id: TypeId) -> bool {
165        type_id == TypeId::of::<T>()
166    }
167}
168
169impl<T> AccessKind for InitializedReader<'_, T>
170where
171    T: Storable + 'static,
172{
173    fn reader(type_id: TypeId) -> bool {
174        type_id == TypeId::of::<T>()
175    }
176}
177
178impl<T> AccessKind for ExclusiveReader<'_, T>
179where
180    T: Storable + 'static,
181{
182    fn reader(type_id: TypeId) -> bool {
183        type_id == TypeId::of::<T>()
184    }
185
186    fn exclusive_reader(type_id: TypeId) -> bool {
187        type_id == TypeId::of::<T>()
188    }
189}
190
191/// Internal helper to query how a cons-lists of [`StoreRequest`] types will use a specific type.
192pub trait AccessCount {
193    /// Returns how many writers for the given type exist in this list.
194    fn writers(type_id: TypeId) -> usize;
195
196    /// Returns how many readers for the given type exist in this list (both exclusive and non-exclusive).
197    fn readers(type_id: TypeId) -> usize;
198
199    /// Returns how many exclusive readers for the given type exist in this list.
200    fn exclusive_readers(type_id: TypeId) -> usize;
201}
202
203impl AccessCount for Nil {
204    fn writers(_type_id: TypeId) -> usize {
205        0
206    }
207
208    fn readers(_type_id: TypeId) -> usize {
209        0
210    }
211
212    fn exclusive_readers(_type_id: TypeId) -> usize {
213        0
214    }
215}
216
217impl<T, U> AccessCount for Cons<T, U>
218where
219    T: AccessKind,
220    U: AccessCount,
221{
222    fn writers(type_id: TypeId) -> usize {
223        (if T::writer(type_id) { 1 } else { 0 }) + U::writers(type_id)
224    }
225
226    fn readers(type_id: TypeId) -> usize {
227        (if T::reader(type_id) { 1 } else { 0 }) + U::readers(type_id)
228    }
229
230    fn exclusive_readers(type_id: TypeId) -> usize {
231        (if T::exclusive_reader(type_id) { 1 } else { 0 }) + U::exclusive_readers(type_id)
232    }
233}
234
235/// Internal helper to query how a cons-list of cons-lists of [`StoreRequest`] types will use a specific type.
236pub trait NestedAccessCount {
237    /// Returns how many writers for the given type exist in this list of lists.
238    fn writers(type_id: TypeId) -> usize;
239
240    /// Returns how many readers for the given type exist in this list of lists (both exclusive and
241    /// non-exclusive).
242    fn readers(type_id: TypeId) -> usize;
243
244    /// Returns how many exclusive readers for the given type exist in this list of lists.
245    fn exclusive_readers(type_id: TypeId) -> usize;
246}
247
248impl NestedAccessCount for Nil {
249    fn writers(_type_id: TypeId) -> usize {
250        0
251    }
252
253    fn readers(_type_id: TypeId) -> usize {
254        0
255    }
256
257    fn exclusive_readers(_type_id: TypeId) -> usize {
258        0
259    }
260}
261
262impl<T, U> NestedAccessCount for Cons<T, U>
263where
264    T: AccessCount,
265    U: NestedAccessCount,
266{
267    fn writers(type_id: TypeId) -> usize {
268        T::writers(type_id) + U::writers(type_id)
269    }
270
271    fn readers(type_id: TypeId) -> usize {
272        T::readers(type_id) + U::readers(type_id)
273    }
274
275    fn exclusive_readers(type_id: TypeId) -> usize {
276        T::exclusive_readers(type_id) + U::exclusive_readers(type_id)
277    }
278}
279
280/// Internal helper to access details about a cons-list of actors so they can be validated against a store.
281pub trait ActorList<'a> {
282    /// A cons-list-of-cons-list-of-store-requests for this cons-list (essentially `self.map(|actor| actor.store_request)`
283    /// where each actor has a cons-list of store-requests).
284    type StoreRequests: NestedAccessCount;
285
286    /// A cons-list of init-contexts for this cons-list (essentially `self.map(|actor| actor.init_context)`).
287    type InitContexts;
288}
289
290impl ActorList<'_> for Nil {
291    type StoreRequests = Nil;
292    type InitContexts = Nil;
293}
294
295impl<'a, T, U> ActorList<'a> for Cons<T, U>
296where
297    T: Actor<'a, StoreRequest: TupleConsToCons>,
298    U: ActorList<'a>,
299    <<T as Actor<'a>>::StoreRequest as TupleConsToCons>::Cons: AccessCount,
300{
301    /// `Actor::StoreRequest` for the `#[actor]` generated types is a tuple-cons-list, for each actor in this list
302    /// convert its store requests into our nominal cons-list.
303    ///
304    /// This doesn't work with manual `Actor` implementations that have non-tuple-cons-list `StoreRequest`s.
305    type StoreRequests = Cons<
306        <<T as Actor<'a>>::StoreRequest as TupleConsToCons>::Cons,
307        <U as ActorList<'a>>::StoreRequests,
308    >;
309
310    /// For `Actor::InitContext` we just need to map directly to the associated type.
311    type InitContexts = Cons<<T as Actor<'a>>::InitContext, <U as ActorList<'a>>::InitContexts>;
312}
313
314/// Internal helper that for given sets of actors and slots validates the guarantees around slot access that we want to
315/// always uphold.
316///
317/// `init_contexts` is a cons-list of the init-context values for the actors in `A`. It is required to be passed here to
318/// drive type-inference for `A` but then just returned.
319///
320/// `_store` is the reference to the store the actors will use. A copy is passed in here as the lifetime of this
321/// reference may be required for the init-contexts inference.
322pub fn validate_actors<'a, A, S, I>(init_contexts: I, _store: Pin<&'a impl Datastore>) -> I
323where
324    A: ActorList<'a, InitContexts = I>,
325    S: IntoSlots,
326{
327    for (type_id, type_name) in S::Slots::all_slots() {
328        assert!(
329            A::StoreRequests::writers(type_id) > 0,
330            "missing writer for `{type_name}`",
331        );
332        assert!(
333            A::StoreRequests::readers(type_id) > 0,
334            "missing reader for `{type_name}`",
335        );
336        assert!(
337            A::StoreRequests::writers(type_id) == 1,
338            "multiple writers for `{type_name}`",
339        );
340        if A::StoreRequests::exclusive_readers(type_id) > 0 {
341            assert!(
342                A::StoreRequests::readers(type_id) == 1,
343                "conflict with exclusive reader for `{type_name}`",
344            );
345        }
346    }
347
348    init_contexts
349}
350
351/// Internal helper to get a full future that initializes and executes an [`Actor`] given a [`Datastore`]
352pub async fn execute_actor<'a, A>(
353    store: Pin<&'a impl Datastore>,
354    init_context: A::InitContext,
355) -> core::convert::Infallible
356where
357    A: Actor<'a>,
358{
359    veecle_telemetry::future::FutureExt::with_span(
360        async move {
361            match A::new(A::StoreRequest::request(store).await, init_context)
362                .run()
363                .await
364            {
365                Err(error) => panic!("{error}"),
366            }
367        },
368        veecle_telemetry::span!("actor", actor = core::any::type_name::<A>()),
369    )
370    .await
371}
372
373/// Execute a given set of actors without heap allocation.
374///
375/// ```rust
376/// use core::convert::Infallible;
377/// use core::fmt::Debug;
378///
379/// use veecle_os_runtime::{Reader, Storable, Writer};
380///
381/// #[derive(Debug, Clone, PartialEq, Eq, Default, Storable)]
382/// pub struct Ping {
383///     value: u32,
384/// }
385///
386/// #[derive(Debug, Clone, PartialEq, Eq, Default, Storable)]
387/// pub struct Pong {
388///     value: u32,
389/// }
390///
391/// #[veecle_os_runtime::actor]
392/// async fn ping_actor(mut ping: Writer<'_, Ping>, pong: Reader<'_, Pong>) -> Infallible {
393///     let mut value = 0;
394///     ping.write(Ping { value }).await;
395///
396///     let mut pong = pong.wait_init().await;
397///     loop {
398///         ping.write(Ping { value }).await;
399///         value += 1;
400///
401///         pong.wait_for_update().await.read(|pong| {
402///             println!("Pong: {}", pong.value);
403///         });
404/// #       // Exit the application to allow doc-tests to complete.
405/// #       std::process::exit(0);
406///     }
407/// }
408///
409/// #[veecle_os_runtime::actor]
410/// async fn pong_actor(mut pong: Writer<'_, Pong>, ping: Reader<'_, Ping>) -> Infallible {
411///     let mut ping = ping.wait_init().await;
412///     loop {
413///         let ping = ping.wait_for_update().await.read_cloned();
414///         println!("Ping: {}", ping.value);
415///
416///         let data = Pong { value: ping.value };
417///         pong.write(data).await;
418///     }
419/// }
420///
421/// futures::executor::block_on(
422///    veecle_os_runtime::execute! {
423///        store: [Ping, Pong],
424///        actors: [PingActor, PongActor],
425///    }
426/// )
427#[macro_export]
428macro_rules! execute {
429    (
430        store: [
431            $($data_type:ty),* $(,)?
432        ],
433        actors: [
434            $($actor_type:ty $(: $init_context:expr )? ),* $(,)?
435        ] $(,)?
436    ) => {{
437        async {
438            let store = core::pin::pin!(
439                $crate::__exports::make_store::<$crate::__make_cons!(@type $($data_type,)*)>(),
440            );
441            let store = store.as_ref();
442
443            let init_contexts = $crate::__exports::validate_actors::<
444                $crate::__make_cons!(@type $($actor_type,)*),
445                $crate::__make_cons!(@type $($data_type,)*),
446                _,
447            >($crate::__make_cons!(@value $(
448                // Wrapper block is used to provide a `()` if no expression is passed.
449                { $($init_context)? },
450            )*), store);
451
452            // To count how many actors there are, we create an array of `()` with the appropriate length.
453            const LEN: usize = [$($crate::discard_to_unit!($actor_type),)*].len();
454
455            let futures: [core::pin::Pin<&mut dyn core::future::Future<Output = core::convert::Infallible>>; LEN] =
456                $crate::make_futures! {
457                    init_contexts: init_contexts,
458                    store: store,
459                    actors: [$($actor_type,)*],
460                };
461
462            static SHARED: $crate::__exports::ExecutorShared<LEN>
463                = $crate::__exports::ExecutorShared::new(&SHARED);
464
465            let executor = $crate::__exports::Executor::new(
466                &SHARED,
467                $crate::__exports::Datastore::source(store),
468                futures,
469            );
470
471            executor.run().await
472        }
473    }};
474}
475
476/// Internal helper to construct an array of pinned futures for given actors + init-contexts + store.
477///
478/// Returns essentially `[Pin<&mut dyn Future<Output = Infallible>; actors.len()]`, but likely needs annotation at the
479/// use site to force the unsize coercion.
480#[doc(hidden)]
481#[macro_export]
482macro_rules! make_futures {
483    (
484        // A cons-list of init-contexts for the passed actors.
485        init_contexts: $init_contexts:expr,
486        store: $store:expr,
487        actors: [
488            $($types:ty,)*
489        ],
490    ) => {
491        $crate::make_futures! {
492            init_contexts: $init_contexts,
493            store: $store,
494            done: [],
495            todo: [$($types,)*],
496            futures: [],
497        }
498    };
499
500    // When there are no more actors, just return the futures as an array.
501    (
502        init_contexts: $init_contexts:expr,
503        store: $store:expr,
504        done: [$($done:ty,)*],
505        todo: [],
506        futures: [
507            $($futures:expr,)*
508        ],
509    ) => {
510        [$($futures,)*]
511    };
512
513    // For each actor, add an element to the futures array, using the already done actors as the depth to read from the
514    // init-contexts cons-list. Then push this actor onto the done list so that the next actor will read deeper from the
515    // init-contexts.
516    (
517        init_contexts: $init_contexts:expr,
518        store: $store:expr,
519        done: [$($done:ty,)*],
520        todo: [$current:ty, $($todo:ty,)*],
521        futures: [
522            $($futures:expr,)*
523        ],
524    ) => {
525        $crate::make_futures! {
526            init_contexts: $init_contexts,
527            store: $store,
528            done: [$($done,)* $current,],
529            todo: [$($todo,)*],
530            futures: [
531                $($futures,)*
532                core::pin::pin!(
533                    $crate::__exports::execute_actor::<$current>(
534                        $store,
535                        $crate::__read_cons! {
536                            from: $init_contexts,
537                            depth: [$($done)*],
538                        },
539                    )
540                ),
541            ],
542        }
543    };
544}
545
546#[doc(hidden)]
547#[macro_export]
548macro_rules! discard_to_unit {
549    ($_:tt) => {
550        ()
551    };
552}