#![expect(private_bounds)]
#![expect(private_interfaces)]
use crate::actor::{Actor, Datastore, StoreRequest};
use crate::cons::{Cons, Nil, TupleConsToCons};
use crate::datastore::{
ExclusiveReader, InitializedReader, Reader, Slot, Storable, Writer, generational,
};
use core::any::TypeId;
use core::pin::Pin;
trait Slots {
fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
where
T: Storable + 'static;
fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)>;
}
impl Slots for Nil {
fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
where
T: Storable + 'static,
{
panic!("no slot available for `{}`", core::any::type_name::<T>())
}
fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)> {
core::iter::empty()
}
}
impl<U, R> Slots for Cons<Slot<U>, R>
where
U: Storable + 'static,
R: Slots,
{
fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
where
T: Storable + 'static,
{
let this = self.project_ref();
if TypeId::of::<U>() == TypeId::of::<T>() {
this.0.assert_is_type()
} else {
this.1.slot::<T>()
}
}
fn all_slots() -> impl Iterator<Item = (TypeId, &'static str)> {
R::all_slots().chain(core::iter::once((
TypeId::of::<U>(),
core::any::type_name::<U>(),
)))
}
}
trait IntoSlots {
type Slots: Slots;
fn make_slots() -> Self::Slots;
}
impl IntoSlots for Nil {
type Slots = Nil;
fn make_slots() -> Self::Slots {
Nil
}
}
impl<T, R> IntoSlots for Cons<T, R>
where
T: Storable + 'static,
R: IntoSlots,
{
type Slots = Cons<Slot<T>, R::Slots>;
fn make_slots() -> Self::Slots {
Cons(Slot::<T>::new(), R::make_slots())
}
}
#[allow(rustdoc::private_intra_doc_links)]
impl<S: Slots> Datastore for Cons<generational::Source, S>
where
S: Slots,
{
fn source(self: Pin<&Self>) -> Pin<&generational::Source> {
let this = self.project_ref();
this.0
}
fn slot<T>(self: Pin<&Self>) -> Pin<&Slot<T>>
where
T: Storable + 'static,
{
let this = self.project_ref();
this.1.slot::<T>()
}
}
pub fn make_store<T>() -> impl Datastore
where
T: IntoSlots,
{
Cons(generational::Source::new(), T::make_slots())
}
pub trait AccessKind {
fn writer(_type_id: TypeId) -> bool {
false
}
fn reader(_type_id: TypeId) -> bool {
false
}
fn exclusive_reader(_type_id: TypeId) -> bool {
false
}
}
impl<T> AccessKind for Writer<'_, T>
where
T: Storable + 'static,
{
fn writer(type_id: TypeId) -> bool {
type_id == TypeId::of::<T>()
}
}
impl<T> AccessKind for Reader<'_, T>
where
T: Storable + 'static,
{
fn reader(type_id: TypeId) -> bool {
type_id == TypeId::of::<T>()
}
}
impl<T> AccessKind for InitializedReader<'_, T>
where
T: Storable + 'static,
{
fn reader(type_id: TypeId) -> bool {
type_id == TypeId::of::<T>()
}
}
impl<T> AccessKind for ExclusiveReader<'_, T>
where
T: Storable + 'static,
{
fn reader(type_id: TypeId) -> bool {
type_id == TypeId::of::<T>()
}
fn exclusive_reader(type_id: TypeId) -> bool {
type_id == TypeId::of::<T>()
}
}
pub trait AccessCount {
fn writers(type_id: TypeId) -> usize;
fn readers(type_id: TypeId) -> usize;
fn exclusive_readers(type_id: TypeId) -> usize;
}
impl AccessCount for Nil {
fn writers(_type_id: TypeId) -> usize {
0
}
fn readers(_type_id: TypeId) -> usize {
0
}
fn exclusive_readers(_type_id: TypeId) -> usize {
0
}
}
impl<T, U> AccessCount for Cons<T, U>
where
T: AccessKind,
U: AccessCount,
{
fn writers(type_id: TypeId) -> usize {
(if T::writer(type_id) { 1 } else { 0 }) + U::writers(type_id)
}
fn readers(type_id: TypeId) -> usize {
(if T::reader(type_id) { 1 } else { 0 }) + U::readers(type_id)
}
fn exclusive_readers(type_id: TypeId) -> usize {
(if T::exclusive_reader(type_id) { 1 } else { 0 }) + U::exclusive_readers(type_id)
}
}
pub trait NestedAccessCount {
fn writers(type_id: TypeId) -> usize;
fn readers(type_id: TypeId) -> usize;
fn exclusive_readers(type_id: TypeId) -> usize;
}
impl NestedAccessCount for Nil {
fn writers(_type_id: TypeId) -> usize {
0
}
fn readers(_type_id: TypeId) -> usize {
0
}
fn exclusive_readers(_type_id: TypeId) -> usize {
0
}
}
impl<T, U> NestedAccessCount for Cons<T, U>
where
T: AccessCount,
U: NestedAccessCount,
{
fn writers(type_id: TypeId) -> usize {
T::writers(type_id) + U::writers(type_id)
}
fn readers(type_id: TypeId) -> usize {
T::readers(type_id) + U::readers(type_id)
}
fn exclusive_readers(type_id: TypeId) -> usize {
T::exclusive_readers(type_id) + U::exclusive_readers(type_id)
}
}
pub trait ActorList<'a> {
type StoreRequests: NestedAccessCount;
type InitContexts;
}
impl ActorList<'_> for Nil {
type StoreRequests = Nil;
type InitContexts = Nil;
}
impl<'a, T, U> ActorList<'a> for Cons<T, U>
where
T: Actor<'a, StoreRequest: TupleConsToCons>,
U: ActorList<'a>,
<<T as Actor<'a>>::StoreRequest as TupleConsToCons>::Cons: AccessCount,
{
type StoreRequests = Cons<
<<T as Actor<'a>>::StoreRequest as TupleConsToCons>::Cons,
<U as ActorList<'a>>::StoreRequests,
>;
type InitContexts = Cons<<T as Actor<'a>>::InitContext, <U as ActorList<'a>>::InitContexts>;
}
pub fn validate_actors<'a, A, S, I>(init_contexts: I, _store: Pin<&'a impl Datastore>) -> I
where
A: ActorList<'a, InitContexts = I>,
S: IntoSlots,
{
for (type_id, type_name) in S::Slots::all_slots() {
assert!(
A::StoreRequests::writers(type_id) > 0,
"missing writer for `{type_name}`",
);
assert!(
A::StoreRequests::readers(type_id) > 0,
"missing reader for `{type_name}`",
);
assert!(
A::StoreRequests::writers(type_id) == 1,
"multiple writers for `{type_name}`",
);
if A::StoreRequests::exclusive_readers(type_id) > 0 {
assert!(
A::StoreRequests::readers(type_id) == 1,
"conflict with exclusive reader for `{type_name}`",
);
}
}
init_contexts
}
pub async fn execute_actor<'a, A>(
store: Pin<&'a impl Datastore>,
init_context: A::InitContext,
) -> core::convert::Infallible
where
A: Actor<'a>,
{
let future = A::new(A::StoreRequest::request(store).await, init_context).run();
#[cfg(feature = "veecle-telemetry")]
let future = veecle_telemetry::future::FutureExt::with_span(
future,
veecle_telemetry::root_span!("actor", actor = core::any::type_name::<A>()),
);
match future.await {
Err(error) => panic!("{error}"),
}
}
#[macro_export]
macro_rules! execute {
(
store: [
$($data_type:ty),* $(,)?
],
actors: [
$($actor_type:ty $(: $init_context:expr )? ),* $(,)?
] $(,)?
) => {{
async {
let store = core::pin::pin!(
$crate::__exports::make_store::<$crate::__make_cons!(@type $($data_type,)*)>(),
);
let store = store.as_ref();
let init_contexts = $crate::__exports::validate_actors::<
$crate::__make_cons!(@type $($actor_type,)*),
$crate::__make_cons!(@type $($data_type,)*),
_,
>($crate::__make_cons!(@value $(
// Wrapper block is used to provide a `()` if no expression is passed.
{ $($init_context)? },
)*), store);
const LEN: usize = [$($crate::discard_to_unit!($actor_type),)*].len();
let futures: [core::pin::Pin<&mut dyn core::future::Future<Output = core::convert::Infallible>>; LEN] =
$crate::make_futures! {
init_contexts: init_contexts,
store: store,
actors: [$($actor_type,)*],
};
static SHARED: $crate::__exports::ExecutorShared<LEN>
= $crate::__exports::ExecutorShared::new(&SHARED);
let executor = $crate::__exports::Executor::new(
&SHARED,
$crate::__exports::Datastore::source(store),
futures,
);
executor.run().await
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! make_futures {
(
// A cons-list of init-contexts for the passed actors.
init_contexts: $init_contexts:expr,
store: $store:expr,
actors: [
$($types:ty,)*
],
) => {
$crate::make_futures! {
init_contexts: $init_contexts,
store: $store,
done: [],
todo: [$($types,)*],
futures: [],
}
};
(
init_contexts: $init_contexts:expr,
store: $store:expr,
done: [$($done:ty,)*],
todo: [],
futures: [
$($futures:expr,)*
],
) => {
[$($futures,)*]
};
(
init_contexts: $init_contexts:expr,
store: $store:expr,
done: [$($done:ty,)*],
todo: [$current:ty, $($todo:ty,)*],
futures: [
$($futures:expr,)*
],
) => {
$crate::make_futures! {
init_contexts: $init_contexts,
store: $store,
done: [$($done,)* $current,],
todo: [$($todo,)*],
futures: [
$($futures,)*
core::pin::pin!(
$crate::__exports::execute_actor::<$current>(
$store,
$crate::__read_cons! {
from: $init_contexts,
depth: [$($done)*],
},
)
),
],
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! discard_to_unit {
($_:tt) => {
()
};
}