use clap::App;
use clap::Arg;
use clap::ArgMatches;
use num_traits::cast::FromPrimitive;
use num_traits::cast::ToPrimitive;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FormatterResult;
use std::io::stdout;
use std::io::BufWriter;
use std::rc::Rc;
use std::str;
use std::str::FromStr;
use strum::IntoStaticStr;
use total_space::*;
const MAX_WORKERS: usize = 4;
declare_agent_indices! {WORKERS}
declare_agent_indices! {CLIENTS}
declare_agent_type_data! { WORKER_TYPE, WorkerState, SimpleModel }
declare_agent_type_data! { CLIENT_TYPE, ClientState, SimpleModel }
declare_agent_index! {SERVER}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, IntoStaticStr)]
enum Payload {
Need,
Task { client: usize },
Completed,
Result,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, IntoStaticStr)]
enum WorkerState {
Idle,
Working { client: usize },
}
impl_enum_data! {
WorkerState = Self::Idle,
"client" => "C",
"Idle" => "IDL",
"Working" => "WRK"
}
#[cfg(struct_instead_of_enum)]
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, IntoStaticStr)]
enum WorkerStateName {
Idle,
Working,
}
#[cfg(struct_instead_of_enum)]
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
struct WorkerState {
name: WorkerStateName,
client: usize,
}
#[cfg(struct_instead_of_enum)]
impl AgentState<WorkerState, Payload> for WorkerState {
fn invalid_because(&self, _instance: usize) -> Option<&'static str> {
if self.name == WorkerStateName::Idle && self.client != 0 {
Some("non-zero client when in the Idle state")
} else {
None
}
}
}
#[cfg(struct_instead_of_enum)]
impl_struct_data! {
WorkerState = WorkerState { name: Idle, client: 0 },
}
impl AgentState<WorkerState, Payload> for WorkerState {
fn max_in_flight_messages(&self, _instance: usize) -> Option<usize> {
Some(agents_count!(CLIENTS))
}
fn activity(&self, _instance: usize) -> Activity<Payload> {
match self {
Self::Idle => Activity::Passive,
Self::Working { .. } => Activity::Process1(Payload::Completed),
}
}
fn reaction(&self, _instance: usize, payload: &Payload) -> Reaction<Self, Payload> {
match (*self, *payload) {
(Self::Idle, Payload::Task { client }) => {
Reaction::Do1(Action::Change(Self::Working { client: client }))
}
(Self::Working { client }, Payload::Completed) => {
Reaction::Do1(Action::ChangeAndSend1(
Self::Idle,
Emit::Unordered(Payload::Result, agent_index!(CLIENTS[client])),
))
}
_ => Reaction::Unexpected,
}
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, IntoStaticStr)]
enum ServerState {
Active,
}
impl_enum_data! {
ServerState = Self::Active,
"Active" => "" }
impl ContainerOf1State<ServerState, WorkerState, Payload> for ServerState {
fn max_in_flight_messages(&self, _instance: usize, _workers: &[WorkerState]) -> Option<usize> {
Some(1)
}
fn is_deferring(&self, _instance: usize, _workers: &[WorkerState]) -> bool {
true
}
fn reaction(
&self,
_instance: usize,
payload: &Payload,
workers: &[WorkerState],
) -> Reaction<Self, Payload> {
match *payload {
Payload::Task { client } => {
Self::task_reaction(client, workers)
}
_ => Reaction::Unexpected,
}
}
}
impl ServerState {
fn task_reaction(client: usize, workers: &[WorkerState]) -> Reaction<Self, Payload> {
let mut actions: [Option<Action<Self, Payload>>; MAX_COUNT] = [None; MAX_COUNT];
let mut has_idle_worker = false;
for (worker, worker_state) in workers.iter().enumerate() {
match *worker_state {
WorkerState::Working {
client: worker_client,
} => {
if worker_client == client {
return Reaction::Unexpected;
}
}
WorkerState::Idle => {
actions[worker] = Some(Action::Send1(Emit::Immediate(
Payload::Task { client },
agent_index!(WORKERS[worker]),
)));
has_idle_worker = true;
}
}
}
if !has_idle_worker {
return Reaction::Defer;
}
Reaction::Do1Of(actions)
}
}
impl_enum_data! { Payload = Self::Result }
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, IntoStaticStr)]
enum ClientState {
Running,
Blocked,
}
impl_enum_data! {
ClientState = Self::Running,
"Running" => "RUN",
"Blocked" => "BLK"
}
impl AgentState<ClientState, Payload> for ClientState {
fn max_in_flight_messages(&self, _instance: usize) -> Option<usize> {
Some(1)
}
fn activity(&self, _instance: usize) -> Activity<Payload> {
match self {
Self::Running => Activity::Process1(Payload::Need),
Self::Blocked => Activity::Passive,
}
}
fn reaction(&self, instance: usize, payload: &Payload) -> Reaction<Self, Payload> {
match (self, payload) {
(Self::Running, Payload::Need) => Reaction::Do1(Action::ChangeAndSend1(
Self::Blocked,
Emit::Unordered(Payload::Task { client: instance }, agent_index!(SERVER)),
)),
(Self::Blocked, Payload::Result) => Reaction::Do1(Action::Change(Self::Running)),
_ => Reaction::Unexpected,
}
}
}
index_type! { StateId, u8 }
index_type! { MessageId, u8 }
index_type! { InvalidId, u8 }
index_type! { ConfigurationId, u32 }
type SimpleModel = Model<
StateId,
MessageId,
InvalidId,
ConfigurationId,
Payload,
6, 14, >;
fn example_model(arg_matches: &ArgMatches) -> SimpleModel {
let workers_arg = arg_matches.value_of("Workers").unwrap();
let workers_count = usize::from_str(workers_arg).expect("invalid number of workers");
assert!(
workers_count > 0,
"the number of workers must be at least 1"
);
assert!(
workers_count <= MAX_WORKERS,
"the number of workers can be at most {}",
MAX_WORKERS
);
let clients_arg = arg_matches.value_of("Clients").unwrap();
let clients_count = usize::from_str(clients_arg).expect("invalid number of clients");
assert!(
clients_count > 0,
"the number of clients must be at least 1"
);
assert!(
clients_count >= workers_count,
"the number of workers can be at most the number of clients"
);
let worker_type = Rc::new(AgentTypeData::<WorkerState, StateId, Payload>::new(
"W", Instances::Count(workers_count), None, ));
let mut server_type = Rc::new(ContainerOf1TypeData::<
ServerState,
WorkerState,
StateId,
Payload,
MAX_WORKERS,
>::new(
"S", Instances::Singleton, worker_type.clone(), worker_type.clone(), ));
Rc::get_mut(&mut server_type).unwrap().set_order(0, 10);
let mut client_type = Rc::new(AgentTypeData::<ClientState, StateId, Payload>::new(
"C", Instances::Count(clients_count), Some(server_type.clone()), ));
for client in 0..clients_count {
Rc::get_mut(&mut client_type)
.unwrap()
.set_order(client, 20 + client);
}
init_agent_type_data!(WORKER_TYPE, worker_type);
init_agent_type_data!(CLIENT_TYPE, client_type);
let size = model_size(
arg_matches, 100, );
init_agent_indices!(WORKERS, worker_type);
init_agent_index!(SERVER, server_type);
init_agent_indices!(CLIENTS, client_type);
let mut model = SimpleModel::with_validator(size, client_type, invalid_because);
model.add_condition(
"ALL_WORKERS_ARE_BUSY",
all_clients_are_blocked,
"All the workers are in the busy state",
);
model.add_condition(
"ALL_CLIENTS_ARE_BLOCKED",
all_workers_are_busy,
"All the clients are in the blocked blocked",
);
model.add_condition(
"DEFERRED_TASK",
has_deferred_task,
"There exists a task request message that is deferred by the server",
);
model
}
fn invalid_because(
_model: &SimpleModel,
_configuration: &<SimpleModel as MetaModel>::Configuration,
) -> Option<&'static str> {
None
}
fn all_workers_are_busy(
_model: &SimpleModel,
configuration: &<SimpleModel as MetaModel>::Configuration,
) -> bool {
agent_states_iter!(
configuration,
WORKER_TYPE,
all(|worker_state| worker_state == WorkerState::Idle)
)
}
fn all_clients_are_blocked(
_model: &SimpleModel,
configuration: &<SimpleModel as MetaModel>::Configuration,
) -> bool {
agent_states_iter!(
configuration,
CLIENT_TYPE,
all(|client_state| client_state == ClientState::Blocked)
)
}
fn has_deferred_task(
model: &SimpleModel,
configuration: &<SimpleModel as MetaModel>::Configuration,
) -> bool {
let task_messages_count = messages_iter!(
model,
configuration,
filter(|message| matches!(message.payload, Payload::Task { .. })).count()
);
let idle_workers_count = agent_states_iter!(
configuration,
WORKER_TYPE,
filter(|worker_state| *worker_state == WorkerState::Idle).count()
);
task_messages_count > idle_workers_count
}
assert_configuration_hash_entry_size!(SimpleModel, 32);
fn main() {
let arg_matches = add_clap(
App::new("clients-server")
.arg(
Arg::with_name("Workers")
.long("Workers")
.short("W")
.help("set the number of workers")
.default_value("1"),
)
.arg(
Arg::with_name("Clients")
.long("Clients")
.short("C")
.help("set the number of clients")
.default_value("1"),
),
)
.get_matches();
let mut model = example_model(&arg_matches);
let mut output = BufWriter::new(stdout());
model.do_clap(&arg_matches, &mut output);
}