|
@@ -1,15 +1,16 @@
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
|
-use btrun::model::End;
|
|
|
+use btrun::model::{End, TransKind};
|
|
|
use proc_macro2::{Ident, TokenStream};
|
|
|
use quote::{format_ident, quote, ToTokens};
|
|
|
|
|
|
use crate::{
|
|
|
case_convert::CaseConvert,
|
|
|
model::{
|
|
|
- ActorKind, ActorModel, MethodModel, MsgInfo, ProtocolModel, StateModel, TypeParamInfo,
|
|
|
- ValueKind, ValueModel,
|
|
|
+ ActorKind, ActorModel, DestKind, MethodModel, MsgInfo, ProtocolModel, StateModel,
|
|
|
+ TypeParamInfo, ValueKind, ValueModel,
|
|
|
},
|
|
|
+ parsing::DestinationState,
|
|
|
};
|
|
|
|
|
|
impl ToTokens for ProtocolModel {
|
|
@@ -159,7 +160,7 @@ impl ProtocolModel {
|
|
|
#state_enum_decl
|
|
|
|
|
|
let actor = #server_loop ;
|
|
|
- runtime.spawn(None, actor).await
|
|
|
+ runtime.spawn(Some(owner_name), actor).await
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -285,7 +286,9 @@ impl ProtocolModel {
|
|
|
}
|
|
|
|
|
|
fn generate_actor_loop(&self, actor: &ActorModel) -> TokenStream {
|
|
|
- let init_state = actor.init_state().name();
|
|
|
+ let init_state = actor.init_state();
|
|
|
+ let startup = self.generate_startup_method_calls(actor);
|
|
|
+ let init_type_param = init_state.type_param();
|
|
|
let state_enum_ident = actor.state_enum_ident();
|
|
|
let msg_enum = self.msg_enum_ident();
|
|
|
let init_state_var = self.init_state_var();
|
|
@@ -300,15 +303,13 @@ impl ProtocolModel {
|
|
|
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>,
|
|
|
- #actor_id: ::btrun::model::ActorId,
|
|
|
- #runtime: &'static ::btrun::Runtime
|
|
|
- | async move {
|
|
|
- #use_statements
|
|
|
- let #actor_name = #runtime . actor_name(#actor_id);
|
|
|
- let mut state = #state_enum_ident :: #init_state (#init_state_var);
|
|
|
+ let state_var = self.state_var();
|
|
|
+ let state_after_startup_name = actor.state_after_startup();
|
|
|
+ let actor_loop = if state_after_startup_name == End::ident() {
|
|
|
+ quote! {}
|
|
|
+ } else {
|
|
|
+ quote! {
|
|
|
+ let mut #state_var = #state_enum_ident::<#init_type_param>::#state_after_startup_name(#state_var);
|
|
|
while let Some(envelope) = mailbox.recv().await {
|
|
|
state = match envelope {
|
|
|
Envelope::Call { #msg, #from, mut #reply, .. } => #call_transitions
|
|
@@ -319,11 +320,67 @@ impl ProtocolModel {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ };
|
|
|
+ quote! {
|
|
|
+ move |
|
|
|
+ mut mailbox: ::btrun::Mailbox<#msg_enum>,
|
|
|
+ #actor_id: ::btrun::model::ActorId,
|
|
|
+ #runtime: &'static ::btrun::Runtime
|
|
|
+ | async move {
|
|
|
+ #use_statements
|
|
|
+ let #actor_name = #runtime.actor_name(#actor_id);
|
|
|
+ let #state_var = #init_state_var;
|
|
|
+ #( #startup )*
|
|
|
+ #actor_loop
|
|
|
Ok(#actor_id)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fn generate_startup_method_calls<'a>(
|
|
|
+ &'a self,
|
|
|
+ actor: &'a ActorModel,
|
|
|
+ ) -> impl 'a + Iterator<Item = TokenStream> {
|
|
|
+ let state_var = self.state_var();
|
|
|
+ actor.startup_methods().map(move |method| {
|
|
|
+ let output_vars = method.output_vars().skip(1);
|
|
|
+ let input_vars = method.inputs().iter().map(|input| input.var_name());
|
|
|
+ let state_name = method.def().in_state.state_trait.as_ref();
|
|
|
+ let state = actor.states().get(state_name).unwrap();
|
|
|
+ let method_name = method.name();
|
|
|
+ let dispatch_msgs = method
|
|
|
+ .outputs()
|
|
|
+ .iter()
|
|
|
+ .flat_map(|output| self.generate_client_handle_msg_dispatch(actor, state, method, output));
|
|
|
+ let spawn_actors = method.outputs().iter().skip(1).flat_map(|output| {
|
|
|
+ if let ValueKind::State { def: _, .. } = output.kind() {
|
|
|
+ Some(quote! {
|
|
|
+ todo!("Spawn actor returned by startup method.");
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ });
|
|
|
+ let error_ident = format_ident!("err");
|
|
|
+ let error = self.generate_actor_error(actor, state, method, quote! { #error_ident });
|
|
|
+ quote! {
|
|
|
+ let (#state_var, #( #output_vars ),*) = {
|
|
|
+ match #state_var.#method_name(#( #input_vars ),*).await {
|
|
|
+ TransResult::Ok(tuple) => {
|
|
|
+ tuple
|
|
|
+ }
|
|
|
+ TransResult::Abort { #error_ident, .. } | TransResult::Fatal { err, .. } => {
|
|
|
+ return Err(#error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ #( #dispatch_msgs )*
|
|
|
+ #( #spawn_actors )*
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
fn generate_call_transitions(&self, actor: &ActorModel) -> TokenStream {
|
|
|
self.generate_transitions(actor, |msg_info| msg_info.is_call())
|
|
|
}
|
|
@@ -385,13 +442,10 @@ impl ProtocolModel {
|
|
|
) -> 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(|| {
|
|
|
+ let next_state = method.outputs().first().unwrap_or_else(|| {
|
|
|
panic!(
|
|
|
"There are no outputs for method {} in state {}.",
|
|
|
method.name(),
|
|
@@ -404,117 +458,65 @@ impl ProtocolModel {
|
|
|
panic!("First output of {} method was not a state.", method.name());
|
|
|
};
|
|
|
let next_state_var = next_state.var_name();
|
|
|
- let out_states = output_iter
|
|
|
+ let out_states = self.generate_out_state_spawns(actor, state, method);
|
|
|
+ let out_msgs = method.outputs().iter()
|
|
|
.flat_map(|output| {
|
|
|
- if let ValueKind::State { def, .. } = output.kind() {
|
|
|
- Some((output.var_name(), def))
|
|
|
+ if let ValueKind::Dest { def, .. } = output.kind() {
|
|
|
+ Some((output, 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);
|
|
|
+ .map(|(output, def)| {
|
|
|
+ let msg_info = self.msg_lookup().lookup(&def.msg);
|
|
|
+ let msg_name = msg_info.msg_name();
|
|
|
+ let var_name = output.var_name();
|
|
|
+ if msg_info.is_reply() {
|
|
|
+ let reply_ident = self.reply_ident();
|
|
|
+ let reply_variant = self.msg_lookup().lookup(&def.msg).msg_name();
|
|
|
+ let error_msg = format!("Failed to send '{}'.", msg_name);
|
|
|
+ let inner_error = quote! { bterr!(#error_msg) };
|
|
|
+ let error = self.generate_actor_error(actor, state, method, inner_error);
|
|
|
quote! {
|
|
|
- if let Some(mut reply) = #reply.take() {
|
|
|
+ if let Some(mut reply) = #reply_ident.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,
|
|
|
- }
|
|
|
- ));
|
|
|
+ return Err(#error);
|
|
|
}
|
|
|
} else {
|
|
|
log::error!(
|
|
|
"Reply to '{}' has already been sent.",
|
|
|
- #msg_enum_kinds :: #msg_type .name()
|
|
|
+ #msg_enum_kinds :: #msg_name .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.") },
|
|
|
- //}
|
|
|
+ match &def.state {
|
|
|
+ DestinationState::Service(_state) => {
|
|
|
+ quote! {
|
|
|
+ todo!("dispatch message to service");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ DestinationState::Individual(state) => {
|
|
|
+ match method.dest_kind(state) {
|
|
|
+ DestKind::Owner => {
|
|
|
+ quote! { todo!("dispatch message to current owner"); }
|
|
|
+ }
|
|
|
+ DestKind::Owned(_name_var) => {
|
|
|
+ quote! { todo!("dispatch message to owned state"); }
|
|
|
+ }
|
|
|
+ DestKind::Sender => {
|
|
|
+ quote! { todo!("dispatch message to sender"); }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
});
|
|
|
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)
|
|
|
- };
|
|
|
+ let error_ident = format_ident!("err");
|
|
|
+ let error = self.generate_actor_error(actor, state, method, quote! { #error_ident });
|
|
|
quote! {
|
|
|
match state.#method_name(#( #args ),*).await {
|
|
|
TransResult::Ok(( #( #out_vars ),* )) => {
|
|
@@ -531,22 +533,44 @@ impl ProtocolModel {
|
|
|
);
|
|
|
#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,
|
|
|
- }
|
|
|
- ));
|
|
|
+ TransResult::Fatal { #error_ident, .. } => {
|
|
|
+ return Err(#error);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ fn generate_actor_error(
|
|
|
+ &self,
|
|
|
+ actor: &ActorModel,
|
|
|
+ state: &StateModel,
|
|
|
+ method: &MethodModel,
|
|
|
+ inner_error: TokenStream,
|
|
|
+ ) -> TokenStream {
|
|
|
+ let actor_id = self.actor_id_param();
|
|
|
+ let init_state_type_param = actor.init_state().type_param();
|
|
|
+ let state_type_param = state.type_param();
|
|
|
+ let msg_enum_kinds = self.msg_enum_kinds_ident();
|
|
|
+ let attribution = method.attribution();
|
|
|
+ let msg_name = attribution.message().msg_type.as_ref();
|
|
|
+ let trans_kind = match attribution.kind() {
|
|
|
+ TransKind::Receive => quote! { TransKind::Receive },
|
|
|
+ TransKind::Send => quote! { TransKind::Send },
|
|
|
+ };
|
|
|
+ quote! {
|
|
|
+ ActorError::new(
|
|
|
+ #inner_error,
|
|
|
+ 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,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
fn generate_control_transitions(&self, _actor: &ActorModel) -> TokenStream {
|
|
|
quote! {
|
|
|
todo!()
|
|
@@ -747,13 +771,6 @@ impl ProtocolModel {
|
|
|
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();
|
|
@@ -865,21 +882,6 @@ impl ProtocolModel {
|
|
|
};
|
|
|
|
|
|
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(|| {
|
|
@@ -895,59 +897,258 @@ impl ProtocolModel {
|
|
|
let runtime_param = self.runtime_param();
|
|
|
let from_ident = self.from_ident();
|
|
|
let actor_name = self.actor_name_ident();
|
|
|
+ let return_assoc_type = method
|
|
|
+ .outputs()
|
|
|
+ .iter()
|
|
|
+ .find(|output| matches!(output.kind(), ValueKind::Return { .. }))
|
|
|
+ .map(|output| {
|
|
|
+ output
|
|
|
+ .assoc_type()
|
|
|
+ .unwrap_or_else(|| panic!("Return value has no associated type."))
|
|
|
+ })
|
|
|
+ .unwrap_or_else(|| panic!("Client method has no return output."));
|
|
|
+ let state_var = self.state_var();
|
|
|
+ let guard_var = self.guard_var();
|
|
|
+ let give_back_current = self.give_back(actor, current_name);
|
|
|
+ let give_back_new = self.give_back(actor, new_name);
|
|
|
+ let to_var = self.to_var();
|
|
|
+ let dispatch_msgs =
|
|
|
+ self.generate_client_handle_msg_dispatches(actor, current_state, method);
|
|
|
+ let handle_method_name = method.handle_name().unwrap_or_else(|| {
|
|
|
+ panic!(
|
|
|
+ "Method '{}' in client '{}' had no handle method name.",
|
|
|
+ method.name(),
|
|
|
+ actor.name()
|
|
|
+ )
|
|
|
+ });
|
|
|
+ let method_to_call = method.name();
|
|
|
+ let call_args = method
|
|
|
+ .inputs()
|
|
|
+ .iter()
|
|
|
+ .map(|input| input.as_client_method_call(self));
|
|
|
+ let out_state_vars = method
|
|
|
+ .outputs()
|
|
|
+ .iter()
|
|
|
+ .flat_map(|output| {
|
|
|
+ if let ValueKind::State { .. } = output.kind() {
|
|
|
+ let var_name = output.var_name();
|
|
|
+ Some(quote! { #var_name, })
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .skip(1);
|
|
|
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,
|
|
|
+ #to_var: ::btrun::model::ServiceAddr,
|
|
|
#( #params ),*
|
|
|
- ) -> ::std::result::Result<#struct_ident<#init_type_param, #new_type_param>, ::btrun::model::ActorError> {
|
|
|
+ ) -> ::btrun::model::TransResult<
|
|
|
+ Self,
|
|
|
+ (
|
|
|
+ #struct_ident<#init_type_param, #new_type_param>,
|
|
|
+ #init_type_param::#return_assoc_type
|
|
|
+ )
|
|
|
+ > {
|
|
|
#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,
|
|
|
+ let mut #guard_var = self.#state_field.lock().await;
|
|
|
+ let state = if let Some(state) = #guard_var.take() {
|
|
|
+ state
|
|
|
+ } else {
|
|
|
+ let err = bterr!(
|
|
|
+ "Handle is no longer usable. Client shared state was not returned."
|
|
|
+ );
|
|
|
+ return TransResult::Fatal { err };
|
|
|
+ };
|
|
|
+ match state {
|
|
|
+ #state_enum_ident::#current_name(#state_var) => {
|
|
|
+ #( #dispatch_msgs )*
|
|
|
+ let result = #state_var.#method_to_call(#( #call_args ),*).await;
|
|
|
+ match result {
|
|
|
+ TransResult::Ok((#state_var, #( #out_state_vars )* return_val)) => {
|
|
|
+ #give_back_new
|
|
|
+ TransResult::Ok((self.#new_state_method(), return_val))
|
|
|
+ }
|
|
|
+ TransResult::Abort { from: #state_var, err, .. } => {
|
|
|
+ #give_back_current
|
|
|
+ TransResult::Abort { from: self, err }
|
|
|
+ }
|
|
|
+ TransResult::Fatal { err, .. } => TransResult::Fatal { err },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ state => {
|
|
|
+ let name = state.name();
|
|
|
+ *#guard_var = Some(state);
|
|
|
+ drop(#guard_var);
|
|
|
+ let err = bterr!("Client is in an unexpected state: '{name}'.");
|
|
|
+ TransResult::Abort { from: self, err }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn generate_client_handle_msg_dispatches<'a>(
|
|
|
+ &'a self,
|
|
|
+ actor: &'a ActorModel,
|
|
|
+ state: &'a StateModel,
|
|
|
+ method: &'a MethodModel,
|
|
|
+ ) -> impl 'a + Iterator<Item = TokenStream> {
|
|
|
+ method.inputs().iter().flat_map(move |input| {
|
|
|
+ self.generate_client_handle_msg_dispatch(actor, state, method, input)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn generate_client_handle_msg_dispatch(
|
|
|
+ &self,
|
|
|
+ actor: &ActorModel,
|
|
|
+ state: &StateModel,
|
|
|
+ method: &MethodModel,
|
|
|
+ value: &ValueModel,
|
|
|
+ ) -> Option<TokenStream> {
|
|
|
+ let msg_enum_ident = self.msg_enum_ident();
|
|
|
+ let runtime_param = self.runtime_param();
|
|
|
+ let to_var = self.to_var();
|
|
|
+ let from_ident = self.from_ident();
|
|
|
+ let give_back_current = self.give_back(actor, state.name());
|
|
|
+ let actor_id = self.actor_id_param();
|
|
|
+ if let ValueKind::Dest { def, msg_type, .. } = value.kind() {
|
|
|
+ let var_name = value.var_name();
|
|
|
+ let msg_info = self.msg_lookup().lookup(&def.msg);
|
|
|
+ if let Some(reply) = msg_info.reply() {
|
|
|
+ let reply_name = reply.msg_name();
|
|
|
+ Some(quote! {
|
|
|
+ let #var_name = {
|
|
|
+ let arg = #msg_enum_ident::#msg_type(#var_name);
|
|
|
+ let result = #runtime_param
|
|
|
+ .call_service(#to_var, #from_ident.clone(), arg)
|
|
|
+ .await;
|
|
|
+ match result {
|
|
|
+ Ok(value) => {
|
|
|
+ match value {
|
|
|
+ #msg_enum_ident::#reply_name(value) => value,
|
|
|
+ value => {
|
|
|
+ #give_back_current
|
|
|
+ let err = bterr!("Unexpected message of type '{}' sent in response '{}' message.", value.name(), stringify!(#msg_type));
|
|
|
+ return TransResult::Abort { from: self, err };
|
|
|
}
|
|
|
- ))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(err) => {
|
|
|
+ #give_back_current
|
|
|
+ return TransResult::Abort { from: self, err };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ match &def.state {
|
|
|
+ DestinationState::Service(_) => Some(quote! {
|
|
|
+ let #var_name = {
|
|
|
+ let arg = #msg_enum_ident::#msg_type(#var_name);
|
|
|
+ let result = #runtime_param
|
|
|
+ .send_service(#to_var, #from_ident.clone(), arg)
|
|
|
+ .await;
|
|
|
+ match result {
|
|
|
+ Ok(()) => (),
|
|
|
+ Err(err) => {
|
|
|
+ #give_back_current
|
|
|
+ return TransResult::Abort { from: self, err };
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
- *guard = Some(new_state);
|
|
|
- }
|
|
|
- Ok(self.#new_state_method())
|
|
|
+ }),
|
|
|
+ DestinationState::Individual(state) => match method.dest_kind(state) {
|
|
|
+ DestKind::Owner => {
|
|
|
+ let actor_name = actor.name();
|
|
|
+ Some(quote! {
|
|
|
+ {
|
|
|
+ let msg = #msg_enum_ident::#msg_type(#var_name);
|
|
|
+ if let Err(err) = #runtime_param.send_owner(#actor_id, msg).await {
|
|
|
+ log::error!("Failed to send message to owner of actor '{}': {err}", stringify!(#actor_name));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ DestKind::Owned(_name_var) => {
|
|
|
+ Some(quote! { todo!("Dispatch to owned state."); })
|
|
|
+ }
|
|
|
+ DestKind::Sender => Some(quote! { todo!("Dispatch to sender.") }),
|
|
|
+ },
|
|
|
}
|
|
|
}
|
|
|
+ } else {
|
|
|
+ None
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn generate_out_state_spawns<'a>(
|
|
|
+ &'a self,
|
|
|
+ actor: &'a ActorModel,
|
|
|
+ state: &'a StateModel,
|
|
|
+ method: &'a MethodModel,
|
|
|
+ ) -> impl 'a + Iterator<Item = TokenStream> {
|
|
|
+ method
|
|
|
+ .outputs()
|
|
|
+ .iter()
|
|
|
+ .skip(1)
|
|
|
+ .flat_map(|output| {
|
|
|
+ if let ValueKind::State { def, .. } = output.kind() {
|
|
|
+ Some((output.var_name(), def))
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .map(|(var_name, def)| {
|
|
|
+ let actor_to_spawn = self.actor_lookup().actor_with_state(&def.state_trait);
|
|
|
+ let model_to_spawn = self
|
|
|
+ .actors()
|
|
|
+ .get(actor_to_spawn)
|
|
|
+ .unwrap_or_else(|| panic!("There was no actor named {actor_to_spawn}."));
|
|
|
+ let spawn_function = model_to_spawn.spawn_function_ident().unwrap_or_else(|| {
|
|
|
+ panic!(
|
|
|
+ "Actor {actor_to_spawn} of kind {:?} has no spawn function.",
|
|
|
+ model_to_spawn.kind()
|
|
|
+ )
|
|
|
+ });
|
|
|
+ let runtime = self.runtime_param();
|
|
|
+ let method_name = method.name();
|
|
|
+ // If the state has an owner specified, then given ownership to the caller,
|
|
|
+ // otherwise retain ownership.
|
|
|
+ if def.states_array_owner().is_some() {
|
|
|
+ let from = self.from_ident();
|
|
|
+ let owner = quote! { #from };
|
|
|
+ quote! {
|
|
|
+ if let Err(err) = #spawn_function(#runtime, #owner, #var_name).await {
|
|
|
+ log::error!(
|
|
|
+ "Failed to spawn {} actor after the {} method: {err}",
|
|
|
+ stringify!(#actor_to_spawn),
|
|
|
+ stringify!(#method_name)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let actor_name = self.actor_name_ident();
|
|
|
+ let owner = quote! { #actor_name.clone() };
|
|
|
+ let error_ident = format_ident!("err");
|
|
|
+ let error =
|
|
|
+ self.generate_actor_error(actor, state, method, quote! { #error_ident });
|
|
|
+ quote! {
|
|
|
+ let #var_name = match #spawn_function(#runtime, #owner, #var_name).await {
|
|
|
+ Ok(name) => name,
|
|
|
+ Err(#error_ident) => {
|
|
|
+ return Err(#error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl MethodModel {
|
|
@@ -955,11 +1156,8 @@ impl MethodModel {
|
|
|
fn generate_trait_def(&self) -> TokenStream {
|
|
|
let method_ident = self.name().as_ref();
|
|
|
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 output_decls = self.outputs().iter().flat_map(|output| output.decl());
|
|
|
+ let output_types = self.outputs().iter().flat_map(|output| output.type_name());
|
|
|
let future_name = self.future();
|
|
|
quote! {
|
|
|
#( #output_decls )*
|