2
0

3 Ревизии c5dcdd2886 ... 5f46436994

Автор SHA1 Съобщение Дата
  Matthew Carr 5f46436994 Got both of the runtime tests to pass with the generated code. преди 2 години
  Matthew Carr 3506942bcd Added a return value to client trait methods. преди 2 години
  Matthew Carr cbc5e22ed9 Started implementing code generation for client handles. преди 2 години

+ 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 )* ','? ;

Файловите разлики са ограничени, защото са твърде много
+ 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;
 }

Някои файлове не бяха показани, защото твърде много файлове са промени