1#![expect(
2 private_bounds,
3 reason = "
4 everything defined in here except the macro are internal helpers,
5 they often mention private types
6 "
7)]
8
9use crate::Never;
10use crate::actor::{Actor, Datastore, StoreRequest};
11use crate::cons::{Cons, Nil, TupleConsToCons};
12use crate::datastore::{
13 ExclusiveReader, InitializedReader, Reader, SlotTrait, Storable, Writer, generational,
14};
15use core::any::TypeId;
16use core::pin::Pin;
17
18trait Slots {
20 fn try_slot<S>(self: Pin<&Self>) -> Option<Pin<&S>>
23 where
24 S: SlotTrait;
25}
26
27impl Slots for Nil {
28 fn try_slot<S>(self: Pin<&Self>) -> Option<Pin<&S>>
29 where
30 S: SlotTrait,
31 {
32 None
33 }
34}
35
36impl<T> Slots for T
37where
38 T: SlotTrait + core::any::Any,
39{
40 fn try_slot<S>(self: Pin<&Self>) -> Option<Pin<&S>>
41 where
42 S: SlotTrait + core::any::Any,
43 {
44 if TypeId::of::<S>() == TypeId::of::<T>() {
45 Some(unsafe {
49 Pin::map_unchecked(self, |this| {
50 core::ptr::NonNull::from_ref(this).cast::<S>().as_ref()
51 })
52 })
53 } else {
54 None
55 }
56 }
57}
58
59impl<U, R> Slots for Cons<U, R>
60where
61 U: Slots,
62 R: Slots,
63{
64 fn try_slot<S>(self: Pin<&Self>) -> Option<Pin<&S>>
65 where
66 S: SlotTrait,
67 {
68 let this = self.project_ref();
69
70 this.0.try_slot::<S>().or_else(|| this.1.try_slot::<S>())
71 }
72}
73
74trait IntoSlots {
76 type Slots: Slots;
78
79 fn make_slots() -> Self::Slots;
81
82 fn validate_all<'a, A>()
84 where
85 A: ActorList<'a>;
86}
87
88impl IntoSlots for Nil {
89 type Slots = Nil;
90
91 fn make_slots() -> Self::Slots {
92 Nil
93 }
94
95 fn validate_all<'a, A>()
96 where
97 A: ActorList<'a>,
98 {
99 }
100}
101
102impl<S> IntoSlots for S
103where
104 S: SlotTrait + 'static,
105{
106 type Slots = S;
107
108 fn make_slots() -> Self::Slots {
109 S::new()
110 }
111
112 fn validate_all<'a, A>()
113 where
114 A: ActorList<'a>,
115 {
116 let type_id = S::data_type_id();
117
118 S::validate_access_pattern(
119 (A::writers_count(type_id), A::writers(type_id)),
120 (
121 A::exclusive_readers_count(type_id),
122 A::exclusive_readers(type_id),
123 ),
124 (
125 A::non_exclusive_readers_count(type_id),
126 A::non_exclusive_readers(type_id),
127 ),
128 );
129 }
130}
131
132impl<S, R> IntoSlots for Cons<S, R>
133where
134 S: IntoSlots,
135 R: IntoSlots,
136{
137 type Slots = Cons<S::Slots, R::Slots>;
138
139 fn make_slots() -> Self::Slots {
140 Cons(S::make_slots(), R::make_slots())
141 }
142
143 fn validate_all<'a, A>()
144 where
145 A: ActorList<'a>,
146 {
147 S::validate_all::<'a, A>();
148 R::validate_all::<'a, A>();
149 }
150}
151
152#[allow(
153 rustdoc::private_intra_doc_links,
154 reason = "
155 This lint is hit when documenting with `--document-private-items`.
156 If we use `expect`, a warning is emitted when not using `--document-private-items`.
157 If we remove the lint, a warning is emitted when using `--document-private-items`.
158 To be able to deny warning, we need to allow the lint here.
159 https://github.com/rust-lang/rust/issues/145449
160 "
161)]
162impl<S: Slots> Datastore for Cons<generational::Source, S>
164where
165 S: Slots,
166{
167 fn source(self: Pin<&Self>) -> Pin<&generational::Source> {
168 let this = self.project_ref();
169 this.0
170 }
171
172 fn slot<T>(self: Pin<&Self>, requestor: &'static str) -> Pin<&T>
173 where
174 T: SlotTrait,
175 {
176 let this = self.project_ref();
177 this.1.try_slot::<T>().unwrap_or_else(|| {
178 panic!(
179 "no slot available for `{}`, required by `{requestor}`",
180 T::data_type_name()
181 )
182 })
183 }
184}
185
186pub(crate) fn make_store<T>() -> impl Datastore
188where
189 T: IntoSlots,
190{
191 Cons(generational::Source::new(), T::make_slots())
192}
193
194pub trait AccessKind {
196 fn writer(_type_id: TypeId) -> bool {
198 false
199 }
200
201 fn reader(_type_id: TypeId) -> bool {
203 false
204 }
205
206 fn exclusive_reader(_type_id: TypeId) -> bool {
208 false
209 }
210}
211
212impl<T> AccessKind for Writer<'_, T>
213where
214 T: Storable + 'static,
215{
216 fn writer(type_id: TypeId) -> bool {
217 type_id == TypeId::of::<T>()
218 }
219}
220
221impl<T> AccessKind for Reader<'_, T>
222where
223 T: Storable + 'static,
224{
225 fn reader(type_id: TypeId) -> bool {
226 type_id == TypeId::of::<T>()
227 }
228}
229
230impl<T> AccessKind for InitializedReader<'_, T>
231where
232 T: Storable + 'static,
233{
234 fn reader(type_id: TypeId) -> bool {
235 type_id == TypeId::of::<T>()
236 }
237}
238
239impl<T> AccessKind for ExclusiveReader<'_, T>
240where
241 T: Storable + 'static,
242{
243 fn reader(type_id: TypeId) -> bool {
244 type_id == TypeId::of::<T>()
245 }
246
247 fn exclusive_reader(type_id: TypeId) -> bool {
248 type_id == TypeId::of::<T>()
249 }
250}
251
252pub trait AccessCount {
254 fn writers(type_id: TypeId) -> usize;
256
257 fn readers(type_id: TypeId) -> usize;
259
260 fn exclusive_readers(type_id: TypeId) -> usize;
262}
263
264impl AccessCount for Nil {
265 fn writers(_type_id: TypeId) -> usize {
266 0
267 }
268
269 fn readers(_type_id: TypeId) -> usize {
270 0
271 }
272
273 fn exclusive_readers(_type_id: TypeId) -> usize {
274 0
275 }
276}
277
278impl<T, U> AccessCount for Cons<T, U>
279where
280 T: AccessKind,
281 U: AccessCount,
282{
283 fn writers(type_id: TypeId) -> usize {
284 (if T::writer(type_id) { 1 } else { 0 }) + U::writers(type_id)
285 }
286
287 fn readers(type_id: TypeId) -> usize {
288 (if T::reader(type_id) { 1 } else { 0 }) + U::readers(type_id)
289 }
290
291 fn exclusive_readers(type_id: TypeId) -> usize {
292 (if T::exclusive_reader(type_id) { 1 } else { 0 }) + U::exclusive_readers(type_id)
293 }
294}
295
296pub(crate) trait ActorList<'a>
298where
299 Self: 'a,
300{
301 type InitContexts;
303
304 type AllSlots;
306
307 fn writers_count(type_id: TypeId) -> usize;
309
310 fn exclusive_readers_count(type_id: TypeId) -> usize;
312
313 fn non_exclusive_readers_count(type_id: TypeId) -> usize;
315
316 fn writers(type_id: TypeId) -> impl Iterator<Item = &'static str>;
320
321 fn exclusive_readers(type_id: TypeId) -> impl Iterator<Item = &'static str>;
327
328 fn non_exclusive_readers(type_id: TypeId) -> impl Iterator<Item = &'static str>;
334}
335
336impl ActorList<'_> for Nil {
337 type InitContexts = Nil;
338 type AllSlots = Nil;
339
340 fn writers_count(_type_id: TypeId) -> usize {
341 0
342 }
343
344 fn exclusive_readers_count(_type_id: TypeId) -> usize {
345 0
346 }
347
348 fn non_exclusive_readers_count(_type_id: TypeId) -> usize {
349 0
350 }
351
352 fn writers(_type_id: TypeId) -> impl Iterator<Item = &'static str> {
353 core::iter::empty()
354 }
355
356 fn exclusive_readers(_type_id: TypeId) -> impl Iterator<Item = &'static str> {
357 core::iter::empty()
358 }
359
360 fn non_exclusive_readers(_type_id: TypeId) -> impl Iterator<Item = &'static str> {
361 core::iter::empty()
362 }
363}
364
365impl<'a, T, U> ActorList<'a> for Cons<T, U>
366where
367 T: Actor<'a, StoreRequest: TupleConsToCons> + 'a,
368 U: ActorList<'a> + 'a,
369 <<T as Actor<'a>>::StoreRequest as TupleConsToCons>::Cons: AccessCount,
370{
371 type InitContexts = Cons<<T as Actor<'a>>::InitContext, <U as ActorList<'a>>::InitContexts>;
373
374 type AllSlots = Cons<<T as Actor<'a>>::Slots, <U as ActorList<'a>>::AllSlots>;
376
377 fn writers_count(type_id: TypeId) -> usize {
378 <T::StoreRequest as TupleConsToCons>::Cons::writers(type_id) + U::writers_count(type_id)
379 }
380
381 fn exclusive_readers_count(type_id: TypeId) -> usize {
382 <T::StoreRequest as TupleConsToCons>::Cons::exclusive_readers(type_id)
383 + U::exclusive_readers_count(type_id)
384 }
385
386 fn non_exclusive_readers_count(type_id: TypeId) -> usize {
387 (<T::StoreRequest as TupleConsToCons>::Cons::readers(type_id)
388 - <T::StoreRequest as TupleConsToCons>::Cons::exclusive_readers(type_id))
389 + U::non_exclusive_readers_count(type_id)
390 }
391
392 fn writers(type_id: TypeId) -> impl Iterator<Item = &'static str> {
393 core::iter::repeat_n(
394 core::any::type_name::<T>(),
395 <T::StoreRequest as TupleConsToCons>::Cons::writers(type_id),
396 )
397 .chain(U::writers(type_id))
398 }
399
400 fn exclusive_readers(type_id: TypeId) -> impl Iterator<Item = &'static str> {
401 core::iter::repeat_n(
402 core::any::type_name::<T>(),
403 <T::StoreRequest as TupleConsToCons>::Cons::exclusive_readers(type_id),
404 )
405 .chain(U::exclusive_readers(type_id))
406 }
407
408 fn non_exclusive_readers(type_id: TypeId) -> impl Iterator<Item = &'static str> {
409 core::iter::repeat_n(
410 core::any::type_name::<T>(),
411 <T::StoreRequest as TupleConsToCons>::Cons::readers(type_id)
412 - <T::StoreRequest as TupleConsToCons>::Cons::exclusive_readers(type_id),
413 )
414 .chain(U::non_exclusive_readers(type_id))
415 }
416}
417
418pub fn make_store_and_validate<'a, A, I>(init_contexts: I) -> (impl Datastore + 'a, I)
425where
426 A: ActorList<'a, InitContexts = I>,
427 A::AllSlots: IntoSlots,
428{
429 let store = make_store::<A::AllSlots>();
430
431 A::AllSlots::validate_all::<'a, A>();
432
433 (store, init_contexts)
434}
435
436pub async fn execute_actor<'a, A>(
438 store: Pin<&'a impl Datastore>,
439 init_context: A::InitContext,
440) -> Never
441where
442 A: Actor<'a>,
443{
444 let requestor = core::any::type_name::<A>();
445 veecle_telemetry::future::FutureExt::with_span(
446 async move {
447 match A::new(
448 A::StoreRequest::request(store, requestor).await,
449 init_context,
450 )
451 .run()
452 .await
453 {
454 Err(error) => panic!("{error}"),
455 }
456 },
457 veecle_telemetry::span!("actor", actor = core::any::type_name::<A>()),
458 )
459 .await
460}
461
462#[macro_export]
515macro_rules! execute {
516 (
517 actors: [
518 $($actor_type:ty $(: $init_context:expr )? ),* $(,)?
519 ] $(,)?
520 ) => {{
521 async {
522 let (store, init_contexts) = {
523 let (store, init_contexts) = $crate::__exports::make_store_and_validate::<
524 $crate::__make_cons!(@type $($actor_type,)*),
525 _,
526 >($crate::__make_cons!(@value $(
527 { $($init_context)? },
529 )*));
530 (core::pin::pin!(store), init_contexts)
531 };
532
533 let store = store.as_ref();
534
535 const LEN: usize = [$($crate::discard_to_unit!($actor_type),)*].len();
537
538 let futures: [core::pin::Pin<&mut dyn core::future::Future<Output = $crate::Never>>; LEN] =
539 $crate::make_futures! {
540 init_contexts: init_contexts,
541 store: store,
542 actors: [$($actor_type,)*],
543 };
544
545 static SHARED: $crate::__exports::ExecutorShared<LEN>
546 = $crate::__exports::ExecutorShared::new(&SHARED);
547
548 let executor = $crate::__exports::Executor::new(
549 &SHARED,
550 $crate::__exports::Datastore::source(store),
551 futures,
552 );
553
554 executor.run().await
555 }
556 }};
557}
558
559#[doc(hidden)]
564#[macro_export]
565macro_rules! make_futures {
566 (
567 init_contexts: $init_contexts:expr,
569 store: $store:expr,
570 actors: [
571 $($types:ty,)*
572 ],
573 ) => {
574 $crate::make_futures! {
575 init_contexts: $init_contexts,
576 store: $store,
577 done: [],
578 todo: [$($types,)*],
579 futures: [],
580 }
581 };
582
583 (
585 init_contexts: $init_contexts:expr,
586 store: $store:expr,
587 done: [$($done:ty,)*],
588 todo: [],
589 futures: [
590 $($futures:expr,)*
591 ],
592 ) => {
593 [$($futures,)*]
594 };
595
596 (
600 init_contexts: $init_contexts:expr,
601 store: $store:expr,
602 done: [$($done:ty,)*],
603 todo: [$current:ty, $($todo:ty,)*],
604 futures: [
605 $($futures:expr,)*
606 ],
607 ) => {
608 $crate::make_futures! {
609 init_contexts: $init_contexts,
610 store: $store,
611 done: [$($done,)* $current,],
612 todo: [$($todo,)*],
613 futures: [
614 $($futures,)*
615 core::pin::pin!(
616 $crate::__exports::execute_actor::<$current>(
617 $store,
618 $crate::__read_cons! {
619 from: $init_contexts,
620 depth: [$($done)*],
621 },
622 )
623 ),
624 ],
625 }
626 };
627}
628
629#[doc(hidden)]
630#[macro_export]
631macro_rules! discard_to_unit {
632 ($_:tt) => {
633 ()
634 };
635}
636
637#[cfg(test)]
638#[cfg_attr(coverage_nightly, coverage(off))]
639mod tests {
640 use core::marker::PhantomData;
641 use core::pin::pin;
642
643 use crate::actor::Datastore;
644 use crate::cons::Cons;
645 use crate::cons::Nil;
646 use crate::datastore::Slot;
647 use crate::execute::generational::Source;
648
649 #[test]
650 #[should_panic(
651 expected = "no slot available for `veecle_os_runtime::execute::tests::nil_slot_panics_with_correct_message::TestType`, required by `test_requestor`"
652 )]
653 fn nil_slot_panics_with_correct_message() {
654 #[derive(Debug, crate::datastore::Storable)]
655 #[storable(crate = crate)]
656 struct TestType;
657
658 let nil = pin!(Cons(Source::new(), Nil));
659 let _slot: core::pin::Pin<&Slot<TestType>> =
660 Datastore::slot(nil.as_ref(), "test_requestor");
661 }
662
663 #[test]
664 #[should_panic(expected = "type inference works")]
665 fn type_inference_for_generic_actors() {
666 use crate::{Actor, Never};
667
668 struct GenericActor<T> {
669 _phantom: PhantomData<T>,
670 }
671
672 impl<'a, T> Actor<'a> for GenericActor<T>
673 where
674 T: core::fmt::Debug + 'static,
675 {
676 type StoreRequest = ();
677 type InitContext = T;
678 type Slots = Nil;
679 type Error = Never;
680
681 fn new((): Self::StoreRequest, _context: Self::InitContext) -> Self {
682 Self {
683 _phantom: PhantomData,
684 }
685 }
686
687 async fn run(self) -> Result<Never, Self::Error> {
688 panic!("type inference works");
689 }
690 }
691
692 futures::executor::block_on(crate::execute! {
693 actors: [
694 GenericActor<_>: 42_i32,
695 ],
696 });
697 }
698}