|  | @@ -1,12 +1,14 @@
 | 
	
		
			
				|  |  | -use std::collections::HashMap;
 | 
	
		
			
				|  |  | +use std::collections::{HashMap, HashSet};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +use btrun::model::End;
 | 
	
		
			
				|  |  |  use proc_macro2::{Ident, TokenStream};
 | 
	
		
			
				|  |  |  use quote::{format_ident, quote, ToTokens};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  use crate::{
 | 
	
		
			
				|  |  |      case_convert::CaseConvert,
 | 
	
		
			
				|  |  |      model::{
 | 
	
		
			
				|  |  | -        ActorKind, ActorModel, MethodModel, MsgInfo, OutputKind, ProtocolModel, TypeParamInfo,
 | 
	
		
			
				|  |  | +        ActorKind, ActorModel, MethodModel, MsgInfo, ProtocolModel, StateModel, TypeParamInfo,
 | 
	
		
			
				|  |  | +        ValueKind, ValueModel,
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -30,22 +32,6 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          let all_replies = msg_lookup.msg_iter().all(|msg| msg.is_reply());
 | 
	
		
			
				|  |  |          let enum_name = self.msg_enum_ident();
 | 
	
		
			
				|  |  |          let enum_kinds_name = self.msg_enum_kinds_ident();
 | 
	
		
			
				|  |  | -        let name_decl_vec: Vec<(Ident, TokenStream)> = variants
 | 
	
		
			
				|  |  | -            .iter()
 | 
	
		
			
				|  |  | -            .map(|variant| {
 | 
	
		
			
				|  |  | -                let variant_str = variant_names_map.get(variant.as_ref()).unwrap();
 | 
	
		
			
				|  |  | -                let name_ident =
 | 
	
		
			
				|  |  | -                    format_ident!("{}_NAME", variant_str.pascal_to_snake().to_uppercase());
 | 
	
		
			
				|  |  | -                let decl = quote! {
 | 
	
		
			
				|  |  | -                    static #name_ident: Lazy<Arc<String>> = Lazy::new(|| {
 | 
	
		
			
				|  |  | -                        Arc::new(#variant_str.into())
 | 
	
		
			
				|  |  | -                    });
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | -                (name_ident, decl)
 | 
	
		
			
				|  |  | -            })
 | 
	
		
			
				|  |  | -            .collect();
 | 
	
		
			
				|  |  | -        let name_decls = name_decl_vec.iter().map(|(_, decl)| decl);
 | 
	
		
			
				|  |  | -        let name_idents = name_decl_vec.iter().map(|(ident, _)| ident);
 | 
	
		
			
				|  |  |          let send_impl = if all_replies {
 | 
	
		
			
				|  |  |              quote! {}
 | 
	
		
			
				|  |  |          } else {
 | 
	
	
		
			
				|  | @@ -55,6 +41,7 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |          let proto_name = &self.def().name_def.name;
 | 
	
		
			
				|  |  |          let doc_comment = format!("Message type for the {proto_name} protocol.");
 | 
	
		
			
				|  |  | +        let name_method = self.generate_name_method(|| variant_names_map.keys().copied());
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  |              #[doc = #doc_comment]
 | 
	
		
			
				|  |  |              #[derive(::serde::Serialize, ::serde::Deserialize)]
 | 
	
	
		
			
				|  | @@ -70,14 +57,7 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              impl ::btrun::model::Named for #enum_kinds_name {
 | 
	
		
			
				|  |  | -                fn name(&self) -> ::std::sync::Arc<String> {
 | 
	
		
			
				|  |  | -                    use ::btrun::model::Lazy;
 | 
	
		
			
				|  |  | -                    use ::std::sync::Arc;
 | 
	
		
			
				|  |  | -                    #( #name_decls )*
 | 
	
		
			
				|  |  | -                    match self {
 | 
	
		
			
				|  |  | -                        #( Self::#variants => #name_idents.clone() ),*
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +                #name_method
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              impl Copy for #enum_kinds_name {}
 | 
	
	
		
			
				|  | @@ -110,7 +90,7 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          });
 | 
	
		
			
				|  |  |          let mut tokens = TokenStream::new();
 | 
	
		
			
				|  |  |          for (trait_ident, methods, is_init_state) in traits {
 | 
	
		
			
				|  |  | -            let method_tokens = methods.map(|x| x.generate_tokens());
 | 
	
		
			
				|  |  | +            let method_tokens = methods.map(|x| x.generate_trait_def());
 | 
	
		
			
				|  |  |              let actor_impl_method = if is_init_state {
 | 
	
		
			
				|  |  |                  quote! {
 | 
	
		
			
				|  |  |                      #[doc = "The name of the implementation for the actor this state is a part of."]
 | 
	
	
		
			
				|  | @@ -153,12 +133,12 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          match actor.kind() {
 | 
	
		
			
				|  |  |              ActorKind::Service => self.generate_service_spawn_function(actor),
 | 
	
		
			
				|  |  |              ActorKind::Worker => self.generate_worker_spawn_function(actor),
 | 
	
		
			
				|  |  | -            ActorKind::Client => TokenStream::default(),
 | 
	
		
			
				|  |  | +            ActorKind::Client => self.generate_client_spawn_function(actor),
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      fn generate_worker_spawn_function(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | -        let function_name = actor.spawn_function_ident();
 | 
	
		
			
				|  |  | +        let worker_spawn_function_name = actor.spawn_function_ident();
 | 
	
		
			
				|  |  |          let TypeParamInfo {
 | 
	
		
			
				|  |  |              type_params,
 | 
	
		
			
				|  |  |              constraints,
 | 
	
	
		
			
				|  | @@ -166,9 +146,9 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          let init_state_type_param = actor.init_state().type_param();
 | 
	
		
			
				|  |  |          let state_enum_decl = self.generate_state_enum(actor);
 | 
	
		
			
				|  |  |          let init_state_var = self.init_state_var();
 | 
	
		
			
				|  |  | -        let server_loop = self.generate_loop(actor);
 | 
	
		
			
				|  |  | +        let server_loop = self.generate_actor_loop(actor);
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  | -            async fn #function_name<#( #type_params ),*>(
 | 
	
		
			
				|  |  | +            async fn #worker_spawn_function_name<#( #type_params ),*>(
 | 
	
		
			
				|  |  |                  runtime: &'static ::btrun::Runtime,
 | 
	
		
			
				|  |  |                  owner_name: ::btrun::model::ActorName,
 | 
	
		
			
				|  |  |                  #init_state_var: #init_state_type_param,
 | 
	
	
		
			
				|  | @@ -185,7 +165,7 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      fn generate_service_spawn_function(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | -        let function_name = format_ident!("register_{}", actor.def().actor.as_ref());
 | 
	
		
			
				|  |  | +        let service_spawn_function_name = format_ident!("register_{}", actor.def().actor.as_ref());
 | 
	
		
			
				|  |  |          let TypeParamInfo {
 | 
	
		
			
				|  |  |              type_params,
 | 
	
		
			
				|  |  |              constraints,
 | 
	
	
		
			
				|  | @@ -194,9 +174,9 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          let state_enum_decl = self.generate_state_enum(actor);
 | 
	
		
			
				|  |  |          let msg_enum = self.msg_enum_ident();
 | 
	
		
			
				|  |  |          let init_state_var = self.init_state_var();
 | 
	
		
			
				|  |  | -        let server_loop = self.generate_loop(actor);
 | 
	
		
			
				|  |  | +        let server_loop = self.generate_actor_loop(actor);
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  | -            async fn #function_name<#( #type_params ),*, F>(
 | 
	
		
			
				|  |  | +            async fn #service_spawn_function_name<#( #type_params ),*, F>(
 | 
	
		
			
				|  |  |                  runtime: &'static ::btrun::Runtime,
 | 
	
		
			
				|  |  |                  service_id: ::btrun::model::ServiceId,
 | 
	
		
			
				|  |  |                  make_init: F,
 | 
	
	
		
			
				|  | @@ -225,13 +205,91 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    fn generate_loop(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | +    fn generate_client_spawn_function(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | +        let init_state_model = actor.init_state();
 | 
	
		
			
				|  |  | +        let init_state = init_state_model.name();
 | 
	
		
			
				|  |  | +        let init_type_param = init_state_model.type_param();
 | 
	
		
			
				|  |  | +        let state_enum = self.generate_client_state_enum(actor);
 | 
	
		
			
				|  |  | +        let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  | +        let handle_struct = self.generate_client_handle(actor);
 | 
	
		
			
				|  |  | +        let handle_struct_ident = actor.handle_struct_ident().unwrap();
 | 
	
		
			
				|  |  | +        let client_spawn_function_name = actor.spawn_function_ident().unwrap();
 | 
	
		
			
				|  |  | +        let TypeParamInfo {
 | 
	
		
			
				|  |  | +            type_params,
 | 
	
		
			
				|  |  | +            constraints,
 | 
	
		
			
				|  |  | +        } = self.type_param_info_for(actor.name());
 | 
	
		
			
				|  |  | +        let init_var = self.init_state_var();
 | 
	
		
			
				|  |  | +        let use_statements = self.use_statements();
 | 
	
		
			
				|  |  | +        let actor_id_param = self.actor_id_param();
 | 
	
		
			
				|  |  | +        let runtime_var = self.runtime_param();
 | 
	
		
			
				|  |  | +        let state_var = self.state_var();
 | 
	
		
			
				|  |  | +        let actor_name_ident = self.actor_name_ident();
 | 
	
		
			
				|  |  | +        let msg_enum = self.msg_enum_ident();
 | 
	
		
			
				|  |  | +        let msg = self.msg_ident();
 | 
	
		
			
				|  |  | +        let from = self.from_ident();
 | 
	
		
			
				|  |  | +        let reply = self.reply_ident();
 | 
	
		
			
				|  |  | +        let call_transitions = self.generate_call_transitions(actor);
 | 
	
		
			
				|  |  | +        let send_transitions = self.generate_send_transitions(actor);
 | 
	
		
			
				|  |  | +        let control_transitions = self.generate_control_transitions(actor);
 | 
	
		
			
				|  |  | +        let end_ident = self.end_ident();
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            #state_enum
 | 
	
		
			
				|  |  | +            #handle_struct
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            async fn #client_spawn_function_name<#( #type_params ),* >(
 | 
	
		
			
				|  |  | +                #init_var: #init_type_param,
 | 
	
		
			
				|  |  | +                runtime: &'static ::btrun::Runtime,
 | 
	
		
			
				|  |  | +            ) -> #handle_struct_ident<#init_type_param, #init_type_param>
 | 
	
		
			
				|  |  | +            where
 | 
	
		
			
				|  |  | +                #( #constraints ),*
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                #use_statements
 | 
	
		
			
				|  |  | +                let shared_state = Arc::new(Mutex::new(Some(#state_enum_ident::#init_state(#init_var))));
 | 
	
		
			
				|  |  | +                let name = {
 | 
	
		
			
				|  |  | +                    let shared_state = shared_state.clone();
 | 
	
		
			
				|  |  | +                    runtime.spawn(None, move |mut mailbox: Mailbox<#msg_enum>, #actor_id_param, #runtime_var| async move {
 | 
	
		
			
				|  |  | +                        let #actor_name_ident = runtime.actor_name(#actor_id_param);
 | 
	
		
			
				|  |  | +                        while let Some(envelope) = mailbox.recv().await {
 | 
	
		
			
				|  |  | +                            let mut guard = shared_state.lock().await;
 | 
	
		
			
				|  |  | +                            let #state_var = guard.take()
 | 
	
		
			
				|  |  | +                                .unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +                                    panic!(
 | 
	
		
			
				|  |  | +                                        "Logic error. The shared state for client {} was not returned.",
 | 
	
		
			
				|  |  | +                                        #init_type_param::actor_impl()
 | 
	
		
			
				|  |  | +                                    )
 | 
	
		
			
				|  |  | +                                });
 | 
	
		
			
				|  |  | +                            let new_state = match envelope {
 | 
	
		
			
				|  |  | +                                Envelope::Call { from: #from, msg: #msg, reply: #reply, .. } => {
 | 
	
		
			
				|  |  | +                                    #call_transitions
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  | +                                Envelope::Send { from: #from, msg: #msg, .. } => {
 | 
	
		
			
				|  |  | +                                    #send_transitions
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  | +                                Envelope::Control(#msg) => {
 | 
	
		
			
				|  |  | +                                    #control_transitions
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  | +                            };
 | 
	
		
			
				|  |  | +                            *guard = Some(new_state);
 | 
	
		
			
				|  |  | +                            if let Some(state) = &*guard {
 | 
	
		
			
				|  |  | +                                if let #state_enum_ident::#end_ident(_) = state {
 | 
	
		
			
				|  |  | +                                    break;
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        Ok(#actor_id_param)
 | 
	
		
			
				|  |  | +                    }).await.unwrap()
 | 
	
		
			
				|  |  | +                };
 | 
	
		
			
				|  |  | +                #handle_struct_ident::new(runtime, shared_state, name)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_actor_loop(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  |          let init_state = actor.init_state().name();
 | 
	
		
			
				|  |  |          let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  |          let msg_enum = self.msg_enum_ident();
 | 
	
		
			
				|  |  |          let init_state_var = self.init_state_var();
 | 
	
		
			
				|  |  |          let end_ident = self.end_ident();
 | 
	
		
			
				|  |  | -        let mailbox = self.mailbox_param();
 | 
	
		
			
				|  |  |          let actor_id = self.actor_id_param();
 | 
	
		
			
				|  |  |          let runtime = self.runtime_param();
 | 
	
		
			
				|  |  |          let from = self.from_ident();
 | 
	
	
		
			
				|  | @@ -241,29 +299,22 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |          let call_transitions = self.generate_call_transitions(actor);
 | 
	
		
			
				|  |  |          let send_transitions = self.generate_send_transitions(actor);
 | 
	
		
			
				|  |  |          let control_transitions = self.generate_control_transitions(actor);
 | 
	
		
			
				|  |  | +        let use_statements = self.use_statements();
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  |              move |
 | 
	
		
			
				|  |  | -                mut #mailbox: ::btrun::Mailbox<#msg_enum>,
 | 
	
		
			
				|  |  | +                mut mailbox: ::btrun::Mailbox<#msg_enum>,
 | 
	
		
			
				|  |  |                  #actor_id: ::btrun::model::ActorId,
 | 
	
		
			
				|  |  |                  #runtime: &'static ::btrun::Runtime
 | 
	
		
			
				|  |  |              | async move {
 | 
	
		
			
				|  |  | -                use ::btlib::bterr;
 | 
	
		
			
				|  |  | -                use ::btrun::{
 | 
	
		
			
				|  |  | -                    log,
 | 
	
		
			
				|  |  | -                    model::{
 | 
	
		
			
				|  |  | -                        Envelope, ControlMsg, Named, TransResult, ActorError, ActorErrorPayload,
 | 
	
		
			
				|  |  | -                        TransKind,
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | +                #use_statements
 | 
	
		
			
				|  |  |                  let #actor_name = #runtime . actor_name(#actor_id);
 | 
	
		
			
				|  |  |                  let mut state = #state_enum_ident :: #init_state (#init_state_var);
 | 
	
		
			
				|  |  | -                while let Some(envelope) = #mailbox.recv().await {
 | 
	
		
			
				|  |  | -                    let new_state = match envelope {
 | 
	
		
			
				|  |  | +                while let Some(envelope) = mailbox.recv().await {
 | 
	
		
			
				|  |  | +                    state = match envelope {
 | 
	
		
			
				|  |  |                          Envelope::Call { #msg, #from, mut #reply, .. } => #call_transitions
 | 
	
		
			
				|  |  |                          Envelope::Send { #msg, #from, .. } => #send_transitions
 | 
	
		
			
				|  |  |                          Envelope::Control(#msg) => #control_transitions
 | 
	
		
			
				|  |  |                      };
 | 
	
		
			
				|  |  | -                    state = new_state;
 | 
	
		
			
				|  |  |                      if let #state_enum_ident::#end_ident(_) = &state {
 | 
	
		
			
				|  |  |                          break;
 | 
	
		
			
				|  |  |                      }
 | 
	
	
		
			
				|  | @@ -288,9 +339,6 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |      ) -> TokenStream {
 | 
	
		
			
				|  |  |          let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  |          let msg_enum_ident = self.msg_enum_ident();
 | 
	
		
			
				|  |  | -        let init_state_type_param = actor.init_state().type_param();
 | 
	
		
			
				|  |  | -        let msg_enum_kinds = self.msg_enum_kinds_ident();
 | 
	
		
			
				|  |  | -        let actor_id = self.actor_id_param();
 | 
	
		
			
				|  |  |          let transitions = actor.states().values().flat_map(|state| {
 | 
	
		
			
				|  |  |              state.methods().values().filter(|method| {
 | 
	
		
			
				|  |  |                  if let Some(in_msg) = method.def().in_msg() {
 | 
	
	
		
			
				|  | @@ -301,128 +349,199 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              })
 | 
	
		
			
				|  |  |              .map(|method| {
 | 
	
		
			
				|  |  | -                let state_type_param = state.type_param();
 | 
	
		
			
				|  |  | -                let mut output_iter = method.outputs().iter();
 | 
	
		
			
				|  |  | -                let next_state = output_iter.next()
 | 
	
		
			
				|  |  | -                    .unwrap_or_else(|| panic!("There are no outputs for method {} in state {}.", method.name(), state.name()));
 | 
	
		
			
				|  |  | -                let next_state_name = if let OutputKind::State { def, .. } = next_state.kind() {
 | 
	
		
			
				|  |  | -                    def.state_trait.as_ref()
 | 
	
		
			
				|  |  | -                } else {
 | 
	
		
			
				|  |  | -                    panic!("First output of {} method was not a state.", method.name());
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | -                let next_state_var = next_state.var_name();
 | 
	
		
			
				|  |  | -                let out_states = output_iter
 | 
	
		
			
				|  |  | -                    .flat_map(|output| {
 | 
	
		
			
				|  |  | -                        if let OutputKind::State { def, .. } = output.kind() {
 | 
	
		
			
				|  |  | -                            Some((output.var_name(), def))
 | 
	
		
			
				|  |  | -                        } else {
 | 
	
		
			
				|  |  | -                            None
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    })
 | 
	
		
			
				|  |  | -                    .map(|(var_name, def)| {
 | 
	
		
			
				|  |  | -                        let spawning_actor = self.actor_lookup().actor_with_state(&def.state_trait);
 | 
	
		
			
				|  |  | -                        let spawning_model = self.actors().get(spawning_actor)
 | 
	
		
			
				|  |  | -                            .unwrap_or_else(|| panic!("There was no actor named {spawning_actor}."));
 | 
	
		
			
				|  |  | -                        let spawn_function = spawning_model.spawn_function_ident()
 | 
	
		
			
				|  |  | -                            .unwrap_or_else(|| panic!("Actor {spawning_actor} of kind {:?} has no spawn function.", spawning_model.kind()));
 | 
	
		
			
				|  |  | -                        let from = self.from_ident();
 | 
	
		
			
				|  |  | -                        let runtime = self.runtime_param();
 | 
	
		
			
				|  |  | -                        let method_name = method.name();
 | 
	
		
			
				|  |  | -                        quote! {
 | 
	
		
			
				|  |  | -                            if let Err(err) = #spawn_function(#runtime, #from, #var_name).await {
 | 
	
		
			
				|  |  | -                                log::error!(
 | 
	
		
			
				|  |  | -                                    "Failed to spawn {} actor after the {} method: {err}",
 | 
	
		
			
				|  |  | -                                    stringify!(#spawning_actor),
 | 
	
		
			
				|  |  | -                                    stringify!(#method_name)
 | 
	
		
			
				|  |  | -                                )
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    });
 | 
	
		
			
				|  |  | -                let out_msgs = method.outputs().iter()
 | 
	
		
			
				|  |  | -                    .flat_map(|output| {
 | 
	
		
			
				|  |  | -                        if let OutputKind::Msg { def, .. } = output.kind() {
 | 
	
		
			
				|  |  | -                            Some((output.var_name(), def))
 | 
	
		
			
				|  |  | -                        } else {
 | 
	
		
			
				|  |  | -                            None
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    })
 | 
	
		
			
				|  |  | -                    .map(|(var_name, dest)| {
 | 
	
		
			
				|  |  | -                        if dest.msg.is_reply() {
 | 
	
		
			
				|  |  | -                            let reply = self.reply_ident();
 | 
	
		
			
				|  |  | -                            let msg_type = &dest.msg.msg_type;
 | 
	
		
			
				|  |  | -                            let reply_variant = self.msg_lookup().lookup(&dest.msg).msg_name();
 | 
	
		
			
				|  |  | -                            let error_msg = format!("Failed to send {} reply.", msg_type);
 | 
	
		
			
				|  |  | -                            quote! {
 | 
	
		
			
				|  |  | -                                if let Some(mut reply) = #reply.take() {
 | 
	
		
			
				|  |  | -                                    if let Err(_) = reply.send(#msg_enum_ident :: #reply_variant (#var_name)) {
 | 
	
		
			
				|  |  | -                                        return Err(ActorError::new(
 | 
	
		
			
				|  |  | -                                            bterr!(#error_msg),
 | 
	
		
			
				|  |  | -                                            ActorErrorPayload {
 | 
	
		
			
				|  |  | -                                                actor_id: #actor_id,
 | 
	
		
			
				|  |  | -                                                actor_impl: #init_state_type_param :: actor_impl(),
 | 
	
		
			
				|  |  | -                                                state: #state_type_param :: state_name(),
 | 
	
		
			
				|  |  | -                                                message: #msg_enum_kinds :: #msg_type .name(),
 | 
	
		
			
				|  |  | -                                                kind: TransKind::Receive,
 | 
	
		
			
				|  |  | -                                            }
 | 
	
		
			
				|  |  | -                                        ));
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -                                } else {
 | 
	
		
			
				|  |  | -                                    log::error!(
 | 
	
		
			
				|  |  | -                                        "Reply to {} message has already been sent.",
 | 
	
		
			
				|  |  | -                                        #msg_enum_kinds :: #msg_type .name()
 | 
	
		
			
				|  |  | -                                    );
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                        } else {
 | 
	
		
			
				|  |  | -                            todo!("Send message to an owned state or to a service.");
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    });
 | 
	
		
			
				|  |  | -                let method_name = method.name();
 | 
	
		
			
				|  |  |                  let state_name = state.name();
 | 
	
		
			
				|  |  | -                let msg_name = method.msg_received_input().unwrap().msg_name();
 | 
	
		
			
				|  |  | -                let out_vars = method.outputs().iter().map(|output| output.var_name());
 | 
	
		
			
				|  |  | +                let msg_name = method.msg_received_input()
 | 
	
		
			
				|  |  | +                    .unwrap_or_else(|| panic!("Method '{}' does not handle any messages.", method.name()))
 | 
	
		
			
				|  |  | +                    .msg_type
 | 
	
		
			
				|  |  | +                    .as_ref();
 | 
	
		
			
				|  |  | +                let args = std::iter::once(self.msg_ident());
 | 
	
		
			
				|  |  | +                let method_call = self.generate_method_call(actor, state, method, args);
 | 
	
		
			
				|  |  |                  quote! {
 | 
	
		
			
				|  |  |                      (#state_enum_ident :: #state_name(state), #msg_enum_ident :: #msg_name(msg)) => {
 | 
	
		
			
				|  |  | -                        match state.#method_name(msg).await {
 | 
	
		
			
				|  |  | -                            TransResult::Ok(( #( #out_vars ),* )) => {
 | 
	
		
			
				|  |  | -                                #( #out_states )*
 | 
	
		
			
				|  |  | -                                #( #out_msgs )*
 | 
	
		
			
				|  |  | -                                #state_enum_ident :: #next_state_name (#next_state_var)
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            TransResult::Abort { from, err, .. } => {
 | 
	
		
			
				|  |  | -                                log::warn!(
 | 
	
		
			
				|  |  | -                                    "Aborted transition from the {} state while handling the {} message: {}",
 | 
	
		
			
				|  |  | -                                    stringify!(#state_name),
 | 
	
		
			
				|  |  | -                                    stringify!(#msg_name),
 | 
	
		
			
				|  |  | -                                    err
 | 
	
		
			
				|  |  | -                                );
 | 
	
		
			
				|  |  | -                                #state_enum_ident :: #state_name(from)
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            TransResult::Fatal { err, .. } => {
 | 
	
		
			
				|  |  | +                        #method_call
 | 
	
		
			
				|  |  | +                    },
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            match (state, msg) {
 | 
	
		
			
				|  |  | +                #( #transitions )*
 | 
	
		
			
				|  |  | +                (state, msg) => {
 | 
	
		
			
				|  |  | +                    log::error!(
 | 
	
		
			
				|  |  | +                        "Unexpected message {} in state {}.", msg.name(), state.name()
 | 
	
		
			
				|  |  | +                    );
 | 
	
		
			
				|  |  | +                    state
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_method_call<TTokens: ToTokens>(
 | 
	
		
			
				|  |  | +        &self,
 | 
	
		
			
				|  |  | +        actor: &ActorModel,
 | 
	
		
			
				|  |  | +        state: &StateModel,
 | 
	
		
			
				|  |  | +        method: &MethodModel,
 | 
	
		
			
				|  |  | +        args: impl Iterator<Item = TTokens>,
 | 
	
		
			
				|  |  | +    ) -> TokenStream {
 | 
	
		
			
				|  |  | +        let msg_enum_ident = self.msg_enum_ident();
 | 
	
		
			
				|  |  | +        let msg_enum_kinds = self.msg_enum_kinds_ident();
 | 
	
		
			
				|  |  | +        let state_type_param = state.type_param();
 | 
	
		
			
				|  |  | +        let actor_id = self.actor_id_param();
 | 
	
		
			
				|  |  | +        let init_state_type_param = actor.init_state().type_param();
 | 
	
		
			
				|  |  | +        let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let mut output_iter = method.output_values().iter();
 | 
	
		
			
				|  |  | +        let next_state = output_iter.next().unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +            panic!(
 | 
	
		
			
				|  |  | +                "There are no outputs for method {} in state {}.",
 | 
	
		
			
				|  |  | +                method.name(),
 | 
	
		
			
				|  |  | +                state.name()
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        let next_state_name = if let ValueKind::State { def, .. } = next_state.kind() {
 | 
	
		
			
				|  |  | +            def.state_trait.as_ref()
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            panic!("First output of {} method was not a state.", method.name());
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        let next_state_var = next_state.var_name();
 | 
	
		
			
				|  |  | +        let out_states = output_iter
 | 
	
		
			
				|  |  | +            .flat_map(|output| {
 | 
	
		
			
				|  |  | +                if let ValueKind::State { def, .. } = output.kind() {
 | 
	
		
			
				|  |  | +                    Some((output.var_name(), def))
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    None
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  | +            .map(|(var_name, def)| {
 | 
	
		
			
				|  |  | +                let spawning_actor = self.actor_lookup().actor_with_state(&def.state_trait);
 | 
	
		
			
				|  |  | +                let spawning_model = self
 | 
	
		
			
				|  |  | +                    .actors()
 | 
	
		
			
				|  |  | +                    .get(spawning_actor)
 | 
	
		
			
				|  |  | +                    .unwrap_or_else(|| panic!("There was no actor named {spawning_actor}."));
 | 
	
		
			
				|  |  | +                let spawn_function = spawning_model.spawn_function_ident().unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +                    panic!(
 | 
	
		
			
				|  |  | +                        "Actor {spawning_actor} of kind {:?} has no spawn function.",
 | 
	
		
			
				|  |  | +                        spawning_model.kind()
 | 
	
		
			
				|  |  | +                    )
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +                let from = self.from_ident();
 | 
	
		
			
				|  |  | +                let runtime = self.runtime_param();
 | 
	
		
			
				|  |  | +                let method_name = method.name();
 | 
	
		
			
				|  |  | +                quote! {
 | 
	
		
			
				|  |  | +                    if let Err(err) = #spawn_function(#runtime, #from, #var_name).await {
 | 
	
		
			
				|  |  | +                        log::error!(
 | 
	
		
			
				|  |  | +                            "Failed to spawn {} actor after the {} method: {err}",
 | 
	
		
			
				|  |  | +                            stringify!(#spawning_actor),
 | 
	
		
			
				|  |  | +                            stringify!(#method_name)
 | 
	
		
			
				|  |  | +                        )
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +        let out_msgs = method.output_values().iter()
 | 
	
		
			
				|  |  | +            .flat_map(|output| {
 | 
	
		
			
				|  |  | +                if let ValueKind::Msg { def, .. } = output.kind() {
 | 
	
		
			
				|  |  | +                    Some((output.var_name(), def))
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    None
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  | +            .map(|(var_name, msg)| {
 | 
	
		
			
				|  |  | +                if msg.is_reply() {
 | 
	
		
			
				|  |  | +                    let reply = self.reply_ident();
 | 
	
		
			
				|  |  | +                    let msg_type = &msg.msg_type;
 | 
	
		
			
				|  |  | +                    let reply_variant = self.msg_lookup().lookup(msg).msg_name();
 | 
	
		
			
				|  |  | +                    let error_msg = format!("Failed to send '{}'.", msg_type);
 | 
	
		
			
				|  |  | +                    quote! {
 | 
	
		
			
				|  |  | +                        if let Some(mut reply) = #reply.take() {
 | 
	
		
			
				|  |  | +                            if let Err(_) = reply.send(#msg_enum_ident :: #reply_variant (#var_name)) {
 | 
	
		
			
				|  |  |                                  return Err(ActorError::new(
 | 
	
		
			
				|  |  | -                                    err,
 | 
	
		
			
				|  |  | +                                    bterr!(#error_msg),
 | 
	
		
			
				|  |  |                                      ActorErrorPayload {
 | 
	
		
			
				|  |  |                                          actor_id: #actor_id,
 | 
	
		
			
				|  |  |                                          actor_impl: #init_state_type_param :: actor_impl(),
 | 
	
		
			
				|  |  |                                          state: #state_type_param :: state_name(),
 | 
	
		
			
				|  |  | -                                        message: #msg_enum_kinds :: #msg_name . name(),
 | 
	
		
			
				|  |  | -                                        kind: TransKind::Receive
 | 
	
		
			
				|  |  | +                                        message: #msg_enum_kinds :: #msg_type .name(),
 | 
	
		
			
				|  |  | +                                        kind: TransKind::Receive,
 | 
	
		
			
				|  |  |                                      }
 | 
	
		
			
				|  |  |                                  ));
 | 
	
		
			
				|  |  |                              }
 | 
	
		
			
				|  |  | +                        } else {
 | 
	
		
			
				|  |  | +                            log::error!(
 | 
	
		
			
				|  |  | +                                "Reply to '{}' has already been sent.",
 | 
	
		
			
				|  |  | +                                #msg_enum_kinds :: #msg_type .name()
 | 
	
		
			
				|  |  | +                            );
 | 
	
		
			
				|  |  |                          }
 | 
	
		
			
				|  |  | -                    },
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    quote! {}
 | 
	
		
			
				|  |  | +                    //match &dest.state {
 | 
	
		
			
				|  |  | +                    //    DestinationState::Service(state) => {
 | 
	
		
			
				|  |  | +                    //        let msg = &dest.msg;
 | 
	
		
			
				|  |  | +                    //        let msg_info = self.msg_lookup().lookup(msg);
 | 
	
		
			
				|  |  | +                    //        let runtime = self.runtime_param();
 | 
	
		
			
				|  |  | +                    //        if msg_info.is_call() {
 | 
	
		
			
				|  |  | +                    //            quote! { todo!("Call a service.") }
 | 
	
		
			
				|  |  | +                    //        } else {
 | 
	
		
			
				|  |  | +                    //            quote! { todo!("Send to a service.") }
 | 
	
		
			
				|  |  | +                    //        }
 | 
	
		
			
				|  |  | +                    //    }
 | 
	
		
			
				|  |  | +                    //    DestinationState::Individual(state)
 | 
	
		
			
				|  |  | +                    //        => quote! { todo!("Send a message to an owned or owner state.") },
 | 
	
		
			
				|  |  | +                    //}
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +        let method_name = method.name();
 | 
	
		
			
				|  |  | +        let state_name = state.name();
 | 
	
		
			
				|  |  | +        let out_vars = method.output_vars();
 | 
	
		
			
				|  |  | +        let (trans_kind, msg_name) = if let Some(input) = method.msg_received_input() {
 | 
	
		
			
				|  |  | +            let trans_kind = quote! { TransKind::Receive };
 | 
	
		
			
				|  |  | +            let msg_name = input.msg_type.as_ref();
 | 
	
		
			
				|  |  | +            (trans_kind, msg_name)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            let trans_kind = quote! { TransKind::Send };
 | 
	
		
			
				|  |  | +            let msg_name = method.output_values().iter().flat_map(|output| {
 | 
	
		
			
				|  |  | +                if let ValueKind::Msg { def, .. } = output.kind() {
 | 
	
		
			
				|  |  | +                    Some(def.msg_type.as_ref())
 | 
	
		
			
				|  |  | +                } else {
 | 
	
		
			
				|  |  | +                    None
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              })
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +            .next()
 | 
	
		
			
				|  |  | +            .unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +                panic!(
 | 
	
		
			
				|  |  | +                    "Method '{}' does not receive or send any messages. It should not have passed validation.",
 | 
	
		
			
				|  |  | +                    method_name
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +            (trans_kind, msg_name)
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  | -            match (state, msg) {
 | 
	
		
			
				|  |  | -                #( #transitions )*
 | 
	
		
			
				|  |  | -                (state, msg) => {
 | 
	
		
			
				|  |  | -                    log::error!(
 | 
	
		
			
				|  |  | -                        "Unexpected message {} in state {}.", msg.name(), state.name()
 | 
	
		
			
				|  |  | +            match state.#method_name(#( #args ),*).await {
 | 
	
		
			
				|  |  | +                TransResult::Ok(( #( #out_vars ),* )) => {
 | 
	
		
			
				|  |  | +                    #( #out_states )*
 | 
	
		
			
				|  |  | +                    #( #out_msgs )*
 | 
	
		
			
				|  |  | +                    #state_enum_ident :: #next_state_name (#next_state_var)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                TransResult::Abort { from, err, .. } => {
 | 
	
		
			
				|  |  | +                    log::warn!(
 | 
	
		
			
				|  |  | +                        "Method {} for actor {} aborted: {}",
 | 
	
		
			
				|  |  | +                        stringify!(#method_name),
 | 
	
		
			
				|  |  | +                        #init_state_type_param :: actor_impl(),
 | 
	
		
			
				|  |  | +                        err
 | 
	
		
			
				|  |  |                      );
 | 
	
		
			
				|  |  | -                    state
 | 
	
		
			
				|  |  | +                    #state_enum_ident :: #state_name(from)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                TransResult::Fatal { err, .. } => {
 | 
	
		
			
				|  |  | +                    return Err(ActorError::new(
 | 
	
		
			
				|  |  | +                        err,
 | 
	
		
			
				|  |  | +                        ActorErrorPayload {
 | 
	
		
			
				|  |  | +                            actor_id: #actor_id,
 | 
	
		
			
				|  |  | +                            actor_impl: #init_state_type_param :: actor_impl(),
 | 
	
		
			
				|  |  | +                            state: #state_type_param :: state_name(),
 | 
	
		
			
				|  |  | +                            message: #msg_enum_kinds :: #msg_name . name(),
 | 
	
		
			
				|  |  | +                            kind: #trans_kind,
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    ));
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -460,47 +579,387 @@ impl ProtocolModel {
 | 
	
		
			
				|  |  |                  quote! { #end_ident(::btrun::model::#end_ident) },
 | 
	
		
			
				|  |  |              ));
 | 
	
		
			
				|  |  |          let type_params = pairs.iter().flat_map(|(_, type_param)| type_param);
 | 
	
		
			
				|  |  | -        let name_pairs: Vec<_> = pairs
 | 
	
		
			
				|  |  | -            .iter()
 | 
	
		
			
				|  |  | -            .map(|(trait_type, _)| {
 | 
	
		
			
				|  |  | -                let trait_type_string = trait_type.to_string();
 | 
	
		
			
				|  |  | -                let name_ident = format_ident!("{}_NAME", trait_type_string.pascal_to_snake().to_uppercase());
 | 
	
		
			
				|  |  | -                let name_decl = quote! {
 | 
	
		
			
				|  |  | -                    static #name_ident: ::btrun::model::Lazy<::std::sync::Arc<String>> =
 | 
	
		
			
				|  |  | -                        ::btrun::model::Lazy::new(|| ::std::sync::Arc::new(#trait_type_string.into()));
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | -                let match_branch = quote! {
 | 
	
		
			
				|  |  | -                    Self::#trait_type(_) => #name_ident.clone(),
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | -                (name_decl, match_branch)
 | 
	
		
			
				|  |  | -            })
 | 
	
		
			
				|  |  | -            .collect();
 | 
	
		
			
				|  |  | -        let name_decls = name_pairs.iter().map(|(name_decl, _)| name_decl);
 | 
	
		
			
				|  |  | -        let name_match_branches = name_pairs.iter().map(|(_, match_branch)| match_branch);
 | 
	
		
			
				|  |  | +        let name_method = self.generate_name_method(|| self.state_idents(actor));
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  |              enum #enum_ident #decl_type_params {
 | 
	
		
			
				|  |  |                  #( #variants ),*
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              impl #decl_type_params ::btrun::model::Named for #enum_ident<#( #type_params ),*> {
 | 
	
		
			
				|  |  | -                fn name(&self) -> ::std::sync::Arc<String> {
 | 
	
		
			
				|  |  | -                    #( #name_decls )*
 | 
	
		
			
				|  |  | -                    match self {
 | 
	
		
			
				|  |  | -                        #( #name_match_branches )*
 | 
	
		
			
				|  |  | +                #name_method
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_client_state_enum(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | +        let init_state = actor.init_state();
 | 
	
		
			
				|  |  | +        let init_type_param = self.init_type_param();
 | 
	
		
			
				|  |  | +        let paths = {
 | 
	
		
			
				|  |  | +            let mut paths = HashMap::<&Ident, TokenStream>::new();
 | 
	
		
			
				|  |  | +            let mut visited = HashSet::<&Ident>::new();
 | 
	
		
			
				|  |  | +            let mut path = Vec::<&Ident>::new();
 | 
	
		
			
				|  |  | +            path.push(init_type_param);
 | 
	
		
			
				|  |  | +            self.make_assoc_type_paths(&mut paths, &mut visited, &mut path, init_state);
 | 
	
		
			
				|  |  | +            paths
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        let end_ident = self.end_ident();
 | 
	
		
			
				|  |  | +        let variant_decls = actor
 | 
	
		
			
				|  |  | +            .states()
 | 
	
		
			
				|  |  | +            .values()
 | 
	
		
			
				|  |  | +            .map(|state| {
 | 
	
		
			
				|  |  | +                let variant = state.name();
 | 
	
		
			
				|  |  | +                let path = paths.get(state.name()).unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +                    panic!(
 | 
	
		
			
				|  |  | +                        "Failed to build a path for state {variant} in actor {}.",
 | 
	
		
			
				|  |  | +                        actor.name()
 | 
	
		
			
				|  |  | +                    )
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +                quote! { #variant(#path), }
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  | +            .chain(std::iter::once(
 | 
	
		
			
				|  |  | +                quote! { #end_ident(::btrun::model::#end_ident) },
 | 
	
		
			
				|  |  | +            ));
 | 
	
		
			
				|  |  | +        let enum_name = actor.state_enum_ident();
 | 
	
		
			
				|  |  | +        let init_state_name = init_state.name();
 | 
	
		
			
				|  |  | +        let name_method = self.generate_name_method(|| self.state_idents(actor));
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            enum #enum_name<#init_type_param: #init_state_name> {
 | 
	
		
			
				|  |  | +                #( #variant_decls )*
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            impl<#init_type_param: #init_state_name> ::btrun::model::Named for #enum_name<#init_type_param> {
 | 
	
		
			
				|  |  | +                #name_method
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_name_method<'a, I, F>(&'a self, get_names: F) -> TokenStream
 | 
	
		
			
				|  |  | +    where
 | 
	
		
			
				|  |  | +        I: Iterator<Item = &'a Ident>,
 | 
	
		
			
				|  |  | +        F: Fn() -> I,
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        let var_names: Vec<_> = get_names()
 | 
	
		
			
				|  |  | +            .map(|name| format_ident!("{}_NAME", name.pascal_to_snake().to_uppercase()))
 | 
	
		
			
				|  |  | +            .collect();
 | 
	
		
			
				|  |  | +        let decls = get_names().zip(var_names.iter()).map(|(name, var_name)| {
 | 
	
		
			
				|  |  | +            quote! {
 | 
	
		
			
				|  |  | +                static #var_name: Lazy<Arc<String>> = Lazy::new(|| {
 | 
	
		
			
				|  |  | +                    Arc::new(stringify!(#name).into())
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        let names = get_names();
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            fn name(&self) -> ::std::sync::Arc<String> {
 | 
	
		
			
				|  |  | +                use ::std::sync::Arc;
 | 
	
		
			
				|  |  | +                use ::btrun::model::Lazy;
 | 
	
		
			
				|  |  | +                #( #decls )*
 | 
	
		
			
				|  |  | +                match self {
 | 
	
		
			
				|  |  | +                    #( Self::#names {..} => #var_names.clone() ),*
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn make_assoc_type_paths<'b, 'a: 'b>(
 | 
	
		
			
				|  |  | +        &'a self,
 | 
	
		
			
				|  |  | +        paths: &'b mut HashMap<&'a Ident, TokenStream>,
 | 
	
		
			
				|  |  | +        visited: &'b mut HashSet<&'a Ident>,
 | 
	
		
			
				|  |  | +        path: &'b mut Vec<&'a Ident>,
 | 
	
		
			
				|  |  | +        current: &'a StateModel,
 | 
	
		
			
				|  |  | +    ) {
 | 
	
		
			
				|  |  | +        visited.insert(current.name());
 | 
	
		
			
				|  |  | +        paths.insert(current.name(), quote! { #( #path )::* });
 | 
	
		
			
				|  |  | +        for output in self.next_states(current) {
 | 
	
		
			
				|  |  | +            let state_trait = output.kind().state_trait().unwrap().as_ref();
 | 
	
		
			
				|  |  | +            if visited.contains(state_trait) {
 | 
	
		
			
				|  |  | +                continue;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            let next = self.get_state(state_trait);
 | 
	
		
			
				|  |  | +            path.push(output.assoc_type().unwrap());
 | 
	
		
			
				|  |  | +            self.make_assoc_type_paths(paths, visited, path, next);
 | 
	
		
			
				|  |  | +            path.pop();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_client_handle(&self, actor: &ActorModel) -> TokenStream {
 | 
	
		
			
				|  |  | +        let struct_ident = actor.handle_struct_ident().unwrap();
 | 
	
		
			
				|  |  | +        let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  | +        let init_type_param = actor.init_state().type_param();
 | 
	
		
			
				|  |  | +        let init_name = actor.init_state().name();
 | 
	
		
			
				|  |  | +        let current_type_param = self.state_type_param();
 | 
	
		
			
				|  |  | +        let new_type_param = self.new_state_type_param();
 | 
	
		
			
				|  |  | +        let new_state_method = self.new_state_method();
 | 
	
		
			
				|  |  | +        let state_field = self.state_field();
 | 
	
		
			
				|  |  | +        let transitions = actor.states().values().flat_map(|current_state| {
 | 
	
		
			
				|  |  | +            current_state
 | 
	
		
			
				|  |  | +                .methods()
 | 
	
		
			
				|  |  | +                .values()
 | 
	
		
			
				|  |  | +                .filter(|method| method.msg_received_input().is_none())
 | 
	
		
			
				|  |  | +                .map(|method| self.generate_client_method(actor, current_state, method))
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            struct #struct_ident<#init_type_param: #init_name, #current_type_param> {
 | 
	
		
			
				|  |  | +                runtime: &'static ::btrun::Runtime,
 | 
	
		
			
				|  |  | +                #state_field: ::std::sync::Arc<::btrun::model::Mutex<Option<#state_enum_ident<#init_type_param>>>>,
 | 
	
		
			
				|  |  | +                actor_name: ::btrun::model::ActorName,
 | 
	
		
			
				|  |  | +                type_state: ::std::marker::PhantomData<#current_type_param>,
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            impl<#init_type_param: #init_name> #struct_ident<#init_type_param, #init_type_param> {
 | 
	
		
			
				|  |  | +                fn new(
 | 
	
		
			
				|  |  | +                    runtime: &'static ::btrun::Runtime,
 | 
	
		
			
				|  |  | +                    #state_field: ::std::sync::Arc<::btrun::model::Mutex<Option<#state_enum_ident<#init_type_param>>>>,
 | 
	
		
			
				|  |  | +                    actor_name: ::btrun::model::ActorName,
 | 
	
		
			
				|  |  | +                ) -> Self {
 | 
	
		
			
				|  |  | +                    Self {
 | 
	
		
			
				|  |  | +                        runtime,
 | 
	
		
			
				|  |  | +                        #state_field,
 | 
	
		
			
				|  |  | +                        actor_name,
 | 
	
		
			
				|  |  | +                        type_state: ::std::marker::PhantomData,
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            impl<#init_type_param: #init_name, #current_type_param> #struct_ident<#init_type_param, #current_type_param> {
 | 
	
		
			
				|  |  | +                fn #new_state_method<#new_type_param>(self) -> #struct_ident<#init_type_param, #new_type_param> {
 | 
	
		
			
				|  |  | +                    #struct_ident {
 | 
	
		
			
				|  |  | +                        runtime: self.runtime,
 | 
	
		
			
				|  |  | +                        #state_field: self.#state_field,
 | 
	
		
			
				|  |  | +                        actor_name: self.actor_name,
 | 
	
		
			
				|  |  | +                        type_state: ::std::marker::PhantomData,
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            #( #transitions )*
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn generate_client_method(
 | 
	
		
			
				|  |  | +        &self,
 | 
	
		
			
				|  |  | +        actor: &ActorModel,
 | 
	
		
			
				|  |  | +        current_state: &StateModel,
 | 
	
		
			
				|  |  | +        method: &MethodModel,
 | 
	
		
			
				|  |  | +    ) -> TokenStream {
 | 
	
		
			
				|  |  | +        let init_state = actor.init_state();
 | 
	
		
			
				|  |  | +        let init_name = init_state.name();
 | 
	
		
			
				|  |  | +        let init_type_param = init_state.type_param();
 | 
	
		
			
				|  |  | +        let new_type_param = self.new_state_type_param();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let handle_method_name = method.handle_name().unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +            panic!(
 | 
	
		
			
				|  |  | +                "Method '{}' in client '{}' had no handle method name.",
 | 
	
		
			
				|  |  | +                method.name(),
 | 
	
		
			
				|  |  | +                actor.name()
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        let current_name = current_state.name();
 | 
	
		
			
				|  |  | +        let current_type_param = current_state.type_param();
 | 
	
		
			
				|  |  | +        let new_state_out = method.next_state();
 | 
	
		
			
				|  |  | +        let new_name = new_state_out.kind().state_trait().unwrap().as_ref();
 | 
	
		
			
				|  |  | +        let new_type_param = if new_name == End::ident() {
 | 
	
		
			
				|  |  | +            self.end_ident()
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            new_type_param
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        fn current_constraint(
 | 
	
		
			
				|  |  | +            current_type_param: &Ident,
 | 
	
		
			
				|  |  | +            current_name: &Ident,
 | 
	
		
			
				|  |  | +            new_type_param: &Ident,
 | 
	
		
			
				|  |  | +            new_state_out: &ValueModel,
 | 
	
		
			
				|  |  | +        ) -> TokenStream {
 | 
	
		
			
				|  |  | +            new_state_out
 | 
	
		
			
				|  |  | +                .assoc_type()
 | 
	
		
			
				|  |  | +                .map(|new_assoc_type| {
 | 
	
		
			
				|  |  | +                    quote! {
 | 
	
		
			
				|  |  | +                        #current_type_param: #current_name<#new_assoc_type = #new_type_param>,
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +                .unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +                    quote! {
 | 
	
		
			
				|  |  | +                        #current_type_param: #current_name,
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                })
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        fn new_constraint(new_type_param: &Ident, new_name: &Ident) -> Option<TokenStream> {
 | 
	
		
			
				|  |  | +            if new_name == End::ident() {
 | 
	
		
			
				|  |  | +                None
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                Some(quote! { #new_type_param: #new_name, })
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // We start by assuming that init, current, and new are all different.
 | 
	
		
			
				|  |  | +        // This means including type constraints for all three.
 | 
	
		
			
				|  |  | +        let type_constraints = {
 | 
	
		
			
				|  |  | +            let current_constraint = current_constraint(
 | 
	
		
			
				|  |  | +                current_type_param,
 | 
	
		
			
				|  |  | +                current_name,
 | 
	
		
			
				|  |  | +                new_type_param,
 | 
	
		
			
				|  |  | +                new_state_out,
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            let new_constraint = new_constraint(new_type_param, new_name);
 | 
	
		
			
				|  |  | +            quote! {
 | 
	
		
			
				|  |  | +                #init_type_param: #init_name,
 | 
	
		
			
				|  |  | +                #current_constraint
 | 
	
		
			
				|  |  | +                #new_constraint
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        #[allow(clippy::collapsible_else_if)]
 | 
	
		
			
				|  |  | +        let (type_constraints, current_type_param, new_type_param) = if init_name == current_name {
 | 
	
		
			
				|  |  | +            let current_type_param = init_type_param;
 | 
	
		
			
				|  |  | +            if current_name == new_name {
 | 
	
		
			
				|  |  | +                // All three are the same, so we mut omit the init and new constraints.
 | 
	
		
			
				|  |  | +                let new_type_param = init_type_param;
 | 
	
		
			
				|  |  | +                let type_constraints = current_constraint(
 | 
	
		
			
				|  |  | +                    current_type_param,
 | 
	
		
			
				|  |  | +                    current_name,
 | 
	
		
			
				|  |  | +                    new_type_param,
 | 
	
		
			
				|  |  | +                    new_state_out,
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                (type_constraints, current_type_param, new_type_param)
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                // init and current are the same, so we must omit the init constraint.
 | 
	
		
			
				|  |  | +                let current_constraint = current_constraint(
 | 
	
		
			
				|  |  | +                    current_type_param,
 | 
	
		
			
				|  |  | +                    current_name,
 | 
	
		
			
				|  |  | +                    new_type_param,
 | 
	
		
			
				|  |  | +                    new_state_out,
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                let new_constraint = new_constraint(new_type_param, new_name);
 | 
	
		
			
				|  |  | +                let type_constraints = quote! {
 | 
	
		
			
				|  |  | +                    #current_constraint
 | 
	
		
			
				|  |  | +                    #new_constraint
 | 
	
		
			
				|  |  | +                };
 | 
	
		
			
				|  |  | +                (type_constraints, current_type_param, new_type_param)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            if current_name == new_name {
 | 
	
		
			
				|  |  | +                // current and new are the same, so we must omit the new constraint.
 | 
	
		
			
				|  |  | +                let new_type_param = current_type_param;
 | 
	
		
			
				|  |  | +                let current_constraint = current_constraint(
 | 
	
		
			
				|  |  | +                    current_type_param,
 | 
	
		
			
				|  |  | +                    current_name,
 | 
	
		
			
				|  |  | +                    new_type_param,
 | 
	
		
			
				|  |  | +                    new_state_out,
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                let type_constraints = quote! {
 | 
	
		
			
				|  |  | +                    #init_type_param: #init_name,
 | 
	
		
			
				|  |  | +                    #current_constraint
 | 
	
		
			
				|  |  | +                };
 | 
	
		
			
				|  |  | +                (type_constraints, current_type_param, new_type_param)
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                // All three are different, so we must not omit any constraints.
 | 
	
		
			
				|  |  | +                (type_constraints, current_type_param, new_type_param)
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        // If the new type is End then it must be fully qualified.
 | 
	
		
			
				|  |  | +        let end_ident = self.end_ident();
 | 
	
		
			
				|  |  | +        let new_type_param = if new_type_param == end_ident {
 | 
	
		
			
				|  |  | +            quote! { ::btrun::model::#end_ident}
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            quote! { #new_type_param }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let params = method.inputs().iter().map(|input| input.as_handle_param());
 | 
	
		
			
				|  |  | +        let first_msg_type = {
 | 
	
		
			
				|  |  | +            let first_input = method
 | 
	
		
			
				|  |  | +                .inputs()
 | 
	
		
			
				|  |  | +                .get(0)
 | 
	
		
			
				|  |  | +                .unwrap_or_else(|| panic!("Method '{}' had no inputs.", method.name()));
 | 
	
		
			
				|  |  | +            if let ValueKind::Dest { msg_type, .. } = first_input.kind() {
 | 
	
		
			
				|  |  | +                msg_type
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                panic!(
 | 
	
		
			
				|  |  | +                    "First input to method '{}' was not a destination.",
 | 
	
		
			
				|  |  | +                    method.name()
 | 
	
		
			
				|  |  | +                )
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        let msg_enum_kinds_ident = self.msg_enum_kinds_ident();
 | 
	
		
			
				|  |  | +        let actor_name_ident = self.actor_name_ident();
 | 
	
		
			
				|  |  | +        let actor_id_param = self.actor_id_param();
 | 
	
		
			
				|  |  | +        let struct_ident = actor.handle_struct_ident().unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +            panic!(
 | 
	
		
			
				|  |  | +                "Attempted to generate client method for non-client actor '{}'.",
 | 
	
		
			
				|  |  | +                actor.name()
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        let use_statements = self.use_statements();
 | 
	
		
			
				|  |  | +        let state_field = self.state_field();
 | 
	
		
			
				|  |  | +        let state_enum_ident = actor.state_enum_ident();
 | 
	
		
			
				|  |  | +        let new_state_method = self.new_state_method();
 | 
	
		
			
				|  |  | +        let runtime_param = self.runtime_param();
 | 
	
		
			
				|  |  | +        let from_ident = self.from_ident();
 | 
	
		
			
				|  |  | +        let actor_name = self.actor_name_ident();
 | 
	
		
			
				|  |  | +        quote! {
 | 
	
		
			
				|  |  | +            #[allow(unreachable_code)]
 | 
	
		
			
				|  |  | +            impl<#type_constraints> #struct_ident<#init_type_param, #current_type_param> {
 | 
	
		
			
				|  |  | +                async fn #handle_method_name(
 | 
	
		
			
				|  |  | +                    self,
 | 
	
		
			
				|  |  | +                    to: ::btrun::model::ServiceAddr,
 | 
	
		
			
				|  |  | +                    #( #params ),*
 | 
	
		
			
				|  |  | +                ) -> ::std::result::Result<#struct_ident<#init_type_param, #new_type_param>, ::btrun::model::ActorError> {
 | 
	
		
			
				|  |  | +                    #use_statements
 | 
	
		
			
				|  |  | +                    let #actor_id_param = self.#actor_name_ident.actor_id();
 | 
	
		
			
				|  |  | +                    let #runtime_param = self.#runtime_param;
 | 
	
		
			
				|  |  | +                    let #from_ident = &self.#actor_name;
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        let mut guard = self.#state_field.lock().await;
 | 
	
		
			
				|  |  | +                        let state = guard
 | 
	
		
			
				|  |  | +                            .take()
 | 
	
		
			
				|  |  | +                            .ok_or_else(|| {
 | 
	
		
			
				|  |  | +                                ActorError::new(
 | 
	
		
			
				|  |  | +                                    bterr!("Client shared state was not returned."),
 | 
	
		
			
				|  |  | +                                    ActorErrorPayload {
 | 
	
		
			
				|  |  | +                                        actor_id: #actor_id_param,
 | 
	
		
			
				|  |  | +                                        actor_impl: #init_type_param :: actor_impl(),
 | 
	
		
			
				|  |  | +                                        state: #current_type_param :: state_name(),
 | 
	
		
			
				|  |  | +                                        message: #msg_enum_kinds_ident::#first_msg_type.name(),
 | 
	
		
			
				|  |  | +                                        kind: TransKind::Send,
 | 
	
		
			
				|  |  | +                                    }
 | 
	
		
			
				|  |  | +                                )
 | 
	
		
			
				|  |  | +                            })?;
 | 
	
		
			
				|  |  | +                        let new_state = match state {
 | 
	
		
			
				|  |  | +                            #state_enum_ident::#current_name(state) => {
 | 
	
		
			
				|  |  | +                                todo!()
 | 
	
		
			
				|  |  | +                            },
 | 
	
		
			
				|  |  | +                            state => {
 | 
	
		
			
				|  |  | +                                *guard = Some(state);
 | 
	
		
			
				|  |  | +                                return Err(ActorError::new(
 | 
	
		
			
				|  |  | +                                    bterr!("Client is in an unexpected state."),
 | 
	
		
			
				|  |  | +                                    ActorErrorPayload {
 | 
	
		
			
				|  |  | +                                        actor_id: #actor_id_param,
 | 
	
		
			
				|  |  | +                                        actor_impl: #init_type_param :: actor_impl(),
 | 
	
		
			
				|  |  | +                                        state: #current_type_param :: state_name(),
 | 
	
		
			
				|  |  | +                                        message: #msg_enum_kinds_ident::#first_msg_type.name(),
 | 
	
		
			
				|  |  | +                                        kind: TransKind::Send,
 | 
	
		
			
				|  |  | +                                    }
 | 
	
		
			
				|  |  | +                                ))
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        };
 | 
	
		
			
				|  |  | +                        *guard = Some(new_state);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    Ok(self.#new_state_method())
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl MethodModel {
 | 
	
		
			
				|  |  | -    /// Generates the tokens for the code which implements this transition.
 | 
	
		
			
				|  |  | -    fn generate_tokens(&self) -> TokenStream {
 | 
	
		
			
				|  |  | +    /// Returns the code which defines this method in its state trait.
 | 
	
		
			
				|  |  | +    fn generate_trait_def(&self) -> TokenStream {
 | 
	
		
			
				|  |  |          let method_ident = self.name().as_ref();
 | 
	
		
			
				|  |  | -        let msg_args = self.inputs().iter();
 | 
	
		
			
				|  |  | -        let output_decls = self.outputs().iter().flat_map(|output| output.decl());
 | 
	
		
			
				|  |  | -        let output_types = self.outputs().iter().flat_map(|output| output.type_name());
 | 
	
		
			
				|  |  | +        let msg_args = self.inputs().iter().map(|input| input.in_method_decl());
 | 
	
		
			
				|  |  | +        let output_decls = self.output_values().iter().flat_map(|output| output.decl());
 | 
	
		
			
				|  |  | +        let output_types = self
 | 
	
		
			
				|  |  | +            .output_values()
 | 
	
		
			
				|  |  | +            .iter()
 | 
	
		
			
				|  |  | +            .flat_map(|output| output.type_name());
 | 
	
		
			
				|  |  |          let future_name = self.future();
 | 
	
		
			
				|  |  |          quote! {
 | 
	
		
			
				|  |  |              #( #output_decls )*
 |