3 Angajamente c5dcdd2886 ... 5f46436994

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  Matthew Carr 5f46436994 Got both of the runtime tests to pass with the generated code. 2 ani în urmă
  Matthew Carr 3506942bcd Added a return value to client trait methods. 2 ani în urmă
  Matthew Carr cbc5e22ed9 Started implementing code generation for client handles. 2 ani în urmă

+ 20 - 20
Cargo.lock

@@ -70,7 +70,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -432,7 +432,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -1071,7 +1071,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -1923,7 +1923,7 @@ dependencies = [
  "proc-macro2",
  "proc-macro2-diagnostics",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2009,7 +2009,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]
@@ -2122,9 +2122,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.71"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 dependencies = [
  "unicode-ident",
 ]
@@ -2137,7 +2137,7 @@ checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
  "version_check",
  "yansi",
 ]
@@ -2211,9 +2211,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -2557,9 +2557,9 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.144"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
 dependencies = [
  "serde_derive",
 ]
@@ -2595,20 +2595,20 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.144"
+version = "1.0.196"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.100",
+ "syn 2.0.48",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.92"
+version = "1.0.113"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a"
+checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
 dependencies = [
  "itoa",
  "ryu",
@@ -2748,9 +2748,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.42"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3154,7 +3154,7 @@ checksum = "3f67b459f42af2e6e1ee213cb9da4dbd022d3320788c3fb3e1b893093f1e45da"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.42",
+ "syn 2.0.48",
 ]
 
 [[package]]

+ 0 - 3
crates/btfs/src/lib.rs

@@ -36,9 +36,6 @@ protocol! {
 
     FileHandle[Opened] -> FileHandle[Opened], >Opened!FileOp;
     Opened?FileOp -> Opened, >FileHandle!FileOp::Reply;
-
-    FileHandle[Opened] -> End, >Opened!Close;
-    Opened?Close -> End;
 }
 
 #[derive(Serialize, Deserialize)]

+ 10 - 2
crates/btproto/src/error.rs

@@ -64,7 +64,7 @@ pub(crate) fn assert_ok<T, E: Into<syn::Result<T>>>(maybe_err: E) {
 pub(crate) fn assert_err<T, E: Into<syn::Result<T>>>(maybe_err: E, expected_msg: &str) {
     let result: syn::Result<T> = maybe_err.into();
     assert!(result.is_err());
-    assert_eq!(expected_msg, result.err().unwrap().to_string());
+    assert_eq!(result.err().unwrap().to_string(), expected_msg);
 }
 
 /// User-visible compile time error messages.
@@ -80,7 +80,7 @@ pub(crate) mod msgs {
     pub(crate) const UNMATCHED_OUTGOING: &str = "No receiver found for message type.";
     pub(crate) const UNMATCHED_INCOMING: &str = "No sender found for message type.";
     pub(crate) const UNDELIVERABLE: &str =
-        "Receiver must either be a service, an owned state, or an out state, or the message must be a reply.";
+        "Receiver must either be a service, an owned state, or an out state. Or, the message must be a reply.";
     pub(crate) const INVALID_REPLY: &str =
         "Replies can only be used in transitions which handle messages.";
     pub(crate) const MULTIPLE_REPLIES: &str =
@@ -89,4 +89,12 @@ pub(crate) mod msgs {
         "This client state is not allowed because it only receives replies. Such a state cannot be observed.";
     pub(crate) const CLIENT_USED_IN_SERVICE: &str =
         "A client state cannot be used inside `service()`.";
+    pub(crate) const AMBIGUOUS_TRANS: &str =
+        "Ambiguous transition definitions. A non-client state can only have a single non-receiving transition.";
+    pub(crate) const EXIT_RACE: &str =
+        "There is a race condition between an exit message and this application message. Avoid sending messages to owned states when exiting.";
+    pub(crate) const INVALID_OUT_STATE_PROVENANCE: &str =
+        "Ownership of this state cannot be given because it is not owned by this transition's input state or input message.";
+    pub(crate) const INVALID_OUT_MSG_PROVENANCE: &str =
+        "Ownership of this state cannot be given because it is not owned by this transition's input state or its input message, and it is not an output state.";
 }

+ 848 - 191
crates/btproto/src/generation.rs

@@ -1,13 +1,16 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 
+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, OutputKind, ProtocolModel, TypeParamInfo,
+        ActorKind, ActorModel, DestKind, MethodModel, MsgInfo, ProtocolModel, StateModel,
+        TypeParamInfo, ValueKind, ValueModel,
     },
+    parsing::DestinationState,
 };
 
 impl ToTokens for ProtocolModel {
@@ -30,22 +33,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 +42,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 +58,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 +91,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 +134,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 +147,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,
@@ -179,13 +160,13 @@ impl ProtocolModel {
                 #state_enum_decl
 
                 let actor = #server_loop ;
-                runtime.spawn(None, actor).await
+                runtime.spawn(Some(owner_name), actor).await
             }
         }
     }
 
     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 +175,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 +206,93 @@ impl ProtocolModel {
         }
     }
 
-    fn generate_loop(&self, actor: &ActorModel) -> TokenStream {
-        let init_state = actor.init_state().name();
+    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();
+        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();
         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,38 +302,85 @@ 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);
-        quote! {
-            move |
-                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,
-                    }
-                };
-                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 {
+        let use_statements = self.use_statements();
+        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
                         Envelope::Send { #msg, #from, .. } => #send_transitions
                         Envelope::Control(#msg) => #control_transitions
                     };
-                    state = new_state;
                     if let #state_enum_ident::#end_ident(_) = &state {
                         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())
     }
@@ -288,9 +396,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,116 +406,16 @@ 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, .. } => {
-                                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: TransKind::Receive
-                                    }
-                                ));
-                            }
-                        }
+                        #method_call
                     },
                 }
             })
@@ -428,6 +433,144 @@ impl ProtocolModel {
         }
     }
 
+    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 init_state_type_param = actor.init_state().type_param();
+        let state_enum_ident = actor.state_enum_ident();
+
+        let next_state = method.outputs().first().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 = self.generate_out_state_spawns(actor, state, method);
+        let out_msgs = method.outputs().iter()
+            .flat_map(|output| {
+                if let ValueKind::Dest { def, .. } = output.kind() {
+                    Some((output, def))
+                } else {
+                    None
+                }
+            })
+            .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_ident.take() {
+                            if let Err(_) = reply.send(#msg_enum_ident :: #reply_variant (#var_name)) {
+                                return Err(#error);
+                            }
+                        } else {
+                            log::error!(
+                                "Reply to '{}' has already been sent.",
+                                #msg_enum_kinds :: #msg_name .name()
+                            );
+                        }
+                    }
+                } else {
+                    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 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 ),* )) => {
+                    #( #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_enum_ident :: #state_name(from)
+                }
+                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!()
@@ -460,45 +603,559 @@ 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 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 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();
+        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! {
+            impl<#type_constraints> #struct_ident<#init_type_param, #current_type_param> {
+                async fn #handle_method_name(
+                    self,
+                    #to_var: ::btrun::model::ServiceAddr,
+                    #( #params ),*
+                ) -> ::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_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 };
+                                }
+                            }
+                        };
+                    }),
+                    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 {
-    /// 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 msg_args = self.inputs().iter().map(|input| input.in_method_decl());
         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();

+ 1 - 1
crates/btproto/src/lib.rs

@@ -36,7 +36,7 @@ macro_rules! unwrap_or_compile_err {
 /// name_def : "named" Ident ;
 /// version_def: "version" LitInt;
 /// actor_def : "let" Ident '=' ident_array ;
-/// ident_array : '[' Ident ( ',' Ident )* ','? ']' ;
+/// ident_array : '[' '*'? Ident ( ',' Ident )* ','? ']' ;
 /// transition : state ( '?' message )?  "->" states_list ( '>' dest_list )? ;
 /// state : Ident ident_array? ;
 /// states_list : state ( ',' state )* ','? ;

Fișier diff suprimat deoarece este prea mare
+ 532 - 152
crates/btproto/src/model.rs


+ 169 - 63
crates/btproto/src/parsing.rs

@@ -220,9 +220,13 @@ pub(crate) struct ActorDef {
     pub(crate) states: IdentArray,
 }
 
+impl ActorDef {
+    const STAR_ERROR: &str = "An actor definition cannot contain a star.";
+}
+
 impl ActorDef {
     #[cfg(test)]
-    pub(crate) fn new<T, I>(actor: &str, state_names: T) -> Self
+    pub(crate) fn new<T, I>(actor: &str, states: T) -> Self
     where
         T: IntoIterator<IntoIter = I>,
         I: ExactSizeIterator<Item = &'static str>,
@@ -231,7 +235,7 @@ impl ActorDef {
             let_token: Token![let](Span::call_site()),
             actor: new_ident(actor).into(),
             eq_token: Token![=](Span::call_site()),
-            states: IdentArray::new(state_names).unwrap(),
+            states: IdentArray::new(states).unwrap(),
         }
     }
 }
@@ -239,12 +243,16 @@ impl ActorDef {
 impl Parse for ActorDef {
     /// actor_def : "let" Ident '=' ident_array ;
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        Ok(Self {
+        let actor_def = Self {
             let_token: input.parse()?,
             actor: Rc::new(input.parse()?),
             eq_token: input.parse()?,
             states: input.parse()?,
-        })
+        };
+        if let Some(star) = &actor_def.states.star {
+            return Err(syn::Error::new(star.span(), Self::STAR_ERROR));
+        }
+        Ok(actor_def)
     }
 }
 
@@ -262,24 +270,67 @@ impl GetSpan for ActorDef {
 #[derive(Hash, PartialEq, Eq)]
 pub(crate) struct IdentArray {
     bracket: Bracket,
+    star: Option<Token![*]>,
     idents: Punctuated<Rc<Ident>, Token![,]>,
 }
 
 impl IdentArray {
     const EMPTY_ERR: &str = "at least one state is required";
+
+    /// Returns the owner state in this array, if this array contains an owner. Otherwise, [None] is
+    /// returned.
+    pub(crate) fn owner(&self) -> Option<&Rc<Ident>> {
+        if self.star.is_some() {
+            self.idents.first()
+        } else {
+            None
+        }
+    }
+
+    /// Returns an iterator over the owned states in this array.
+    pub(crate) fn owned(&self) -> impl Iterator<Item = &Rc<Ident>> {
+        let mut idents = self.idents.iter();
+        if self.star.is_some() {
+            // Skip the first ident because it's the owner.
+            idents.next();
+        }
+        idents
+    }
+
+    /// Returns an iterator over all of the states in this array, owner and owned alike.
+    pub(crate) fn all(&self) -> impl Iterator<Item = &Rc<Ident>> {
+        self.idents.iter()
+    }
 }
 
 #[cfg(test)]
 impl IdentArray {
-    pub(crate) fn new<T, I>(state_names: T) -> Option<Self>
+    pub(crate) fn new<T, I>(owned_names: T) -> Option<Self>
     where
         T: IntoIterator<IntoIter = I>,
         I: ExactSizeIterator<Item = &'static str>,
     {
-        let state_names = state_names.into_iter();
-        if state_names.len() > 0 {
+        Self::new_with_owner(None, owned_names)
+    }
+
+    pub(crate) fn new_with_owner<T, I>(
+        owner_name: Option<&'static str>,
+        owned_names: T,
+    ) -> Option<Self>
+    where
+        T: IntoIterator<IntoIter = I>,
+        I: ExactSizeIterator<Item = &'static str>,
+    {
+        let owned_names = owned_names.into_iter();
+        // When this was written `std::iter::Chain<T, U>` didn't implement `ExactSizeIterator` even
+        // when both `T` and `U` did.
+        let len = owned_names.len() + if owner_name.is_some() { 1 } else { 0 };
+        let star = owner_name.map(|_| Token![*](Span::call_site()));
+        let state_names = owner_name.into_iter().chain(owned_names);
+        if len > 0 {
             Some(Self {
                 bracket: Bracket::default(),
+                star,
                 idents: state_names.map(new_ident).map(Rc::new).collect(),
             })
         } else {
@@ -303,18 +354,17 @@ impl Parse for IdentArray {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
         let content;
         let bracket = bracketed!(content in input);
+        let star = content.parse::<Token![*]>().ok();
         let idents =
             content.parse_terminated(|input| Ok(Rc::new(Ident::parse(input)?)), Token![,])?;
         if idents.is_empty() {
             return Err(syn::Error::new(bracket.span.open(), Self::EMPTY_ERR));
         }
-        Ok(Self { bracket, idents })
-    }
-}
-
-impl AsRef<Punctuated<Rc<Ident>, Token![,]>> for IdentArray {
-    fn as_ref(&self) -> &Punctuated<Rc<Ident>, Token![,]> {
-        &self.idents
+        Ok(Self {
+            bracket,
+            star,
+            idents,
+        })
     }
 }
 
@@ -334,9 +384,21 @@ impl Transition {
         self.in_msg.as_ref().map(|(_, msg)| msg)
     }
 
-    /// Returns true if and only if this [Transition] is not receiving a message.
-    pub(crate) fn not_receiving(&self) -> bool {
-        self.in_msg.is_none()
+    /// Returns an iterator over all of the states owned by this transition.
+    pub(crate) fn owned_states(&self) -> impl Iterator<Item = &'_ Rc<Ident>> {
+        let in_state_owned = self
+            .in_state
+            .states_array
+            .as_ref()
+            .into_iter()
+            .flat_map(|states_array| states_array.owned());
+        let in_msg_owned = self.in_msg().into_iter().flat_map(|msg| {
+            msg.states_array
+                .as_ref()
+                .into_iter()
+                .flat_map(|states_array| states_array.owned())
+        });
+        in_msg_owned.chain(in_state_owned)
     }
 }
 
@@ -398,59 +460,66 @@ impl GetSpan for Transition {
     }
 }
 
-struct IdentIter<'a> {
-    idents: Option<syn::punctuated::Iter<'a, Rc<Ident>>>,
+#[cfg_attr(test, derive(Debug))]
+#[derive(Hash, PartialEq, Eq)]
+pub(crate) struct State {
+    pub(crate) state_trait: Rc<Ident>,
+    pub(crate) states_array: Option<IdentArray>,
 }
 
-impl<'a> IdentIter<'a> {
-    fn new(idents: Option<syn::punctuated::Iter<'a, Rc<Ident>>>) -> Self {
-        Self { idents }
+impl State {
+    /// Returns an iterator over the owned states in this state's states array.
+    pub(crate) fn states_array_owned(&self) -> impl Iterator<Item = &'_ Rc<Ident>> {
+        self.states_array
+            .as_ref()
+            .into_iter()
+            .flat_map(|states_array| states_array.owned())
     }
-}
 
-impl<'a> Iterator for IdentIter<'a> {
-    type Item = &'a Rc<Ident>;
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(idents) = self.idents.as_mut() {
-            idents.next()
+    pub(crate) fn states_array_owner(&self) -> Option<&Rc<Ident>> {
+        if let Some(states_array) = self.states_array.as_ref() {
+            states_array.owner()
         } else {
             None
         }
     }
-}
 
-#[cfg_attr(test, derive(Debug))]
-#[derive(Hash, PartialEq, Eq)]
-pub(crate) struct State {
-    pub(crate) state_trait: Rc<Ident>,
-    pub(crate) owned_states: Option<IdentArray>,
-}
-
-impl State {
-    pub(crate) fn owned_states(&self) -> impl Iterator<Item = &'_ Rc<Ident>> {
-        IdentIter::new(
-            self.owned_states
-                .as_ref()
-                .map(|idents| idents.as_ref().iter()),
-        )
+    /// Returns an iterator over all of the states in the states array associated with this state.
+    pub(crate) fn states_array_all(&self) -> impl Iterator<Item = &'_ Rc<Ident>> {
+        self.states_array
+            .as_ref()
+            .into_iter()
+            .flat_map(|states_array| states_array.all())
     }
 }
 
 #[cfg(test)]
 impl State {
-    pub(crate) fn new<T, I>(state_trait: &str, owned_states: T) -> Self
+    pub(crate) fn new<T, I>(state_trait: &str, owned_names: T) -> Self
     where
         T: IntoIterator<IntoIter = I>,
         I: ExactSizeIterator<Item = &'static str>,
     {
-        Self {
-            state_trait: new_ident(state_trait).into(),
-            owned_states: IdentArray::new(owned_states),
-        }
+        Self::new_with_owner(state_trait, None, owned_names)
     }
 
     fn new_empty_owned(state_trait: &str) -> Self {
-        Self::new(state_trait, std::iter::empty())
+        Self::new_with_owner(state_trait, None, std::iter::empty())
+    }
+
+    pub(crate) fn new_with_owner<T, I>(
+        state_trait: &str,
+        owner_name: Option<&'static str>,
+        owned_names: T,
+    ) -> Self
+    where
+        T: IntoIterator<IntoIter = I>,
+        I: ExactSizeIterator<Item = &'static str>,
+    {
+        Self {
+            state_trait: new_ident(state_trait).into(),
+            states_array: IdentArray::new_with_owner(owner_name, owned_names),
+        }
     }
 }
 
@@ -465,14 +534,14 @@ impl Parse for State {
         };
         Ok(Self {
             state_trait,
-            owned_states,
+            states_array: owned_states,
         })
     }
 }
 
 impl GetSpan for State {
     fn span(&self) -> Span {
-        self.state_trait.span().left_join(&self.owned_states)
+        self.state_trait.span().left_join(&self.states_array)
     }
 }
 
@@ -596,6 +665,13 @@ pub(crate) enum DestinationState {
 impl DestinationState {
     const NO_STATE_ERR: &str = "expected destination state";
     const MULTI_STATE_ERR: &str = "only one destination state is allowed";
+
+    /// Returns a reference to the [State] in this value.
+    pub(crate) fn state_ref(&self) -> &State {
+        match self {
+            Self::Individual(state) | Self::Service(state) => state,
+        }
+    }
 }
 
 impl Parse for DestinationState {
@@ -618,7 +694,7 @@ impl Parse for DestinationState {
             }
             Ok(DestinationState::Service(State {
                 state_trait: dest_state.into(),
-                owned_states: None,
+                states_array: None,
             }))
         } else {
             Ok(DestinationState::Individual(input.parse()?))
@@ -641,7 +717,7 @@ impl GetSpan for DestinationState {
 pub(crate) struct Message {
     pub(crate) msg_type: Rc<Ident>,
     reply_part: Option<MessageReplyPart>,
-    pub(crate) owned_states: Option<IdentArray>,
+    pub(crate) states_array: Option<IdentArray>,
     ident: Option<Ident>,
 }
 
@@ -661,11 +737,10 @@ impl Message {
     }
 
     pub(crate) fn owned_states(&self) -> impl Iterator<Item = &'_ Rc<Ident>> {
-        IdentIter::new(
-            self.owned_states
-                .as_ref()
-                .map(|states| states.as_ref().iter()),
-        )
+        self.states_array
+            .as_ref()
+            .into_iter()
+            .flat_map(|states_array| states_array.owned())
     }
 }
 
@@ -689,7 +764,7 @@ impl Message {
         Self {
             msg_type: Rc::new(new_ident(msg_type)),
             reply_part,
-            owned_states: IdentArray::new(owned_states),
+            states_array: IdentArray::new(owned_states),
             ident: ident_field,
         }
     }
@@ -716,7 +791,7 @@ impl Parse for Message {
         Ok(Self {
             msg_type,
             reply_part,
-            owned_states,
+            states_array: owned_states,
             ident,
         })
     }
@@ -727,7 +802,7 @@ impl GetSpan for Message {
         self.msg_type
             .span()
             .left_join(self.reply_part.as_ref())
-            .left_join(&self.owned_states)
+            .left_join(&self.states_array)
     }
 }
 
@@ -829,7 +904,7 @@ impl MaybeGetSpan for Span {
     }
 }
 
-trait LeftJoin<Rhs> {
+pub(crate) trait LeftJoin<Rhs> {
     /// Attempts to join two [GetSpan] values, but if the result of the join is `None`, then just
     /// the left span is returned.
     fn left_join(&self, other: Rhs) -> Span;
@@ -879,6 +954,8 @@ impl<T: Parse, U: Parse> ParsePunctuatedList for Punctuated<Rc<T>, U> {
 
 #[cfg(test)]
 mod tests {
+    use crate::error::assert_err;
+
     use super::*;
     use syn::parse_str;
 
@@ -1026,6 +1103,15 @@ Init?Activate -> End;"
         assert_eq!(expected, actual);
     }
 
+    #[test]
+    fn actor_def_parse_star_is_err() {
+        const INPUT: &str = "let actor = [*Owner, Owned];";
+
+        let actual = parse_str::<ActorDef>(INPUT);
+
+        assert_err(actual, ActorDef::STAR_ERROR);
+    }
+
     #[test]
     fn ident_array_new() {
         const EXPECTED: [&str; 2] = ["Red", "Green"];
@@ -1075,6 +1161,26 @@ Init?Activate -> End;"
         assert_eq!(IdentArray::EMPTY_ERR, err_str);
     }
 
+    #[test]
+    fn ident_array_stared_is_owner() {
+        const EXPECTED_OWNER: &str = "Owner";
+        const EXPECTED_OWNED: &str = "Owned";
+        let input = format!("[*{EXPECTED_OWNER}, {EXPECTED_OWNED}]");
+
+        let actual = parse_str::<IdentArray>(input.as_str()).unwrap();
+
+        assert_eq!(
+            Some(true),
+            actual
+                .owner()
+                .map(|actual| actual.as_ref() == EXPECTED_OWNER)
+        );
+        let actual_owned_vec: Vec<_> = actual.owned().collect();
+        assert_eq!(1, actual_owned_vec.len());
+        let actual_owned = actual_owned_vec.first().unwrap().as_ref();
+        assert_eq!(actual_owned, EXPECTED_OWNED);
+    }
+
     #[test]
     fn transition_parse_minimal() {
         const EXPECTED_IN_STATE: &str = "Catcher";
@@ -1314,7 +1420,7 @@ Init?Activate -> End;"
 
         assert_eq!(actual.msg_type.as_ref(), EXPECTED_MSG_TYPE);
         assert_eq!(actual.is_reply(), EXPECTED_IS_REPLY);
-        let idents = actual.owned_states.unwrap().idents;
+        let idents = actual.states_array.unwrap().idents;
         assert_eq!(idents.len(), EXPECTED_OWNED_STATES.len());
         assert_eq!(idents[0].as_ref(), EXPECTED_OWNED_STATES[0]);
         assert_eq!(idents[1].as_ref(), EXPECTED_OWNED_STATES[1]);

+ 519 - 16
crates/btproto/src/validation.rs

@@ -1,4 +1,4 @@
-use std::{collections::HashSet, hash::Hash};
+use std::{collections::HashSet, hash::Hash, rc::Rc};
 
 use proc_macro2::{Ident, Span};
 
@@ -6,8 +6,8 @@ use btrun::model::End;
 
 use crate::{
     error::{self, MaybeErr},
-    model::{MsgInfo, ProtocolModel},
-    parsing::{DestinationState, GetSpan, State},
+    model::{ActorKind, MsgInfo, ProtocolModel, ValueKind},
+    parsing::{DestinationState, GetSpan, LeftJoin, State},
 };
 
 impl ProtocolModel {
@@ -18,6 +18,10 @@ impl ProtocolModel {
             .combine(self.no_undeliverable_msgs())
             .combine(self.replies_expected())
             .combine(self.no_unobservable_states())
+            .combine(self.no_ambiguous_trans())
+            .combine(self.no_exit_races())
+            .combine(self.no_invalid_out_state_provenance())
+            .combine(self.no_invalid_out_msg_provenance())
             .into()
     }
 
@@ -27,7 +31,7 @@ impl ProtocolModel {
         let mut declared: HashSet<&Ident> = HashSet::new();
         declared.insert(&end);
         for actor_def in self.def().actor_defs.iter() {
-            for state in actor_def.states.as_ref().iter() {
+            for state in actor_def.states.all() {
                 declared.insert(state);
             }
         }
@@ -35,13 +39,13 @@ impl ProtocolModel {
         for transition in self.def().transitions.iter() {
             let in_state = &transition.in_state;
             used.insert(&in_state.state_trait);
-            used.extend(in_state.owned_states().map(|ident| ident.as_ref()));
+            used.extend(in_state.states_array_owned().map(|ident| ident.as_ref()));
             if let Some(in_msg) = transition.in_msg() {
                 used.extend(in_msg.owned_states().map(|ident| ident.as_ref()));
             }
             for out_states in transition.out_states.as_ref().iter() {
                 used.insert(&out_states.state_trait);
-                used.extend(out_states.owned_states().map(|ident| ident.as_ref()));
+                used.extend(out_states.states_array_owned().map(|ident| ident.as_ref()));
             }
             // We don't have to check the states referred to in out_msgs because the
             // receivers_and_senders_matched method ensures that each of these exists in a receiver
@@ -173,17 +177,24 @@ impl ProtocolModel {
                 match &dest.state {
                     DestinationState::Service(_) => continue,
                     DestinationState::Individual(dest_state) => {
-                        let owned_states = transition
+                        let allowed_from_msg = transition
+                            .in_msg()
+                            .into_iter()
+                            .flat_map(|msg| msg.states_array.as_ref())
+                            .flat_map(|array| array.all())
+                            .map(|ptr| ptr.as_ref());
+                        let allowed_from_state = transition
                             .in_state
-                            .owned_states()
-                            .map(|ident| ident.as_ref());
+                            .states_array_all()
+                            .map(|ident| ident.as_ref())
+                            .chain(allowed_from_msg);
                         let allowed = allowed_states.get_or_insert_with(|| {
                             transition
                                 .out_states
                                 .as_ref()
                                 .iter()
                                 .map(|state| state.state_trait.as_ref())
-                                .chain(owned_states)
+                                .chain(allowed_from_state)
                                 .collect()
                         });
                         if !allowed.contains(dest_state.state_trait.as_ref()) {
@@ -247,15 +258,197 @@ impl ProtocolModel {
             .filter(|actor| actor.kind().is_client())
             .flat_map(|actor| actor.states().values())
             .filter(|state| {
-                state.methods().values().all(|method| {
-                    if let Some(in_msg) = method.def().in_msg() {
-                        in_msg.is_reply()
+                !state.methods().is_empty()
+                    && state.methods().values().all(|method| {
+                        if let Some(in_msg) = method.def().in_msg() {
+                            in_msg.is_reply()
+                        } else {
+                            false
+                        }
+                    })
+            })
+            .map(|state| syn::Error::new(state.span(), error::msgs::UNOBSERVABLE_STATE))
+            .collect()
+    }
+
+    /// Checks that there are no two transitions defined in a non-client state which do not receive
+    /// messages. Such transition create ambiguity because we don't know which of the two
+    /// transitions to execute before entering the non-client's loop.
+    fn no_ambiguous_trans(&self) -> MaybeErr {
+        self.actors_iter()
+            .flat_map(|actor| {
+                if matches!(actor.kind(), ActorKind::Client) {
+                    return None;
+                }
+                let no_input_methods: Vec<_> = actor
+                    .states()
+                    .values()
+                    .flat_map(|state| state.methods().values())
+                    .filter(|method| method.msg_received_input().is_none())
+                    .collect();
+                if no_input_methods.len() > 1 {
+                    Some(no_input_methods)
+                } else {
+                    None
+                }
+            })
+            .map(|no_input_methods| {
+                let mut iter = no_input_methods.into_iter();
+                let mut span = iter.next().unwrap().span();
+                for method in iter {
+                    span = span.left_join(method.span());
+                }
+                syn::Error::new(span, error::msgs::AMBIGUOUS_TRANS)
+            })
+            .collect()
+    }
+
+    /// Ensures that no race conditions exist between the delivery of exit messages by the runtime
+    /// and application messages.
+    fn no_exit_races(&self) -> MaybeErr {
+        self.methods_iter()
+            .filter(|method| {
+                let next_state_name =
+                    if let ValueKind::State { def, .. } = method.next_state().kind() {
+                        def.state_trait.as_ref()
                     } else {
-                        false
+                        panic!(
+                            "Next state output of method '{}' is not actually a state.",
+                            method.name()
+                        );
+                    };
+                next_state_name == End::ident()
+            })
+            .flat_map(|method| {
+                let owned: HashSet<_> = method
+                    .def()
+                    .owned_states()
+                    .map(|ptr| ptr.as_ref())
+                    .collect();
+                method.def().out_msgs.as_ref().iter().flat_map(move |dest| {
+                    if let DestinationState::Individual(state) = &dest.state {
+                        let dest_state = state.state_trait.as_ref();
+                        if owned.contains(dest_state) {
+                            Some(dest)
+                        } else {
+                            None
+                        }
+                    } else {
+                        None
                     }
                 })
             })
-            .map(|state| syn::Error::new(state.span(), error::msgs::UNOBSERVABLE_STATE))
+            .map(|dest| syn::Error::new(dest.span(), error::msgs::EXIT_RACE))
+            .collect()
+    }
+
+    /// Checks that ownership granted to output states given when the input
+    /// state or the input message in the same transition owns the granted state.
+    fn no_invalid_out_state_provenance(&self) -> MaybeErr {
+        self.actors_iter()
+            .flat_map(|actor| actor.states().values().map(move |state| (actor, state)))
+            .flat_map(|(actor, state)| state.methods().values().map(move |method| (actor, method)))
+            .flat_map(|(actor, method)| {
+                let valid_owned_states: HashSet<&Rc<Ident>> = if actor.kind().is_client() {
+                    // A client is allowed to get ownership from the replies to the messages it's
+                    // sending.
+                    let states_from_reply = method
+                        .def()
+                        .out_msgs
+                        .as_ref()
+                        .iter()
+                        .filter(|out_msg| {
+                            let msg_info = self.msg_lookup().lookup(&out_msg.msg);
+                            msg_info.is_call()
+                        })
+                        .flat_map(|out_msg| {
+                            let receiver_state_name =
+                                out_msg.state.state_ref().state_trait.as_ref();
+                            let receiver_state = self.get_state(receiver_state_name);
+                            let msg = &out_msg.msg;
+                            let receiver_method = receiver_state
+                                .methods()
+                                .values()
+                                .find(|method| method.def().in_msg() == Some(msg))
+                                .unwrap_or_else(|| {
+                                    panic!(
+                                    "There is no receiver for message '{}' sent by method '{}'.",
+                                    msg.msg_type.as_ref(),
+                                    method.name(),
+                                )
+                                });
+                            let reply_msg = receiver_method
+                                .def()
+                                .out_msgs
+                                .as_ref()
+                                .iter()
+                                .find(|reply_msg| {
+                                    reply_msg.msg.is_reply()
+                                        && reply_msg.msg.msg_type.as_ref() == msg.msg_type.as_ref()
+                                })
+                                .unwrap_or_else(|| {
+                                    panic!(
+                                        "Method '{}' is not sending reply to message '{}'.",
+                                        receiver_method.name(),
+                                        msg.msg_type.as_ref(),
+                                    )
+                                });
+                            reply_msg.msg.owned_states()
+                        });
+                    method
+                        .def()
+                        .owned_states()
+                        .chain(states_from_reply)
+                        .collect()
+                } else {
+                    method.def().owned_states().collect()
+                };
+                let valid_owned_states = Rc::new(valid_owned_states);
+                method
+                    .def()
+                    .out_states
+                    .as_ref()
+                    .iter()
+                    .flat_map(move |out_state| {
+                        let valid_owned_states = valid_owned_states.clone();
+                        out_state
+                            .states_array_owned()
+                            .filter(move |out_state_owned| {
+                                !valid_owned_states.contains(out_state_owned)
+                            })
+                    })
+            })
+            .map(|state| syn::Error::new(state.span(), error::msgs::INVALID_OUT_STATE_PROVENANCE))
+            .collect()
+    }
+
+    /// Checks that ownership granted to output messages is only given for states owned by the
+    /// input state, input message, or which are output states in the same transition.
+    fn no_invalid_out_msg_provenance(&self) -> MaybeErr {
+        self.methods_iter()
+            .flat_map(|method| {
+                let out_msg_not_valid = |state: &&Rc<Ident>| {
+                    method
+                        .def()
+                        .owned_states()
+                        .chain(
+                            method
+                                .def()
+                                .out_states
+                                .as_ref()
+                                .iter()
+                                .map(|out_state| &out_state.state_trait),
+                        )
+                        .all(|owned_state| owned_state.as_ref() != state.as_ref())
+                };
+                method
+                    .def()
+                    .out_msgs
+                    .as_ref()
+                    .iter()
+                    .flat_map(move |out_msg| out_msg.msg.owned_states().filter(out_msg_not_valid))
+            })
+            .map(|state| syn::Error::new(state.span(), error::msgs::INVALID_OUT_MSG_PROVENANCE))
             .collect()
     }
 }
@@ -272,7 +465,7 @@ mod tests {
     fn all_states_declared_and_used_ok() {
         let input = ProtocolModel::new(Protocol::minimal()).unwrap();
 
-        let result = input.all_states_declared_and_used();
+        let result = input.validate();
 
         assert_ok(result);
     }
@@ -635,6 +828,48 @@ mod tests {
         assert_ok(result);
     }
 
+    #[test]
+    fn no_undeliverable_msgs_sending_when_handling_owned_msg_ok() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("EndAndSend"),
+            [
+                ActorDef::new("client", ["Client", "Subbed"]),
+                ActorDef::new("server", ["Server"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Subbed", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("GiveSelf", false, ["Subbed"]),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("GiveSelf", false, ["Subbed"])),
+                    [State::new("Server", ["Subbed"])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Subbed", [])),
+                        Message::new("FinalOrders", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Subbed", []),
+                    Some(Message::new("FinalOrders", false, [])),
+                    [State::new("End", [])],
+                    [],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_ok(result);
+    }
+
     #[test]
     fn no_undeliverable_msgs_err() {
         let input = ProtocolModel::new(Protocol::new(
@@ -728,4 +963,272 @@ mod tests {
 
         assert_err(result, error::msgs::MULTIPLE_REPLIES);
     }
+
+    #[test]
+    fn no_unobservable_states_reply_to_client_ok() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("Test"),
+            [
+                ActorDef::new("server", ["Server"]),
+                ActorDef::new("client", ["Client"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Client", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Msg", false, [])),
+                    [State::new("Server", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Client", [])),
+                        Message::new("Msg", true, []),
+                    )],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_ok(result);
+    }
+
+    /// Only one method in a non-client actor state is allowed to have no input message, because
+    /// otherwise there would be ambiguity as to which method to call before the actor entered
+    /// its loop.
+    #[test]
+    fn no_ambiguous_trans_err() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("MultipleNoInputTransitions"),
+            [
+                ActorDef::new("client", ["Client"]),
+                ActorDef::new("server", ["Server"]),
+                ActorDef::new("worker", ["Worker"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Client", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Msg", false, [])),
+                    [State::new("Server", []), State::new("Worker", [])],
+                    [],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Halt", false, [])),
+                    [State::new("End", [])],
+                    [],
+                ),
+                // Notice that worker sends two different messages from its initial state. This
+                // crates a ambiguity because we don't know which one should be called in the
+                // generated code.
+                Transition::new(
+                    State::new("Worker", []),
+                    None,
+                    [State::new("Worker", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Worker", []),
+                    None,
+                    [State::new("End", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Halt", false, []),
+                    )],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.no_ambiguous_trans();
+
+        assert_err(result, error::msgs::AMBIGUOUS_TRANS);
+    }
+
+    #[test]
+    fn no_exit_races_err() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("EndAndSend"),
+            [
+                ActorDef::new("client", ["Client", "Subbed"]),
+                ActorDef::new("server", ["Server"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Subbed", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("GiveSelf", false, ["Subbed"]),
+                    )],
+                ),
+                // Notice that Server sends FinalOrders to Subbed and immediately exits. This
+                // creates a race condition between the owner exited message sent by the runtime
+                // and the FinalOrders message.
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("GiveSelf", false, ["Subbed"])),
+                    [State::new("End", ["Subbed"])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Subbed", [])),
+                        Message::new("FinalOrders", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Subbed", []),
+                    Some(Message::new("FinalOrders", false, [])),
+                    [State::new("End", [])],
+                    [],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_err(result, error::msgs::EXIT_RACE);
+    }
+
+    #[test]
+    fn no_invalid_out_state_provenance_ok() {
+        let input = ProtocolModel::new(Protocol::minimal()).unwrap();
+
+        let result = input.no_invalid_out_state_provenance();
+
+        assert_ok(result);
+    }
+
+    #[test]
+    fn no_invalid_out_state_provenance_ownership_from_reply_ok() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("OwnershipFromReply"),
+            [
+                ActorDef::new("client", ["Client"]),
+                ActorDef::new("handle", ["Handle"]),
+                ActorDef::new("server", ["Server"]),
+                ActorDef::new("worker", ["Worker"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Client", []), State::new("Handle", ["Worker"])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Open", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Open", false, [])),
+                    [State::new("Server", []), State::new("Worker", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Client", [])),
+                        Message::new("Open", true, ["Worker"]),
+                    )],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_ok(result);
+    }
+
+    #[test]
+    fn no_invalid_out_state_provenance_err() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("Test"),
+            [
+                ActorDef::new("server", ["Server"]),
+                ActorDef::new("client", ["Client"]),
+                ActorDef::new("worker", ["Worker"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Client", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Msg", false, [])),
+                    [
+                        State::new("Server", []),
+                        // Note that this transition doesn't have the right to give ownership of
+                        // Client.
+                        State::new("Worker", ["Client"]),
+                    ],
+                    [],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_err(result, error::msgs::INVALID_OUT_STATE_PROVENANCE);
+    }
+
+    #[test]
+    fn no_invalid_out_msgs_provenance_err() {
+        let input = ProtocolModel::new(Protocol::new(
+            NameDef::new("Test"),
+            [
+                ActorDef::new("server", ["Server"]),
+                ActorDef::new("client", ["Client"]),
+            ],
+            [
+                Transition::new(
+                    State::new("Client", []),
+                    None,
+                    [State::new("Client", [])],
+                    [Dest::new(
+                        DestinationState::Service(State::new("Server", [])),
+                        Message::new("Msg", false, []),
+                    )],
+                ),
+                Transition::new(
+                    State::new("Server", []),
+                    Some(Message::new("Msg", false, [])),
+                    [State::new("Server", [])],
+                    [Dest::new(
+                        DestinationState::Individual(State::new("Client", [])),
+                        // Note that the this transition has no right to give ownership of Client.
+                        Message::new("Msg", true, ["Client"]),
+                    )],
+                ),
+            ],
+        ))
+        .unwrap();
+
+        let result = input.validate();
+
+        assert_err(result, error::msgs::INVALID_OUT_MSG_PROVENANCE);
+    }
 }

+ 44 - 33
crates/btproto/tests/protocol_tests.rs

@@ -42,9 +42,9 @@ fn minimal_syntax() {
         None => (),
     }
 
-    struct ServerState;
+    struct ServerImpl;
 
-    impl Server for ServerState {
+    impl Server for ServerImpl {
         actor_name!("minimal_server");
 
         type HandleMsgFut = Ready<TransResult<Self, End>>;
@@ -53,14 +53,15 @@ fn minimal_syntax() {
         }
     }
 
-    struct ClientState;
+    struct ClientImpl;
 
-    impl Client for ClientState {
+    impl Client for ClientImpl {
         actor_name!("minimal_client");
 
-        type OnSendMsgFut = Ready<TransResult<Self, End>>;
-        fn on_send_msg(self, _msg: &mut Msg) -> Self::OnSendMsgFut {
-            ready(TransResult::Ok(End))
+        type OnSendMsgReturn = usize;
+        type OnSendMsgFut = Ready<TransResult<Self, (End, usize)>>;
+        fn on_send_msg(self) -> Self::OnSendMsgFut {
+            ready(TransResult::Ok((End, 42)))
         }
     }
 }
@@ -82,9 +83,9 @@ fn reply() {
         None => (),
     }
 
-    struct ListeningState;
+    struct ListeningImpl;
 
-    impl Listening for ListeningState {
+    impl Listening for ListeningImpl {
         actor_name!("reply_server");
 
         type HandlePingListening = Self;
@@ -94,14 +95,15 @@ fn reply() {
         }
     }
 
-    struct ClientState;
+    struct ClientImpl;
 
-    impl Client for ClientState {
+    impl Client for ClientImpl {
         actor_name!("reply_client");
 
+        type OnSendPingReturn = ();
         type OnSendPingClient = Self;
-        type OnSendPingFut = Ready<TransResult<Self, (Self, <Ping as CallMsg>::Reply)>>;
-        fn on_send_ping(self, _ping: &mut Ping) -> Self::OnSendPingFut {
+        type OnSendPingFut = Ready<TransResult<Self, (Self, ())>>;
+        fn on_send_ping(self, _ping: <Ping as CallMsg>::Reply) -> Self::OnSendPingFut {
             ready(TransResult::Ok((self, ())))
         }
     }
@@ -119,9 +121,9 @@ fn client_callback() {
         let server = [Listening];
         let worker = [Working];
         let client = [Unregistered, Registered];
-        Unregistered -> Registered, >service(Listening)!Register[Registered];
-        Listening?Register[Registered] -> Listening, Working[Registered];
-        Working[Registered] -> End, >Registered!Completed;
+        Unregistered -> Registered, >service(Listening)!Register[*Registered];
+        Listening?Register[*Registered] -> Listening, Working[*Registered];
+        Working[*Registered] -> End, >Registered!Completed;
         Registered?Completed -> End;
     }
 
@@ -129,46 +131,55 @@ fn client_callback() {
     match msg {
         Some(ClientCallbackMsgs::Register(msg)) => assert_type!(msg, Register),
         Some(ClientCallbackMsgs::Completed(msg)) => assert_type!(msg, Completed),
-        _ => (),
+        None => (),
+    }
+
+    let client_state: Option<ClientState<UnregisteredImpl>> = None;
+    match client_state {
+        Some(ClientState::Unregistered(state)) => assert_type!(state, UnregisteredImpl),
+        Some(ClientState::Registered(state)) => assert_type!(state, RegisteredImpl),
+        Some(ClientState::End(state)) => assert_type!(state, End),
+        None => (),
     }
 
-    struct UnregisteredState;
+    struct UnregisteredImpl;
 
-    impl Unregistered for UnregisteredState {
+    impl Unregistered for UnregisteredImpl {
         actor_name!("callback_client");
 
-        type OnSendRegisterRegistered = RegisteredState;
-        type OnSendRegisterFut = Ready<TransResult<Self, Self::OnSendRegisterRegistered>>;
-        fn on_send_register(self, _arg: &mut Register) -> Self::OnSendRegisterFut {
-            ready(TransResult::Ok(RegisteredState))
+        type OnSendRegisterReturn = ();
+        type OnSendRegisterRegistered = RegisteredImpl;
+        type OnSendRegisterFut = Ready<TransResult<Self, (Self::OnSendRegisterRegistered, ())>>;
+        fn on_send_register(self) -> Self::OnSendRegisterFut {
+            ready(TransResult::Ok((RegisteredImpl, ())))
         }
     }
 
-    struct RegisteredState;
+    struct RegisteredImpl;
 
-    impl Registered for RegisteredState {
+    impl Registered for RegisteredImpl {
         type HandleCompletedFut = Ready<TransResult<Self, End>>;
         fn handle_completed(self, _arg: Completed) -> Self::HandleCompletedFut {
             ready(TransResult::Ok(End))
         }
     }
 
-    struct ListeningState;
+    struct ListeningImpl;
 
-    impl Listening for ListeningState {
+    impl Listening for ListeningImpl {
         actor_name!("callback_server");
 
-        type HandleRegisterListening = ListeningState;
-        type HandleRegisterWorking = WorkingState;
-        type HandleRegisterFut = Ready<TransResult<Self, (ListeningState, WorkingState)>>;
+        type HandleRegisterListening = ListeningImpl;
+        type HandleRegisterWorking = WorkingImpl;
+        type HandleRegisterFut = Ready<TransResult<Self, (ListeningImpl, WorkingImpl)>>;
         fn handle_register(self, _arg: Register) -> Self::HandleRegisterFut {
-            ready(TransResult::Ok((self, WorkingState)))
+            ready(TransResult::Ok((self, WorkingImpl)))
         }
     }
 
-    struct WorkingState;
+    struct WorkingImpl;
 
-    impl Working for WorkingState {
+    impl Working for WorkingImpl {
         actor_name!("callback_worker");
 
         type OnSendCompletedFut = Ready<TransResult<Self, (End, Completed)>>;

+ 28 - 0
crates/btrun/src/lib.rs

@@ -232,6 +232,29 @@ impl Runtime {
         }
     }
 
+    /// Sends `msg` to the owner of `from`.
+    /// 
+    /// If `from` is not the ID of an actor in this runtime, [RuntimeError::BadActorId] is returned.
+    /// 
+    /// If the actor `from` doesn't have an owner, [RuntimeError::NoOwner] is returned.
+    pub async fn send_owner<T: 'static + MsgEnum>(
+        &'static self,
+        from: ActorId,
+        msg: T,
+    ) -> Result<()> {
+        let owner = {
+            let handles = self.handles.read().await;
+            let handle = handles
+                .get(&from)
+                .ok_or_else(|| bterr!(RuntimeError::BadActorId(from)))?;
+            handle
+                .owner()
+                .ok_or_else(|| bterr!(RuntimeError::NoOwner(from)))?
+                .clone()
+        };
+        self.send(owner, self.actor_name(from), msg).await
+    }
+
     fn service_not_registered_err(id: &ServiceId) -> btlib::Error {
         bterr!("Service is not registered: '{id}'")
     }
@@ -485,14 +508,17 @@ pub async fn do_nothing_actor(
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum RuntimeError {
     BadActorName(ActorName),
+    BadActorId(ActorId),
     BadServiceId(ServiceId),
     BadOwnerName(ActorName),
+    NoOwner(ActorId),
 }
 
 impl Display for RuntimeError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::BadActorName(name) => write!(f, "bad actor name: {name}"),
+            Self::BadActorId(actor_id) => write!(f, "bad actor ID: {actor_id}"),
             Self::BadServiceId(service_id) => {
                 write!(f, "service ID is not registered: {service_id}")
             }
@@ -500,6 +526,7 @@ impl Display for RuntimeError {
                 f,
                 "Non-existent name {name} can't be used as an actor owner."
             ),
+            Self::NoOwner(actor_id) => write!(f, "no owner exists for actor: {actor_id}"),
         }
     }
 }
@@ -708,6 +735,7 @@ macro_rules! test_setup {
         pub(crate) const LOG_LEVEL: &str = "warn";
 
         #[::ctor::ctor]
+        #[allow(non_snake_case)]
         fn ctor() {
             ::std::env::set_var("RUST_LOG", format!("{},quinn=WARN", LOG_LEVEL));
             let mut builder = ::env_logger::Builder::from_default_env();

+ 25 - 1
crates/btrun/src/model.rs

@@ -6,7 +6,7 @@ use btlib::{bterr, BlockPath, Result};
 use btserde::{field_helpers::smart_ptr, from_slice, write_to};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use tokio::{
-    sync::{mpsc, oneshot, Mutex},
+    sync::{mpsc, oneshot},
     task::AbortHandle,
 };
 use uuid::Uuid;
@@ -14,6 +14,7 @@ use uuid::Uuid;
 use crate::{WireEnvelope, WireReply};
 
 pub use once_cell::sync::Lazy;
+pub use tokio::sync::Mutex;
 
 /// The return type of actor closures.
 pub type ActorResult = std::result::Result<ActorId, ActorError>;
@@ -37,6 +38,24 @@ pub enum TransResult<From, To> {
     Fatal { err: btlib::Error },
 }
 
+impl<From, To> TransResult<From, To> {
+    /// If this value is `Ok` then the `To` value it contains is returned. Otherwise, this method
+    /// panics.
+    pub fn unwrap(self) -> To {
+        match self {
+            Self::Ok(to) => to,
+            Self::Abort { err, .. } => {
+                let backtrace = err.as_ref().backtrace();
+                panic!("Called `TransResult::unwrap()` on an `Abort` value: {err}\n{backtrace}")
+            }
+            Self::Fatal { err, .. } => {
+                let backtrace = err.as_ref().backtrace();
+                panic!("Called `TransResult::unwrap()` on a `Fatal` value: {err}\n{backtrace}")
+            }
+        }
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
 /// Specifies a kind of transition, either a `Send` or a `Receive`.
 pub enum TransKind {
@@ -711,6 +730,11 @@ impl ActorHandle {
         (self.wire_deliverer)(envelope).await
     }
 
+    /// Returns a reference to the name of this actor's owner, if it has one.
+    pub(crate) fn owner(&self) -> Option<&ActorName> {
+        self.owner.as_ref()
+    }
+
     /// Takes the owner name out of this handle.
     pub(crate) fn take_owner(&mut self) -> Option<ActorName> {
         self.owner.take()

+ 38 - 523
crates/btrun/tests/runtime_tests.rs

@@ -1,16 +1,12 @@
-#![feature(impl_trait_in_assoc_type)]
-
 use btrun::model::*;
 use btrun::test_setup;
 use btrun::*;
 
-use btlib::Result;
 use btproto::protocol;
 use log;
-use once_cell::sync::Lazy;
 use serde::{Deserialize, Serialize};
 use std::{
-    future::{ready, Future, Ready},
+    future::{ready, Ready},
     sync::{
         atomic::{AtomicU8, Ordering},
         Arc,
@@ -44,202 +40,6 @@ mod ping_pong {
     // When a state is expecting a Reply message, an error occurs if the message is not received
     // in a timely manner.
 
-    enum PingClientState<T: Client> {
-        Client(T),
-        End(End),
-    }
-
-    impl<T: Client> PingClientState<T> {
-        const fn name(&self) -> &'static str {
-            match self {
-                Self::Client(_) => "Client",
-                Self::End(_) => "End",
-            }
-        }
-    }
-
-    struct ClientHandle<T: Client> {
-        state: Option<PingClientState<T>>,
-        client_name: ActorName,
-        runtime: &'static Runtime,
-    }
-
-    impl<T: Client> ClientHandle<T> {
-        async fn send_ping(&mut self, mut msg: Ping, service: ServiceAddr) -> Result<PingReply> {
-            let state = self
-                .state
-                .take()
-                .ok_or_else(|| bterr!("State was not returned."))?;
-            let (new_state, result) = match state {
-                PingClientState::Client(state) => match state.on_send_ping(&mut msg).await {
-                    TransResult::Ok((new_state, _)) => {
-                        let new_state = PingClientState::End(new_state);
-                        let result = self
-                            .runtime
-                            .call_service(
-                                service,
-                                self.client_name.clone(),
-                                PingProtocolMsgs::Ping(msg),
-                            )
-                            .await;
-                        (new_state, result)
-                    }
-                    TransResult::Abort { from, err } => {
-                        let new_state = PingClientState::Client(from);
-                        (new_state, Err(err))
-                    }
-                    TransResult::Fatal { err } => return Err(err),
-                },
-                state => {
-                    let result = Err(bterr!("Can't send Ping in state {}.", state.name()));
-                    (state, result)
-                }
-            };
-            self.state = Some(new_state);
-            let reply = result?;
-            match reply {
-                PingProtocolMsgs::PingReply(reply) => Ok(reply),
-                msg => Err(bterr!(
-                    "Unexpected message type sent in reply: {}",
-                    msg.name()
-                )),
-            }
-        }
-    }
-
-    async fn spawn_client_manual<T: Client>(init: T, runtime: &'static Runtime) -> ClientHandle<T> {
-        let state = Some(PingClientState::Client(init));
-        let client_name = runtime.spawn(None, do_nothing_actor).await.unwrap();
-        ClientHandle {
-            state,
-            client_name,
-            runtime,
-        }
-    }
-
-    async fn register_server_manual<Init, F>(
-        make_init: F,
-        rt: &'static Runtime,
-        id: ServiceId,
-    ) -> Result<ServiceName>
-    where
-        Init: 'static + Server,
-        F: 'static + Send + Sync + Clone + Fn() -> Init,
-    {
-        enum ServerState<S> {
-            Server(S),
-            End(End),
-        }
-
-        impl<S> Named for ServerState<S> {
-            fn name(&self) -> Arc<String> {
-                static SERVER_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("Server".into()));
-                static END_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("End".into()));
-                match self {
-                    Self::Server(_) => SERVER_NAME.clone(),
-                    Self::End(_) => END_NAME.clone(),
-                }
-            }
-        }
-
-        async fn server_loop<Init, F>(
-            _runtime: &'static Runtime,
-            make_init: F,
-            mut mailbox: Mailbox<PingProtocolMsgs>,
-            actor_id: ActorId,
-        ) -> ActorResult
-        where
-            Init: 'static + Server,
-            F: 'static + Send + Sync + FnOnce() -> Init,
-        {
-            let mut state = ServerState::Server(make_init());
-            while let Some(envelope) = mailbox.recv().await {
-                state = match envelope {
-                    Envelope::Call {
-                        msg,
-                        reply: replier,
-                        ..
-                    } => match (state, msg) {
-                        (ServerState::Server(listening_state), PingProtocolMsgs::Ping(msg)) => {
-                            match listening_state.handle_ping(msg).await {
-                                TransResult::Ok((new_state, reply)) => {
-                                    let replier = replier
-                                        .ok_or_else(|| bterr!("Reply has already been sent."))
-                                        .unwrap();
-                                    //let replier =
-                                    //    replier.expect("The reply has already been sent.");
-                                    if let Err(_) = replier.send(PingProtocolMsgs::PingReply(reply))
-                                    {
-                                        return Err(ActorError::new(
-                                            bterr!("Failed to send Ping reply."),
-                                            ActorErrorPayload {
-                                                actor_id,
-                                                actor_impl: Init::actor_impl(),
-                                                state: Init::state_name(),
-                                                message: PingProtocolMsgKinds::Ping.name(),
-                                                kind: TransKind::Receive,
-                                            },
-                                        ));
-                                    }
-                                    ServerState::End(new_state)
-                                }
-                                TransResult::Abort { from, err } => {
-                                    log::warn!("Aborted transition from the {} while handling the {} message: {}", "Server", "Ping", err);
-                                    ServerState::Server(from)
-                                }
-                                TransResult::Fatal { err } => {
-                                    return Err(ActorError::new(
-                                        err,
-                                        ActorErrorPayload {
-                                            actor_id,
-                                            actor_impl: Init::actor_impl(),
-                                            state: Init::state_name(),
-                                            message: PingProtocolMsgKinds::Ping.name(),
-                                            kind: TransKind::Receive,
-                                        },
-                                    ));
-                                }
-                            }
-                        }
-                        (state, _) => state,
-                    },
-                    envelope => {
-                        return Err(ActorError::new(
-                            bterr!("Unexpected envelope type: {}", envelope.name()),
-                            ActorErrorPayload {
-                                actor_id,
-                                actor_impl: Init::actor_impl(),
-                                state: state.name(),
-                                message: envelope.msg_name(),
-                                kind: TransKind::Receive,
-                            },
-                        ))
-                    }
-                };
-
-                if let ServerState::End(_) = state {
-                    break;
-                }
-            }
-            Ok(actor_id)
-        }
-
-        rt.register::<PingProtocolMsgs, _>(id, move |runtime| {
-            let make_init = make_init.clone();
-            let fut = async move {
-                let actor_impl = runtime
-                    .spawn(None, move |mailbox, act_id, runtime| {
-                        server_loop(runtime, make_init, mailbox, act_id)
-                    })
-                    .await
-                    .unwrap();
-                Ok(actor_impl)
-            };
-            Box::pin(fut)
-        })
-        .await
-    }
-
     #[derive(Serialize, Deserialize)]
     pub struct Ping;
     impl CallMsg for Ping {
@@ -249,24 +49,25 @@ mod ping_pong {
     #[derive(Serialize, Deserialize)]
     pub struct PingReply;
 
-    struct ClientState {
+    struct ClientImpl {
         counter: Arc<AtomicU8>,
     }
 
-    impl ClientState {
+    impl ClientImpl {
         fn new(counter: Arc<AtomicU8>) -> Self {
             counter.fetch_add(1, Ordering::SeqCst);
             Self { counter }
         }
     }
 
-    impl Client for ClientState {
+    impl Client for ClientImpl {
         actor_name!("ping_client");
 
-        type OnSendPingFut = impl Future<Output = TransResult<Self, (End, PingReply)>>;
-        fn on_send_ping(self, _msg: &mut Ping) -> Self::OnSendPingFut {
+        type OnSendPingReturn = ();
+        type OnSendPingFut = Ready<TransResult<Self, (End, ())>>;
+        fn on_send_ping(self, _msg: PingReply) -> Self::OnSendPingFut {
             self.counter.fetch_sub(1, Ordering::SeqCst);
-            ready(TransResult::Ok((End, PingReply)))
+            ready(TransResult::Ok((End, ())))
         }
     }
 
@@ -276,7 +77,8 @@ mod ping_pong {
 
     impl ServerState {
         fn new(counter: Arc<AtomicU8>) -> Self {
-            counter.fetch_add(1, Ordering::SeqCst);
+            let count = counter.fetch_add(1, Ordering::SeqCst);
+            log::info!("New service provider started. Count is now '{count}'.");
             Self { counter }
         }
     }
@@ -284,7 +86,7 @@ mod ping_pong {
     impl Server for ServerState {
         actor_name!("ping_server");
 
-        type HandlePingFut = impl Future<Output = TransResult<Self, (End, PingReply)>>;
+        type HandlePingFut = Ready<TransResult<Self, (End, PingReply)>>;
         fn handle_ping(self, _msg: Ping) -> Self::HandlePingFut {
             self.counter.fetch_sub(1, Ordering::SeqCst);
             ready(TransResult::Ok((End, PingReply)))
@@ -303,18 +105,15 @@ mod ping_pong {
                     let server_counter = service_counter.clone();
                     ServerState::new(server_counter)
                 };
-                register_server_manual(make_init, &RUNTIME, service_id.clone())
+                register_server(&RUNTIME, service_id.clone(), make_init)
                     .await
                     .unwrap()
             };
-            let mut client_handle =
-                spawn_client_manual(ClientState::new(counter.clone()), &RUNTIME).await;
+            let client_handle = spawn_client(ClientImpl::new(counter.clone()), &RUNTIME).await;
             let service_addr = ServiceAddr::new(service_name, true);
-            client_handle.send_ping(Ping, service_addr).await.unwrap();
+            client_handle.send_ping(service_addr, Ping).await.unwrap();
 
             assert_eq!(0, counter.load(Ordering::SeqCst));
-
-            RUNTIME.deregister(&service_id, None).await.unwrap();
         });
     }
 }
@@ -335,11 +134,8 @@ mod travel_agency {
         Choosing -> Choosing, >service(Listening)!Accept;
         Choosing -> Choosing, >service(Listening)!Reject;
         Listening?Query -> Listening, >Choosing!Query::Reply;
-        Choosing?Query::Reply -> Choosing;
         Listening?Accept -> End, >Choosing!Accept::Reply;
-        Choosing?Accept::Reply -> End;
         Listening?Reject -> End, >Choosing!Reject::Reply;
-        Choosing?Reject::Reply -> End;
     }
 
     #[derive(Serialize, Deserialize)]
@@ -364,14 +160,11 @@ mod travel_agency {
     }
 }
 
-#[allow(dead_code)]
 mod client_callback {
-
     use super::*;
 
     use btlib::bterr;
-    use once_cell::sync::Lazy;
-    use std::{panic::panic_any, time::Duration};
+    use std::time::Duration;
     use tokio::{sync::oneshot, time::timeout};
 
     #[derive(Serialize, Deserialize)]
@@ -389,9 +182,9 @@ mod client_callback {
         let server = [Listening];
         let worker = [Working];
         let client = [Unregistered, Registered];
-        Unregistered -> Registered, >service(Listening)!Register[Registered];
-        Listening?Register[Registered] -> Listening, Working[Registered];
-        Working[Registered] -> End, >Registered!Completed;
+        Unregistered -> Registered, >service(Listening)!Register[*Registered];
+        Listening?Register[*Registered] -> Listening, Working[*Registered];
+        Working[*Registered] -> End, >Registered!Completed;
         Registered?Completed -> End;
     }
 
@@ -402,12 +195,16 @@ mod client_callback {
     impl Unregistered for UnregisteredState {
         actor_name!("callback_client");
 
+        type OnSendRegisterReturn = ();
         type OnSendRegisterRegistered = RegisteredState;
-        type OnSendRegisterFut = Ready<TransResult<Self, Self::OnSendRegisterRegistered>>;
-        fn on_send_register(self, _arg: &mut Register) -> Self::OnSendRegisterFut {
-            ready(TransResult::Ok(RegisteredState {
-                sender: self.sender,
-            }))
+        type OnSendRegisterFut = Ready<TransResult<Self, (Self::OnSendRegisterRegistered, ())>>;
+        fn on_send_register(self) -> Self::OnSendRegisterFut {
+            ready(TransResult::Ok((
+                RegisteredState {
+                    sender: self.sender,
+                },
+                (),
+            )))
         }
     }
 
@@ -424,7 +221,7 @@ mod client_callback {
     }
 
     struct ListeningState {
-        multiple: usize,
+        multiply_by: usize,
     }
 
     impl Listening for ListeningState {
@@ -434,7 +231,7 @@ mod client_callback {
         type HandleRegisterWorking = WorkingState;
         type HandleRegisterFut = Ready<TransResult<Self, (ListeningState, WorkingState)>>;
         fn handle_register(self, arg: Register) -> Self::HandleRegisterFut {
-            let multiple = self.multiple;
+            let multiple = self.multiply_by;
             ready(TransResult::Ok((
                 self,
                 WorkingState {
@@ -460,308 +257,26 @@ mod client_callback {
         }
     }
 
-    use ::tokio::sync::Mutex;
-
-    enum ClientState<Init: Unregistered> {
-        Unregistered(Init),
-        Registered(Init::OnSendRegisterRegistered),
-        End(End),
-    }
-
-    impl<Init: Unregistered> Named for ClientState<Init> {
-        fn name(&self) -> Arc<String> {
-            static UNREGISTERED_NAME: Lazy<Arc<String>> =
-                Lazy::new(|| Arc::new("Unregistered".into()));
-            static REGISTERED_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("Registered".into()));
-            static END_NAME: Lazy<Arc<String>> = Lazy::new(|| Arc::new("End".into()));
-            match self {
-                Self::Unregistered(_) => UNREGISTERED_NAME.clone(),
-                Self::Registered(_) => REGISTERED_NAME.clone(),
-                Self::End(_) => END_NAME.clone(),
-            }
-        }
-    }
-
-    struct ClientHandle<Init: Unregistered> {
-        runtime: &'static Runtime,
-        state: Arc<Mutex<Option<ClientState<Init>>>>,
-        name: ActorName,
-    }
-
-    impl<Init: Unregistered> ClientHandle<Init> {
-        async fn send_register(&self, to: ServiceAddr, mut msg: Register) -> Result<()> {
-            let mut guard = self.state.lock().await;
-            let state = guard
-                .take()
-                .unwrap_or_else(|| panic!("Logic error. The state was not returned."));
-            let new_state = match state {
-                ClientState::Unregistered(state) => match state.on_send_register(&mut msg).await {
-                    TransResult::Ok(new_state) => {
-                        let msg = ClientCallbackMsgs::Register(msg);
-                        self.runtime
-                            .send_service(to, self.name.clone(), msg)
-                            .await?;
-                        ClientState::Registered(new_state)
-                    }
-                    TransResult::Abort { from, err } => {
-                        log::warn!(
-                            "Aborted transition from the {} state: {}",
-                            "Unregistered",
-                            err
-                        );
-                        ClientState::Unregistered(from)
-                    }
-                    TransResult::Fatal { err } => {
-                        return Err(err);
-                    }
-                },
-                state => state,
-            };
-            *guard = Some(new_state);
-            Ok(())
-        }
-    }
-
-    async fn spawn_client_manual<Init>(init: Init, runtime: &'static Runtime) -> ClientHandle<Init>
-    where
-        Init: 'static + Unregistered,
-    {
-        let state = Arc::new(Mutex::new(Some(ClientState::Unregistered(init))));
-        let name = {
-            let state = state.clone();
-            runtime.spawn(None, move |mut mailbox, actor_id, _| async move {
-                while let Some(envelope) = mailbox.recv().await {
-                    let mut guard = state.lock().await;
-                    let state = guard.take()
-                        .unwrap_or_else(|| panic!("Logic error. The state was not returned."));
-                    let new_state = match envelope {
-                        Envelope::Send { msg, .. } => {
-                            match (state, msg) {
-                                (ClientState::Registered(curr_state), ClientCallbackMsgs::Completed(msg)) => {
-                                    match curr_state.handle_completed(msg).await {
-                                        TransResult::Ok(next) =>  ClientState::<Init>::End(next),
-                                        TransResult::Abort { from, err } => {
-                                            log::warn!("Aborted transition from the {} state while handling the {} message: {}", "Registered", "Completed", err);
-                                            ClientState::Registered(from)
-                                        }
-                                        TransResult::Fatal { err } => {
-                                            panic_any(ActorError::new(
-                                                err,
-                                                ActorErrorPayload {
-                                                actor_id,
-                                                actor_impl: Init::actor_impl(),
-                                                state: Init::OnSendRegisterRegistered::state_name(),
-                                                message: ClientCallbackMsgKinds::Completed.name(),
-                                                kind: TransKind::Receive,
-                                            }));
-                                        }
-                                    }
-                                }
-                                (state, msg) => {
-                                    log::error!("Unexpected message {} in state {}.", msg.name(), state.name());
-                                    state
-                                }
-                            }
-                        }
-                        envelope => return Err(ActorError::new(
-                            bterr!("Unexpected envelope type: {}", envelope.name()),
-                            ActorErrorPayload {
-                            actor_id,
-                            actor_impl: Init::actor_impl(),
-                            state: state.name(),
-                            message: envelope.msg_name(),
-                            kind: TransKind::Receive,
-                        }))
-                    };
-                    *guard = Some(new_state);
-                }
-                Ok(actor_id)
-            }).await.unwrap()
-        };
-        ClientHandle {
-            runtime,
-            state,
-            name,
-        }
-    }
-
-    async fn register_server_manual<Init, F>(
-        make_init: F,
-        runtime: &'static Runtime,
-        service_id: ServiceId,
-    ) -> Result<ServiceName>
-    where
-        Init: 'static + Listening<HandleRegisterListening = Init>,
-        F: 'static + Send + Sync + Clone + Fn() -> Init,
-    {
-        enum ServerState<Init: Listening> {
-            Listening(Init),
-        }
-
-        impl<S: Listening> Named for ServerState<S> {
-            fn name(&self) -> Arc<String> {
-                static LISTENING_NAME: Lazy<Arc<String>> =
-                    Lazy::new(|| Arc::new("Listening".into()));
-                match self {
-                    Self::Listening(_) => LISTENING_NAME.clone(),
-                }
-            }
-        }
-
-        async fn server_loop<Init, F>(
-            runtime: &'static Runtime,
-            make_init: F,
-            mut mailbox: Mailbox<ClientCallbackMsgs>,
-            actor_id: ActorId,
-        ) -> ActorResult
-        where
-            Init: 'static + Listening<HandleRegisterListening = Init>,
-            F: 'static + Send + Sync + Fn() -> Init,
-        {
-            let mut state = ServerState::Listening(make_init());
-            while let Some(envelope) = mailbox.recv().await {
-                let new_state = match envelope {
-                    Envelope::Send { msg, from, .. } => match (state, msg) {
-                        (ServerState::Listening(curr_state), ClientCallbackMsgs::Register(msg)) => {
-                            match curr_state.handle_register(msg).await {
-                                TransResult::Ok((new_state, working_state)) => {
-                                    start_worker_manual(working_state, from, runtime).await;
-                                    ServerState::Listening(new_state)
-                                }
-                                TransResult::Abort { from, err } => {
-                                    log::warn!("Aborted transition from the {} state while handling the {} message: {}", "Listening", "Register", err);
-                                    ServerState::Listening(from)
-                                }
-                                TransResult::Fatal { err } => {
-                                    let err = ActorError::new(
-                                        err,
-                                        ActorErrorPayload {
-                                            actor_id,
-                                            actor_impl: Init::actor_impl(),
-                                            state: Init::state_name(),
-                                            message: ClientCallbackMsgKinds::Register.name(),
-                                            kind: TransKind::Receive,
-                                        },
-                                    );
-                                    panic_any(format!("{err}"));
-                                }
-                            }
-                        }
-                        (state, msg) => {
-                            log::error!(
-                                "Unexpected message {} in state {}.",
-                                msg.name(),
-                                state.name()
-                            );
-                            state
-                        }
-                    },
-                    envelope => {
-                        return Err(ActorError::new(
-                            bterr!("Unexpected envelope type: {}", envelope.name()),
-                            ActorErrorPayload {
-                                actor_id,
-                                actor_impl: Init::actor_impl(),
-                                state: state.name(),
-                                message: envelope.msg_name(),
-                                kind: TransKind::Receive,
-                            },
-                        ))
-                    }
-                };
-                state = new_state;
-            }
-            Ok(actor_id)
-        }
-
-        runtime
-            .register::<ClientCallbackMsgs, _>(service_id, move |runtime: &'static Runtime| {
-                let make_init = make_init.clone();
-                let fut = async move {
-                    let make_init = make_init.clone();
-                    let actor_impl = runtime
-                        .spawn(None, move |mailbox, act_id, runtime| {
-                            server_loop(runtime, make_init, mailbox, act_id)
-                        })
-                        .await
-                        .unwrap();
-                    Ok(actor_impl)
-                };
-                Box::pin(fut)
-            })
-            .await
-    }
-
-    async fn start_worker_manual<Init>(
-        init: Init,
-        owned: ActorName,
-        runtime: &'static Runtime,
-    ) -> ActorName
-    where
-        Init: 'static + Working,
-    {
-        enum WorkerState<S: Working> {
-            Working(S),
-        }
-
-        runtime
-            .spawn::<ClientCallbackMsgs, _, _>(
-                Some(owned.clone()),
-                move |_, actor_id, _| async move {
-                    let msg = match init.on_send_completed().await {
-                        TransResult::Ok((End, msg)) => msg,
-                        TransResult::Abort { err, .. } | TransResult::Fatal { err } => {
-                            let err = ActorError::new(
-                                err,
-                                ActorErrorPayload {
-                                    actor_id,
-                                    actor_impl: Init::actor_impl(),
-                                    state: Init::state_name(),
-                                    message: ClientCallbackMsgKinds::Completed.name(),
-                                    kind: TransKind::Send,
-                                },
-                            );
-                            panic_any(format!("{err}"))
-                        }
-                    };
-                    let from = runtime.actor_name(actor_id);
-                    let msg = ClientCallbackMsgs::Completed(msg);
-                    runtime.send(owned, from, msg).await.unwrap_or_else(|err| {
-                        let err = ActorError::new(
-                            err,
-                            ActorErrorPayload {
-                                actor_id,
-                                actor_impl: Init::actor_impl(),
-                                state: Init::state_name(),
-                                message: ClientCallbackMsgKinds::Completed.name(),
-                                kind: TransKind::Send,
-                            },
-                        );
-                        panic_any(format!("{err}"));
-                    });
-                    Ok(actor_id)
-                },
-            )
-            .await
-            .unwrap()
-    }
-
     #[test]
     fn client_callback_protocol() {
         ASYNC_RT.block_on(async {
             const SERVICE_ID: &str = "ClientCallbackProtocolListening";
+            let factor = 21usize;
+            let multiply_by = 2usize;
+            let expected = multiply_by * factor;
             let service_id = ServiceId::from(SERVICE_ID);
+
             let service_name = {
-                let make_init = move || ListeningState { multiple: 2 };
-                register_server_manual(make_init, &RUNTIME, service_id.clone())
+                let make_init = move || ListeningState { multiply_by };
+                register_server(&RUNTIME, service_id.clone(), make_init)
                     .await
                     .unwrap()
             };
             let (sender, receiver) = oneshot::channel();
-            let client_handle = spawn_client_manual(UnregisteredState { sender }, &RUNTIME).await;
+            let client_handle = spawn_client(UnregisteredState { sender }, &RUNTIME).await;
             let service_addr = ServiceAddr::new(service_name, false);
             client_handle
-                .send_register(service_addr, Register { factor: 21 })
+                .send_register(service_addr, Register { factor })
                 .await
                 .unwrap();
             let value = timeout(Duration::from_millis(500), receiver)
@@ -769,7 +284,7 @@ mod client_callback {
                 .unwrap()
                 .unwrap();
 
-            assert_eq!(42, value);
+            assert_eq!(expected, value);
         });
     }
 }

+ 0 - 1
crates/btsector/src/lib.rs

@@ -63,5 +63,4 @@ protocol! {
     let client = [Client];
     Client -> Client, >service(Listening)!SectorMsg;
     Listening?SectorMsg -> Listening, >Client!SectorMsg::Reply;
-    Client?SectorMsg::Reply -> Client;
 }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff