use crate::evo;
use process_anywhere::{Computer, Process};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::{self, BufRead, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
fn timestamp() -> String {
use chrono::{SecondsFormat, Utc};
let rfc3339 = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, false);
rfc3339.replacen('T', " ", 1)
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct EnvironmentSpec {
#[serde(skip)]
pub spec: PathBuf,
pub name: String,
pub path: PathBuf,
#[serde(default)]
pub description: String,
pub body_types: Vec<BodySpec>,
#[serde(default)]
pub settings: Vec<SettingsSpec>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl EnvironmentSpec {
pub fn new(path: impl AsRef<Path>) -> Self {
let path = path.as_ref(); let spec = std::fs::read_to_string(path).unwrap_or_else(|err| panic!("error reading file {path:?} {err}"));
let mut this: EnvironmentSpec =
serde_json::from_str(&spec).unwrap_or_else(|err| panic!("error parsing JSON file {path:?} {err}",));
this.spec = path.into();
this.normalize_path();
this
}
fn normalize_path(&mut self) {
if self.path.is_relative() {
self.path = self.spec.parent().unwrap().join(&self.path);
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct BodySpec {
pub name: String,
#[serde(default)]
pub description: String,
pub sensors: Vec<InterfaceSpec>,
pub motors: Vec<InterfaceSpec>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InterfaceSpec {
pub id: u64,
pub name: String,
#[serde(default)]
pub description: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "type")]
#[serde(deny_unknown_fields)]
pub enum SettingsSpec {
#[serde(alias = "float")]
Real {
name: String,
#[serde(default)]
description: String,
minimum: f64,
maximum: f64,
default: f64,
},
#[serde(alias = "int")]
Integer {
name: String,
#[serde(default)]
description: String,
minimum: i64,
maximum: i64,
default: i64,
},
#[serde(alias = "bool")]
Boolean {
name: String,
#[serde(default)]
description: String,
default: bool,
},
#[serde(alias = "enum")]
Enumeration {
name: String,
#[serde(default)]
description: String,
values: Vec<String>,
default: String,
},
#[serde(alias = "str")]
String {
name: String,
#[serde(default)]
description: String,
default: String,
},
}
impl SettingsSpec {
pub fn name(&self) -> &str {
match self {
Self::Real { name, .. }
| Self::Integer { name, .. }
| Self::Boolean { name, .. }
| Self::Enumeration { name, .. }
| Self::String { name, .. } => name,
}
}
pub fn description(&self) -> &str {
match self {
Self::Real { description, .. }
| Self::Integer { description, .. }
| Self::Boolean { description, .. }
| Self::Enumeration { description, .. }
| Self::String { description, .. } => description,
}
}
pub fn r#type(&self) -> &str {
match self {
Self::Real { .. } => "Real",
Self::Integer { .. } => "Integer",
Self::Boolean { .. } => "Boolean",
Self::Enumeration { .. } => "Enumeration",
Self::String { .. } => "String",
}
}
pub fn default(&self) -> String {
match self {
Self::Real { default, .. } => default.to_string(),
Self::Integer { default, .. } => default.to_string(),
Self::Boolean { default, .. } => default.to_string(),
Self::Enumeration { default, .. } => default.to_string(),
Self::String { default, .. } => default.to_string(),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub enum Mode {
Headless,
#[default]
Graphical,
}
impl std::fmt::Display for Mode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Mode::Graphical => write!(f, "graphical"),
Mode::Headless => write!(f, "headless"),
}
}
}
impl FromStr for Mode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().to_ascii_lowercase();
match s.as_str() {
"graphical" => Ok(Mode::Graphical),
"headless" => Ok(Mode::Headless),
_ => Err(format!("expected either \"graphical\" or \"headless\", got \"{s}\"")),
}
}
}
impl From<bool> for Mode {
fn from(mode: bool) -> Self {
if mode {
Mode::Graphical
} else {
Mode::Headless
}
}
}
pub fn get_args() -> (EnvironmentSpec, Mode, HashMap<String, String>) {
let mut arg_iter = std::env::args();
let _program = arg_iter.next();
let spec_file = arg_iter.next();
let mode = arg_iter.next();
let mut settings: Vec<String> = arg_iter.collect();
let Some(spec_file) = spec_file else {
panic!("Argument Error: missing environment specification")
};
let spec_file = Path::new(&spec_file)
.canonicalize()
.unwrap_or_else(|err| panic!("File Error: {err}: {spec_file:?}"));
let spec_data =
std::fs::read_to_string(&spec_file).unwrap_or_else(|err| panic!("File Error: {err}: {spec_file:?}"));
let mut env_spec: EnvironmentSpec =
serde_json::from_str(&spec_data).unwrap_or_else(|err| panic!("JSON Decode Error: {err}: {spec_file:?}"));
env_spec.spec = spec_file;
let mode = if let Some(mode) = mode {
mode.parse().unwrap_or_else(|err| panic!("Argument Error: {err}"))
} else {
Mode::default()
};
let mut defaults: HashMap<String, _> = env_spec
.settings
.iter()
.map(|item| (item.name().to_string(), item.default()))
.collect();
let mut settings = settings.chunks_exact_mut(2);
for chunk in &mut settings {
let item = std::mem::take(&mut chunk[0]);
let value = std::mem::take(&mut chunk[1]);
if !defaults.contains_key(&item) {
panic!("Argument Error: unexpected parameter \"{item}\"")
}
defaults.insert(item, value);
}
if !settings.into_remainder().is_empty() {
panic!("Argument Error: odd number of settings, expected key-value pairs");
}
(env_spec, mode, defaults)
}
pub fn input() -> Result<(Individual, Box<[u8]>), io::Error> {
io::stdout().flush()?;
let mut line = String::new();
let stdin = &mut io::stdin().lock();
stdin.read_line(&mut line)?;
let metadata: Individual = serde_json::from_str(&line)?;
let binary = crate::read_bytes(stdin, metadata.genome)?;
Ok((metadata, binary))
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Individual {
pub name: String,
#[serde(default)]
pub population: String,
#[serde(default)]
pub parents: Vec<String>,
pub controller: Vec<String>,
pub genome: usize,
#[serde(flatten)]
pub extra: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
#[non_exhaustive]
enum JsonMessage {
Spawn {
#[serde(rename = "Spawn")]
population: String,
},
Mate {
#[serde(rename = "Mate")]
parents: [String; 2],
},
Score {
#[serde(rename = "Score")]
value: String,
#[serde(default)]
name: String,
},
Telemetry {
#[serde(rename = "Telemetry")]
info: HashMap<String, String>,
#[serde(default)]
name: String,
},
Death {
#[serde(rename = "Death", default)]
name: String,
},
}
pub fn spawn(population: &str) {
println!(r#"{{"Spawn":"{}"}}"#, population);
}
pub fn mate(parent1: &str, parent2: &str) {
println!(r#"{{"Mate":["{parent1}","{parent2}"]}}"#);
}
pub fn score(individual: &str, value: &str) {
println!(r#"{{"Score":"{value}","name":"{}"}}"#, individual);
}
pub fn telemetry(individual: &str, info: &HashMap<String, String>) {
let mut json = String::new();
for (key, value) in info {
json.push('"');
json.push_str(key);
json.push('"');
json.push(':');
json.push('"');
json.push_str(value);
json.push('"');
json.push(',');
}
json.pop(); println!(r#"{{"Telemetry":{{{json}}},"name":"{}"}}"#, individual);
}
pub fn death(individual: &str) {
println!(r#"{{"Death":"{}"}}"#, individual);
}
pub struct Environment {
env_spec: Arc<EnvironmentSpec>,
mode: Mode,
settings: HashMap<String, String>,
process: Box<Process>,
outstanding: HashMap<String, Box<evo::Individual>>,
stderr: Box<dyn Write>,
}
impl Environment {
pub fn new(
computer: Arc<Computer>,
env_spec: Arc<EnvironmentSpec>,
mode: Mode,
settings: HashMap<String, String>,
stderr: Option<Box<dyn Write>>,
) -> Self {
let stderr = stderr.unwrap_or_else(|| Box::new(io::stderr()));
let mut command = Vec::with_capacity(3 + 2 * env_spec.settings.len());
command.push(env_spec.path.to_str().unwrap().to_string());
command.push(env_spec.spec.to_str().unwrap().to_string());
command.push(mode.to_string());
for arg in env_spec.settings.iter() {
command.push(arg.name().to_string());
if let Some(value) = settings.get(arg.name()) {
command.push(value.to_string());
} else {
command.push(arg.default());
}
}
let command_str: Vec<&str> = command.iter().map(String::as_str).collect();
Self {
env_spec,
mode,
settings,
process: computer.exec(&command_str).unwrap(),
outstanding: HashMap::new(),
stderr,
}
}
pub fn is_alive(&mut self) -> bool {
self.process.is_alive().unwrap_or(false)
}
fn forward_stderr(&mut self) -> Result<(), process_anywhere::Error> {
let data = self.process.error_bytes()?;
if !data.is_empty() {
self.stderr.write_all(&data)?;
}
Ok(())
}
pub fn get_env_spec(&self) -> &EnvironmentSpec {
&self.env_spec
}
pub fn get_mode(&self) -> Mode {
self.mode
}
pub fn get_settings(&self) -> &HashMap<String, String> {
&self.settings
}
pub fn get_outstanding(&self) -> &HashMap<String, Box<evo::Individual>> {
&self.outstanding
}
pub fn get_outstanding_mut(&mut self, name: &str) -> Option<&mut evo::Individual> {
self.outstanding.get_mut(name).map(Box::as_mut)
}
pub fn quit(&mut self) {
self.forward_stderr().unwrap();
self.process.close_stdin().unwrap();
}
pub fn birth(&mut self, mut individual: evo::Individual, phenome: &[u8]) {
#[derive(Serialize)]
struct Metadata<'a> {
name: &'a str,
population: &'a str,
parents: &'a [String],
controller: &'a [String],
genome: usize,
}
let metadata = Metadata {
name: &individual.name,
population: &individual.population,
parents: &individual.parents,
controller: &individual.controller,
genome: phenome.len(),
};
let mut message = serde_json::to_vec(&metadata).unwrap();
message.push(b'\n');
message.extend_from_slice(phenome);
self.process.send_bytes(&message).unwrap();
individual.birth_date = timestamp();
let name_conflict = self
.outstanding
.insert(individual.name.to_string(), Box::new(individual));
debug_assert!(name_conflict.is_none());
}
pub fn poll(&mut self) -> Result<Option<Message>, process_anywhere::Error> {
self.forward_stderr()?;
let Some(line) = self.process.recv_line()? else {
return Ok(None);
};
let mut message: JsonMessage = serde_json::from_str(&line).unwrap();
match &mut message {
JsonMessage::Spawn { population } => {
if population.is_empty() {
if self.env_spec.body_types.len() == 1 {
*population = self.env_spec.body_types[0].name.to_string();
} else {
panic!("missing population");
}
}
}
JsonMessage::Score { name, .. } | JsonMessage::Telemetry { name, .. } | JsonMessage::Death { name, .. } => {
if name.is_empty() {
if self.outstanding.len() == 1 {
*name = self.outstanding.keys().next().unwrap().to_string();
} else {
panic!("missing name");
}
}
}
_ => {}
}
match &message {
JsonMessage::Spawn { population } => {
assert!(self.env_spec.body_types.iter().any(|pop| &pop.name == population));
}
JsonMessage::Mate { parents } => {
assert!(self.outstanding.contains_key(&parents[0]));
assert!(self.outstanding.contains_key(&parents[1]));
}
JsonMessage::Score { name, .. } | JsonMessage::Telemetry { name, .. } | JsonMessage::Death { name, .. } => {
assert!(self.outstanding.contains_key(name));
}
}
match message {
JsonMessage::Score { name, value } => {
let individual = self.outstanding.get_mut(&name).unwrap();
individual.score = Some(value);
Ok(None) }
JsonMessage::Telemetry { name, mut info } => {
let individual = self.outstanding.get_mut(&name).unwrap();
for (k, v) in info.drain() {
individual.telemetry.insert(k, v);
}
Ok(None) }
JsonMessage::Death { name } => {
let mut individual = self.outstanding.remove(&name).unwrap();
individual.death_date = timestamp();
Ok(Some(Message::Death { individual }))
}
JsonMessage::Spawn { population } => Ok(Some(Message::Spawn { population })),
JsonMessage::Mate { parents } => Ok(Some(Message::Mate { parents })),
}
}
}
impl Drop for Environment {
fn drop(&mut self) {
let _ = self.forward_stderr();
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Message {
Spawn { population: String },
Mate { parents: [String; 2] },
Death { individual: Box<evo::Individual> },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_example_environment_specifications() {
let mut examples_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
examples_dir.pop();
examples_dir.push("examples");
for entry in examples_dir.read_dir().unwrap() {
let Ok(entry) = entry else { continue };
if entry.path().is_dir() {
for file in entry.path().read_dir().unwrap() {
let Ok(file) = file else { continue };
if let Some(ext) = file.path().extension() {
if ext == "env" {
println!("EnvironmentSpec::new(\"{}\")", file.path().display());
EnvironmentSpec::new(file.path());
}
}
}
}
}
}
#[test]
fn send_string() {
assert_eq!(
serde_json::to_string(&Individual {
name: "1234".to_string(),
population: "pop1".to_string(),
parents: vec!["1020".to_string(), "1077".to_string()],
controller: vec!["/usr/bin/q".to_string()],
genome: 456789,
extra: Default::default(),
})
.unwrap(),
r#"{"name":"1234","population":"pop1","parents":["1020","1077"],"controller":["/usr/bin/q"],"genome":456789}"#
);
}
#[test]
fn recv_string() {
assert_eq!(
serde_json::to_string(&JsonMessage::Spawn {
population: String::new()
})
.unwrap(),
r#"{"Spawn":""}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Spawn {
population: "pop1".to_string()
})
.unwrap(),
r#"{"Spawn":"pop1"}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Mate {
parents: ["parent1".to_string(), "parent2".to_string()]
})
.unwrap(),
r#"{"Mate":["parent1","parent2"]}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Score {
name: "xyz".to_string(),
value: "-3.7".to_string(),
})
.unwrap(),
r#"{"Score":"-3.7","name":"xyz"}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Telemetry {
name: "abcd".to_string(),
info: HashMap::new()
})
.unwrap(),
r#"{"Telemetry":{},"name":"abcd"}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Death { name: String::new() }).unwrap(),
r#"{"Death":""}"#
);
assert_eq!(
serde_json::to_string(&JsonMessage::Death {
name: "abc".to_string()
})
.unwrap(),
r#"{"Death":"abc"}"#
);
}
}