|
@@ -23,8 +23,47 @@ pub(crate) struct ProtocolModel {
|
|
|
|
|
|
impl ProtocolModel {
|
|
|
pub(crate) fn new(def: Protocol) -> syn::Result<Self> {
|
|
|
- let actor_lookup = ActorLookup::new(def.actor_defs.iter().map(|x| x.as_ref()));
|
|
|
- let msg_lookup = MsgLookup::new(def.transitions.iter().map(|x| x.as_ref()));
|
|
|
+ let get_transitions = || def.transitions.iter().map(|x| x.as_ref());
|
|
|
+ let actor_lookup =
|
|
|
+ ActorLookup::new(def.actor_defs.iter().map(|x| x.as_ref()), get_transitions())?;
|
|
|
+ let mut is_client = HashMap::<Rc<Ident>, bool>::new();
|
|
|
+ // First mark all actors as not clients.
|
|
|
+ for actor_def in def.actor_defs.iter() {
|
|
|
+ is_client.insert(actor_def.actor.clone(), false);
|
|
|
+ }
|
|
|
+ // For every actor which is not spawned by another actor, mark it as a client if its
|
|
|
+ // initial state receives no messages, then mark all of the actors it spawns as clients.
|
|
|
+ for actor_def in def.actor_defs.iter() {
|
|
|
+ if !actor_lookup.parents(actor_def.actor.as_ref()).is_empty() {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ let init_state = actor_def.states.as_ref().first().unwrap();
|
|
|
+ let init_state_receives_no_msgs = def
|
|
|
+ .transitions
|
|
|
+ .iter()
|
|
|
+ .filter(|transition| {
|
|
|
+ transition.in_state.state_trait.as_ref() == init_state.as_ref()
|
|
|
+ })
|
|
|
+ .all(|transition| transition.in_msg().is_none());
|
|
|
+ if init_state_receives_no_msgs {
|
|
|
+ mark_all_progeny(&actor_lookup, &mut is_client, actor_def.actor.as_ref());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn mark_all_progeny(
|
|
|
+ actor_lookup: &ActorLookup,
|
|
|
+ is_client: &mut HashMap<Rc<Ident>, bool>,
|
|
|
+ actor_name: &Ident,
|
|
|
+ ) {
|
|
|
+ *is_client.get_mut(actor_name).unwrap() = true;
|
|
|
+ let children = actor_lookup.children(actor_name);
|
|
|
+ for child in children {
|
|
|
+ *is_client.get_mut(child).unwrap() = true;
|
|
|
+ mark_all_progeny(actor_lookup, is_client, child.as_ref());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let msg_lookup = MsgLookup::new(get_transitions());
|
|
|
let mut actors = HashMap::new();
|
|
|
for actor_def in def.actor_defs.iter() {
|
|
|
let actor_name = &actor_def.actor;
|
|
@@ -39,7 +78,12 @@ impl ProtocolModel {
|
|
|
.cloned();
|
|
|
(state_name.clone(), transitions)
|
|
|
});
|
|
|
- let actor = ActorModel::new(actor_def.clone(), &msg_lookup, transitions_by_state)?;
|
|
|
+ let actor = ActorModel::new(
|
|
|
+ actor_def.clone(),
|
|
|
+ &msg_lookup,
|
|
|
+ *is_client.get(&actor_def.actor).unwrap(),
|
|
|
+ transitions_by_state,
|
|
|
+ )?;
|
|
|
actors.insert(actor_name.clone(), actor);
|
|
|
}
|
|
|
Ok(Self {
|
|
@@ -81,12 +125,22 @@ impl ProtocolModel {
|
|
|
pub(crate) struct ActorModel {
|
|
|
#[allow(dead_code)]
|
|
|
def: Rc<ActorDef>,
|
|
|
+ /// Indicates if this actor is a client.
|
|
|
+ ///
|
|
|
+ /// A client is an actor which is not spawned by another actor (i.e. has no parents) and
|
|
|
+ /// whose initial state has no transition which receives a message, or which is spawned by a
|
|
|
+ /// client.
|
|
|
is_client: bool,
|
|
|
states: HashMap<Rc<Ident>, StateModel>,
|
|
|
}
|
|
|
|
|
|
impl ActorModel {
|
|
|
- fn new<S, T>(def: Rc<ActorDef>, messages: &MsgLookup, state_iter: S) -> syn::Result<Self>
|
|
|
+ fn new<S, T>(
|
|
|
+ def: Rc<ActorDef>,
|
|
|
+ messages: &MsgLookup,
|
|
|
+ is_client: bool,
|
|
|
+ state_iter: S,
|
|
|
+ ) -> syn::Result<Self>
|
|
|
where
|
|
|
S: IntoIterator<Item = (Rc<Ident>, T)>,
|
|
|
T: IntoIterator<Item = Rc<Transition>>,
|
|
@@ -95,10 +149,6 @@ impl ActorModel {
|
|
|
.into_iter()
|
|
|
.map(|(name, transitions)| (name, transitions.into_iter().collect()))
|
|
|
.collect();
|
|
|
- let is_client = transitions
|
|
|
- .values()
|
|
|
- .flatten()
|
|
|
- .any(|transition| transition.is_client());
|
|
|
let mut states = HashMap::new();
|
|
|
for (name, transitions) in transitions.into_iter() {
|
|
|
let state = StateModel::new(name.clone(), messages, transitions, is_client)?;
|
|
@@ -209,18 +259,24 @@ impl MethodModel {
|
|
|
msg_names.push('_');
|
|
|
msg_names.push_str(dest.msg.variant().pascal_to_snake().as_str());
|
|
|
}
|
|
|
- format_ident!("send_{msg_names}")
|
|
|
+ format_ident!("on_send_{msg_names}")
|
|
|
};
|
|
|
Ok(name)
|
|
|
}
|
|
|
|
|
|
fn new_inputs(def: &Transition, messages: &MsgLookup, part_of_client: bool) -> Vec<InputModel> {
|
|
|
let mut inputs = Vec::new();
|
|
|
+ let arg_kind = if def.is_client() {
|
|
|
+ InputKind::ByMutRef
|
|
|
+ } else {
|
|
|
+ InputKind::ByValue
|
|
|
+ };
|
|
|
if let Some(in_msg) = def.in_msg() {
|
|
|
let msg_info = messages.lookup(in_msg);
|
|
|
inputs.push(InputModel::new(
|
|
|
msg_info.msg_name().clone(),
|
|
|
msg_info.msg_type.clone(),
|
|
|
+ arg_kind,
|
|
|
))
|
|
|
}
|
|
|
if part_of_client {
|
|
@@ -229,6 +285,7 @@ impl MethodModel {
|
|
|
inputs.push(InputModel::new(
|
|
|
msg_info.msg_name().clone(),
|
|
|
msg_info.msg_type.clone(),
|
|
|
+ arg_kind,
|
|
|
))
|
|
|
}
|
|
|
}
|
|
@@ -290,16 +347,30 @@ impl GetSpan for MethodModel {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[cfg_attr(test, derive(Debug))]
|
|
|
+#[derive(Clone)]
|
|
|
+enum InputKind {
|
|
|
+ ByValue,
|
|
|
+ ByMutRef,
|
|
|
+}
|
|
|
+
|
|
|
+impl Copy for InputKind {}
|
|
|
+
|
|
|
#[cfg_attr(test, derive(Debug))]
|
|
|
pub(crate) struct InputModel {
|
|
|
name: Ident,
|
|
|
arg_type: Rc<TokenStream>,
|
|
|
+ arg_kind: InputKind,
|
|
|
}
|
|
|
|
|
|
impl InputModel {
|
|
|
- fn new(type_name: Rc<Ident>, arg_type: Rc<TokenStream>) -> Self {
|
|
|
+ fn new(type_name: Rc<Ident>, arg_type: Rc<TokenStream>, arg_kind: InputKind) -> Self {
|
|
|
let name = format_ident!("{}_arg", type_name.to_string().pascal_to_snake());
|
|
|
- Self { name, arg_type }
|
|
|
+ Self {
|
|
|
+ name,
|
|
|
+ arg_type,
|
|
|
+ arg_kind,
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -307,7 +378,11 @@ impl ToTokens for InputModel {
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
|
let name = &self.name;
|
|
|
let arg_type = self.arg_type.as_ref();
|
|
|
- tokens.extend(quote! { #name : #arg_type })
|
|
|
+ let modifier = match self.arg_kind {
|
|
|
+ InputKind::ByValue => quote! {},
|
|
|
+ InputKind::ByMutRef => quote! { &mut },
|
|
|
+ };
|
|
|
+ tokens.extend(quote! { #name : #modifier #arg_type })
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -385,30 +460,94 @@ pub(crate) enum OutputKind {
|
|
|
},
|
|
|
}
|
|
|
|
|
|
+/// A type used to query information about actors, states, and their relationships.
|
|
|
pub(crate) struct ActorLookup {
|
|
|
+ /// A map from an actor name to the set of states which are part of that actor.
|
|
|
actor_states: HashMap<Rc<Ident>, HashSet<Rc<Ident>>>,
|
|
|
+ #[allow(dead_code)]
|
|
|
+ /// A map from a state name to the actor name which that state is a part of.
|
|
|
+ actors_by_state: HashMap<Rc<Ident>, Rc<Ident>>,
|
|
|
+ /// A map from an actor name to the set of actor names which spawn it.
|
|
|
+ parents: HashMap<Rc<Ident>, HashSet<Rc<Ident>>>,
|
|
|
+ /// A map from an actor name to the set of actors names which it spawns.
|
|
|
+ children: HashMap<Rc<Ident>, HashSet<Rc<Ident>>>,
|
|
|
}
|
|
|
|
|
|
impl ActorLookup {
|
|
|
- fn new<'a>(actor_defs: impl IntoIterator<Item = &'a ActorDef>) -> Self {
|
|
|
+ fn new<'a, A, T>(actor_defs: A, transitions: T) -> syn::Result<Self>
|
|
|
+ where
|
|
|
+ A: IntoIterator<Item = &'a ActorDef>,
|
|
|
+ T: IntoIterator<Item = &'a Transition>,
|
|
|
+ {
|
|
|
let mut actor_states = HashMap::new();
|
|
|
- for actor_def in actor_defs.into_iter() {
|
|
|
+ let mut actors_by_state = HashMap::new();
|
|
|
+ let mut parents = HashMap::new();
|
|
|
+ let mut children = HashMap::new();
|
|
|
+ for actor_def in actor_defs {
|
|
|
let mut states = HashSet::new();
|
|
|
for state in actor_def.states.as_ref().iter() {
|
|
|
states.insert(state.clone());
|
|
|
+ actors_by_state.insert(state.clone(), actor_def.actor.clone());
|
|
|
+ }
|
|
|
+ let actor_name = &actor_def.actor;
|
|
|
+ actor_states.insert(actor_name.clone(), states);
|
|
|
+ parents.insert(actor_name.clone(), HashSet::new());
|
|
|
+ children.insert(actor_name.clone(), HashSet::new());
|
|
|
+ }
|
|
|
+
|
|
|
+ for transition in transitions {
|
|
|
+ let in_state = transition.in_state.state_trait.as_ref();
|
|
|
+ let parent = actors_by_state
|
|
|
+ .get(in_state)
|
|
|
+ .ok_or_else(|| syn::Error::new(in_state.span(), error::msgs::UNDECLARED_STATE))?;
|
|
|
+ // The first output state is skipped because the current actor is transitioning to it,
|
|
|
+ // its not creating a new actor.
|
|
|
+ for out_state in transition.out_states.as_ref().iter().skip(1) {
|
|
|
+ let out_state = out_state.state_trait.as_ref();
|
|
|
+ let child = actors_by_state.get(out_state).ok_or_else(|| {
|
|
|
+ syn::Error::new(out_state.span(), error::msgs::UNDECLARED_STATE)
|
|
|
+ })?;
|
|
|
+ parents
|
|
|
+ .entry(child.clone())
|
|
|
+ .or_insert_with(HashSet::new)
|
|
|
+ .insert(parent.clone());
|
|
|
+ children
|
|
|
+ .entry(parent.clone())
|
|
|
+ .or_insert_with(HashSet::new)
|
|
|
+ .insert(child.clone());
|
|
|
}
|
|
|
- actor_states.insert(actor_def.actor.clone(), states);
|
|
|
}
|
|
|
- Self { actor_states }
|
|
|
+
|
|
|
+ Ok(Self {
|
|
|
+ actor_states,
|
|
|
+ actors_by_state,
|
|
|
+ parents,
|
|
|
+ children,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
+ const UNKNOWN_ACTOR_ERR: &str =
|
|
|
+ "Unknown actor. This indicates there is a bug in the btproto crate.";
|
|
|
+
|
|
|
/// Returns the set of states associated with the given actor.
|
|
|
///
|
|
|
/// This method **panics** if you call it with a non-existent actor name.
|
|
|
pub(crate) fn actor_states(&self, actor_name: &Ident) -> &HashSet<Rc<Ident>> {
|
|
|
- self.actor_states.get(actor_name).unwrap_or_else(|| {
|
|
|
- panic!("Unknown actor. This indicates there is a bug in the btproto crate.")
|
|
|
- })
|
|
|
+ self.actor_states
|
|
|
+ .get(actor_name)
|
|
|
+ .unwrap_or_else(|| panic!("actor_states: {}", Self::UNKNOWN_ACTOR_ERR))
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn parents(&self, actor_name: &Ident) -> &HashSet<Rc<Ident>> {
|
|
|
+ self.parents
|
|
|
+ .get(actor_name)
|
|
|
+ .unwrap_or_else(|| panic!("parents: {}", Self::UNKNOWN_ACTOR_ERR))
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn children(&self, actor_name: &Ident) -> &HashSet<Rc<Ident>> {
|
|
|
+ self.children
|
|
|
+ .get(actor_name)
|
|
|
+ .unwrap_or_else(|| panic!("children: {}", Self::UNKNOWN_ACTOR_ERR))
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -767,4 +906,103 @@ mod tests {
|
|
|
assert_eq!(1, outputs.iter().filter(|is_reply| **is_reply).count());
|
|
|
assert_eq!(1, outputs.iter().filter(|is_reply| !*is_reply).count());
|
|
|
}
|
|
|
+
|
|
|
+ fn simple_client_server_proto() -> Protocol {
|
|
|
+ Protocol::new(
|
|
|
+ NameDef::new("IsClientTest"),
|
|
|
+ [
|
|
|
+ ActorDef::new("server", ["Server"]),
|
|
|
+ ActorDef::new("client", ["Client"]),
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ Transition::new(
|
|
|
+ State::new("Client", []),
|
|
|
+ None,
|
|
|
+ [State::new("End", [])],
|
|
|
+ [Dest::new(
|
|
|
+ DestinationState::Service(State::new("Server", [])),
|
|
|
+ Message::new("Msg", false, []),
|
|
|
+ )],
|
|
|
+ ),
|
|
|
+ Transition::new(
|
|
|
+ State::new("Server", []),
|
|
|
+ Some(Message::new("Msg", false, [])),
|
|
|
+ [State::new("End", [])],
|
|
|
+ [],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn is_client_false_for_server() {
|
|
|
+ let input = simple_client_server_proto();
|
|
|
+
|
|
|
+ let actual = ProtocolModel::new(input).unwrap();
|
|
|
+
|
|
|
+ let server = actual.actors.get(&format_ident!("server")).unwrap();
|
|
|
+ assert!(!server.is_client());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn is_client_true_for_client() {
|
|
|
+ let input = simple_client_server_proto();
|
|
|
+
|
|
|
+ let actual = ProtocolModel::new(input).unwrap();
|
|
|
+
|
|
|
+ let client = actual.actors.get(&format_ident!("client")).unwrap();
|
|
|
+ assert!(client.is_client());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn is_client_false_for_worker() {
|
|
|
+ let input = Protocol::new(
|
|
|
+ NameDef::new("IsClientTest"),
|
|
|
+ [
|
|
|
+ ActorDef::new("server", ["Listening"]),
|
|
|
+ ActorDef::new("worker", ["Working"]),
|
|
|
+ ActorDef::new("client", ["Unregistered", "Registered"]),
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ Transition::new(
|
|
|
+ State::new("Unregistered", []),
|
|
|
+ None,
|
|
|
+ [State::new("Registered", [])],
|
|
|
+ [Dest::new(
|
|
|
+ DestinationState::Service(State::new("Listening", [])),
|
|
|
+ Message::new("Register", false, ["Registered"]),
|
|
|
+ )],
|
|
|
+ ),
|
|
|
+ Transition::new(
|
|
|
+ State::new("Listening", []),
|
|
|
+ Some(Message::new("Register", false, ["Registered"])),
|
|
|
+ [
|
|
|
+ State::new("Listening", []),
|
|
|
+ State::new("Working", ["Registered"]),
|
|
|
+ ],
|
|
|
+ [],
|
|
|
+ ),
|
|
|
+ Transition::new(
|
|
|
+ State::new("Working", ["Registered"]),
|
|
|
+ None,
|
|
|
+ [State::new("End", [])],
|
|
|
+ [Dest::new(
|
|
|
+ DestinationState::Individual(State::new("Registered", [])),
|
|
|
+ Message::new("Completed", false, []),
|
|
|
+ )],
|
|
|
+ ),
|
|
|
+ Transition::new(
|
|
|
+ State::new("Registered", []),
|
|
|
+ Some(Message::new("Completed", false, [])),
|
|
|
+ [State::new("End", [])],
|
|
|
+ [],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+
|
|
|
+ let actual = ProtocolModel::new(input).unwrap();
|
|
|
+
|
|
|
+ let worker = actual.actors.get(&format_ident!("worker")).unwrap();
|
|
|
+ assert!(!worker.is_client());
|
|
|
+ }
|
|
|
}
|