|
@@ -1,12 +1,14 @@
|
|
-use std::collections::HashMap;
|
|
|
|
|
|
+use std::collections::{HashMap, HashSet};
|
|
|
|
|
|
|
|
+use btrun::model::End;
|
|
use proc_macro2::{Ident, TokenStream};
|
|
use proc_macro2::{Ident, TokenStream};
|
|
use quote::{format_ident, quote, ToTokens};
|
|
use quote::{format_ident, quote, ToTokens};
|
|
|
|
|
|
use crate::{
|
|
use crate::{
|
|
case_convert::CaseConvert,
|
|
case_convert::CaseConvert,
|
|
model::{
|
|
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 all_replies = msg_lookup.msg_iter().all(|msg| msg.is_reply());
|
|
let enum_name = self.msg_enum_ident();
|
|
let enum_name = self.msg_enum_ident();
|
|
let enum_kinds_name = self.msg_enum_kinds_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 {
|
|
let send_impl = if all_replies {
|
|
quote! {}
|
|
quote! {}
|
|
} else {
|
|
} else {
|
|
@@ -55,6 +41,7 @@ impl ProtocolModel {
|
|
};
|
|
};
|
|
let proto_name = &self.def().name_def.name;
|
|
let proto_name = &self.def().name_def.name;
|
|
let doc_comment = format!("Message type for the {proto_name} protocol.");
|
|
let doc_comment = format!("Message type for the {proto_name} protocol.");
|
|
|
|
+ let name_method = self.generate_name_method(|| variant_names_map.keys().copied());
|
|
quote! {
|
|
quote! {
|
|
#[doc = #doc_comment]
|
|
#[doc = #doc_comment]
|
|
#[derive(::serde::Serialize, ::serde::Deserialize)]
|
|
#[derive(::serde::Serialize, ::serde::Deserialize)]
|
|
@@ -70,14 +57,7 @@ impl ProtocolModel {
|
|
}
|
|
}
|
|
|
|
|
|
impl ::btrun::model::Named for #enum_kinds_name {
|
|
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 {}
|
|
impl Copy for #enum_kinds_name {}
|
|
@@ -110,7 +90,7 @@ impl ProtocolModel {
|
|
});
|
|
});
|
|
let mut tokens = TokenStream::new();
|
|
let mut tokens = TokenStream::new();
|
|
for (trait_ident, methods, is_init_state) in traits {
|
|
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 {
|
|
let actor_impl_method = if is_init_state {
|
|
quote! {
|
|
quote! {
|
|
#[doc = "The name of the implementation for the actor this state is a part of."]
|
|
#[doc = "The name of the implementation for the actor this state is a part of."]
|
|
@@ -153,12 +133,12 @@ impl ProtocolModel {
|
|
match actor.kind() {
|
|
match actor.kind() {
|
|
ActorKind::Service => self.generate_service_spawn_function(actor),
|
|
ActorKind::Service => self.generate_service_spawn_function(actor),
|
|
ActorKind::Worker => self.generate_worker_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 {
|
|
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 {
|
|
let TypeParamInfo {
|
|
type_params,
|
|
type_params,
|
|
constraints,
|
|
constraints,
|
|
@@ -166,9 +146,9 @@ impl ProtocolModel {
|
|
let init_state_type_param = actor.init_state().type_param();
|
|
let init_state_type_param = actor.init_state().type_param();
|
|
let state_enum_decl = self.generate_state_enum(actor);
|
|
let state_enum_decl = self.generate_state_enum(actor);
|
|
let init_state_var = self.init_state_var();
|
|
let init_state_var = self.init_state_var();
|
|
- let server_loop = self.generate_loop(actor);
|
|
|
|
|
|
+ let server_loop = self.generate_actor_loop(actor);
|
|
quote! {
|
|
quote! {
|
|
- async fn #function_name<#( #type_params ),*>(
|
|
|
|
|
|
+ async fn #worker_spawn_function_name<#( #type_params ),*>(
|
|
runtime: &'static ::btrun::Runtime,
|
|
runtime: &'static ::btrun::Runtime,
|
|
owner_name: ::btrun::model::ActorName,
|
|
owner_name: ::btrun::model::ActorName,
|
|
#init_state_var: #init_state_type_param,
|
|
#init_state_var: #init_state_type_param,
|
|
@@ -185,7 +165,7 @@ impl ProtocolModel {
|
|
}
|
|
}
|
|
|
|
|
|
fn generate_service_spawn_function(&self, actor: &ActorModel) -> TokenStream {
|
|
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 {
|
|
let TypeParamInfo {
|
|
type_params,
|
|
type_params,
|
|
constraints,
|
|
constraints,
|
|
@@ -194,9 +174,9 @@ impl ProtocolModel {
|
|
let state_enum_decl = self.generate_state_enum(actor);
|
|
let state_enum_decl = self.generate_state_enum(actor);
|
|
let msg_enum = self.msg_enum_ident();
|
|
let msg_enum = self.msg_enum_ident();
|
|
let init_state_var = self.init_state_var();
|
|
let init_state_var = self.init_state_var();
|
|
- let server_loop = self.generate_loop(actor);
|
|
|
|
|
|
+ let server_loop = self.generate_actor_loop(actor);
|
|
quote! {
|
|
quote! {
|
|
- async fn #function_name<#( #type_params ),*, F>(
|
|
|
|
|
|
+ async fn #service_spawn_function_name<#( #type_params ),*, F>(
|
|
runtime: &'static ::btrun::Runtime,
|
|
runtime: &'static ::btrun::Runtime,
|
|
service_id: ::btrun::model::ServiceId,
|
|
service_id: ::btrun::model::ServiceId,
|
|
make_init: F,
|
|
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 init_state = actor.init_state().name();
|
|
let state_enum_ident = actor.state_enum_ident();
|
|
let state_enum_ident = actor.state_enum_ident();
|
|
let msg_enum = self.msg_enum_ident();
|
|
let msg_enum = self.msg_enum_ident();
|
|
let init_state_var = self.init_state_var();
|
|
let init_state_var = self.init_state_var();
|
|
let end_ident = self.end_ident();
|
|
let end_ident = self.end_ident();
|
|
- let mailbox = self.mailbox_param();
|
|
|
|
let actor_id = self.actor_id_param();
|
|
let actor_id = self.actor_id_param();
|
|
let runtime = self.runtime_param();
|
|
let runtime = self.runtime_param();
|
|
let from = self.from_ident();
|
|
let from = self.from_ident();
|
|
@@ -241,29 +299,22 @@ impl ProtocolModel {
|
|
let call_transitions = self.generate_call_transitions(actor);
|
|
let call_transitions = self.generate_call_transitions(actor);
|
|
let send_transitions = self.generate_send_transitions(actor);
|
|
let send_transitions = self.generate_send_transitions(actor);
|
|
let control_transitions = self.generate_control_transitions(actor);
|
|
let control_transitions = self.generate_control_transitions(actor);
|
|
|
|
+ let use_statements = self.use_statements();
|
|
quote! {
|
|
quote! {
|
|
move |
|
|
move |
|
|
- mut #mailbox: ::btrun::Mailbox<#msg_enum>,
|
|
|
|
|
|
+ mut mailbox: ::btrun::Mailbox<#msg_enum>,
|
|
#actor_id: ::btrun::model::ActorId,
|
|
#actor_id: ::btrun::model::ActorId,
|
|
#runtime: &'static ::btrun::Runtime
|
|
#runtime: &'static ::btrun::Runtime
|
|
| async move {
|
|
| 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 #actor_name = #runtime . actor_name(#actor_id);
|
|
let mut state = #state_enum_ident :: #init_state (#init_state_var);
|
|
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::Call { #msg, #from, mut #reply, .. } => #call_transitions
|
|
Envelope::Send { #msg, #from, .. } => #send_transitions
|
|
Envelope::Send { #msg, #from, .. } => #send_transitions
|
|
Envelope::Control(#msg) => #control_transitions
|
|
Envelope::Control(#msg) => #control_transitions
|
|
};
|
|
};
|
|
- state = new_state;
|
|
|
|
if let #state_enum_ident::#end_ident(_) = &state {
|
|
if let #state_enum_ident::#end_ident(_) = &state {
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -288,9 +339,6 @@ impl ProtocolModel {
|
|
) -> TokenStream {
|
|
) -> TokenStream {
|
|
let state_enum_ident = actor.state_enum_ident();
|
|
let state_enum_ident = actor.state_enum_ident();
|
|
let msg_enum_ident = self.msg_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| {
|
|
let transitions = actor.states().values().flat_map(|state| {
|
|
state.methods().values().filter(|method| {
|
|
state.methods().values().filter(|method| {
|
|
if let Some(in_msg) = method.def().in_msg() {
|
|
if let Some(in_msg) = method.def().in_msg() {
|
|
@@ -301,128 +349,199 @@ impl ProtocolModel {
|
|
}
|
|
}
|
|
})
|
|
})
|
|
.map(|method| {
|
|
.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 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! {
|
|
quote! {
|
|
(#state_enum_ident :: #state_name(state), #msg_enum_ident :: #msg_name(msg)) => {
|
|
(#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(
|
|
return Err(ActorError::new(
|
|
- err,
|
|
|
|
|
|
+ bterr!(#error_msg),
|
|
ActorErrorPayload {
|
|
ActorErrorPayload {
|
|
actor_id: #actor_id,
|
|
actor_id: #actor_id,
|
|
actor_impl: #init_state_type_param :: actor_impl(),
|
|
actor_impl: #init_state_type_param :: actor_impl(),
|
|
state: #state_type_param :: state_name(),
|
|
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! {
|
|
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) },
|
|
quote! { #end_ident(::btrun::model::#end_ident) },
|
|
));
|
|
));
|
|
let type_params = pairs.iter().flat_map(|(_, type_param)| type_param);
|
|
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! {
|
|
quote! {
|
|
enum #enum_ident #decl_type_params {
|
|
enum #enum_ident #decl_type_params {
|
|
#( #variants ),*
|
|
#( #variants ),*
|
|
}
|
|
}
|
|
|
|
|
|
impl #decl_type_params ::btrun::model::Named for #enum_ident<#( #type_params ),*> {
|
|
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 {
|
|
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 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();
|
|
let future_name = self.future();
|
|
quote! {
|
|
quote! {
|
|
#( #output_decls )*
|
|
#( #output_decls )*
|