aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-03-07 09:12:47 -0700
committerMelody Horn <melody@boringcactus.com>2021-03-07 09:12:47 -0700
commit36d5ff553a1d65d70da5251b8865d62294c929f6 (patch)
tree065618d7f6c0a407b844842c310919010384beca
parent28a8fb462fa88da300c6cf016ed3c70c402b8dba (diff)
downloadhope-36d5ff553a1d65d70da5251b8865d62294c929f6.tar.gz
hope-36d5ff553a1d65d70da5251b8865d62294c929f6.zip
tear it all up and implement super basic actors
-rw-r--r--Cargo.lock10
-rw-r--r--Cargo.toml1
-rw-r--r--src/actor.rs93
-rw-r--r--src/basic_actors.rs136
-rw-r--r--src/main.rs79
-rw-r--r--src/number.rs39
-rw-r--r--src/system.rs31
-rw-r--r--src/world.rs47
8 files changed, 400 insertions, 36 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5cffe7d..56b23b8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -230,6 +230,7 @@ name = "hope"
version = "0.0.0"
dependencies = [
"minifb",
+ "uuid",
]
[[package]]
@@ -639,6 +640,15 @@ dependencies = [
]
[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index ae5be6a..46d806b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,3 +10,4 @@ license-file = "LICENSE.txt"
[dependencies]
minifb = "0.19.2"
+uuid = { version = "0.8.2", features = ["v4"] }
diff --git a/src/actor.rs b/src/actor.rs
index 3b03fa6..e83c003 100644
--- a/src/actor.rs
+++ b/src/actor.rs
@@ -1,4 +1,14 @@
+use std::collections::HashMap;
+use std::sync::mpsc::{Receiver, Sender};
+use std::thread;
+
+use uuid::Uuid;
+
use crate::number::Number;
+use crate::world::World;
+
+#[derive(Clone, Hash, Copy, Eq, PartialEq)]
+pub struct ActorId(Uuid);
#[derive(Clone)]
pub enum Type {
@@ -18,12 +28,91 @@ pub enum Type {
}
#[derive(Clone)]
+pub enum Value {
+ String(String),
+ Record(Vec<(String, Value)>),
+ Number(Number),
+ List(Vec<Value>),
+}
+
+#[derive(Clone)]
pub struct Slot {
pub name: String,
pub r#type: Type,
}
pub trait Actorful {
- fn inputs() -> Vec<Slot>;
- fn outputs() -> Vec<Slot>;
+ fn inputs(&self) -> Vec<Slot>;
+ fn outputs(&self) -> Vec<Slot>;
+ fn launch(&self, input_channels: HashMap<String, Receiver<Value>>, output_channels: HashMap<String, Sender<Value>>, world: &mut World);
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send>;
+}
+
+#[derive(Clone)]
+pub enum ProducerSlotID {
+ Input(String),
+ ChildOutput(ActorId, String),
+}
+
+#[derive(Clone)]
+pub enum ConsumerSlotID {
+ Output(String),
+ ChildInput(ActorId, String),
+}
+
+#[derive(Clone, Default)]
+pub struct ProgrammableActor {
+ pub inputs: Vec<Slot>,
+ pub outputs: Vec<Slot>,
+ pub children: HashMap<ActorId, String>,
+ pub cables: Vec<(ProducerSlotID, ConsumerSlotID)>,
+}
+
+impl ProgrammableActor {
+ pub fn add_child(&mut self, actor_type: String) -> ActorId {
+ let id = ActorId(Uuid::new_v4());
+ self.children.insert(id, actor_type);
+ id
+ }
+
+ pub fn add_cable(&mut self, in_slot: ProducerSlotID, out_slot: ConsumerSlotID) {
+ self.cables.push((in_slot, out_slot));
+ }
+}
+
+impl Actorful for ProgrammableActor {
+ fn inputs(&self) -> Vec<Slot> {
+ self.inputs.clone()
+ }
+
+ fn outputs(&self) -> Vec<Slot> {
+ self.outputs.clone()
+ }
+
+ fn launch(&self, mut input_channels: HashMap<String, Receiver<Value>>, mut output_channels: HashMap<String, Sender<Value>>, world: &mut World) {
+ let mut child_channels = HashMap::new();
+ for (&child_id, child_type) in &self.children {
+ let (_, child_inputs, child_outputs) = world.spawn_actor(child_type);
+ child_channels.insert(child_id, (child_inputs, child_outputs));
+ }
+ for (source, dest) in &self.cables {
+ let source = match source {
+ ProducerSlotID::Input(name) => input_channels.remove(name).unwrap(),
+ ProducerSlotID::ChildOutput(id, name) => child_channels[id].1.remove(name).unwrap()
+ };
+ let dest = match dest {
+ ConsumerSlotID::Output(name) => output_channels.remove(name).unwrap(),
+ ConsumerSlotID::ChildInput(id, name) => child_channels[id].0.remove(name).unwrap()
+ };
+ thread::spawn(move || {
+ for val in source.iter() {
+ dest.send(val).unwrap()
+ }
+ });
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send> {
+ Box::new(self.clone())
+ }
}
diff --git a/src/basic_actors.rs b/src/basic_actors.rs
new file mode 100644
index 0000000..6f514d6
--- /dev/null
+++ b/src/basic_actors.rs
@@ -0,0 +1,136 @@
+use std::collections::HashMap;
+use std::sync::mpsc::{Sender, Receiver};
+
+use crate::actor::{Value, Type, Actorful, Slot};
+use crate::number::Number;
+use crate::world::World;
+
+#[derive(Clone)]
+pub struct Constant {
+ pub r#type: Type,
+ pub value: Value,
+}
+
+impl Actorful for Constant {
+ fn inputs(&self) -> Vec<Slot> {
+ vec![]
+ }
+
+ fn outputs(&self) -> Vec<Slot> {
+ vec![Slot { name: "Value".to_string(), r#type: self.r#type.clone() }]
+ }
+
+ fn launch(&self, _input_channels: HashMap<String, Receiver<Value>>, output_channels: HashMap<String, Sender<Value>>, _world: &mut World) {
+ loop {
+ output_channels["Value"].send(self.value.clone()).unwrap()
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send> {
+ Box::new(self.clone())
+ }
+}
+
+#[derive(Clone)]
+pub struct Add;
+
+impl Actorful for Add {
+ fn inputs(&self) -> Vec<Slot> {
+ vec![
+ Slot { name: "N1".to_string(), r#type: Type::AnyNumber },
+ Slot { name: "N2".to_string(), r#type: Type::AnyNumber },
+ ]
+ }
+
+ fn outputs(&self) -> Vec<Slot> {
+ vec![Slot { name: "Result".to_string(), r#type: Type::AnyNumber }]
+ }
+
+ fn launch(&self, mut input_channels: HashMap<String, Receiver<Value>>, output_channels: HashMap<String, Sender<Value>>, _world: &mut World) {
+ let n1 = input_channels.remove("N1").unwrap();
+ let n2 = input_channels.remove("N2").unwrap();
+ for (n1, n2) in n1.iter().zip(n2.iter()) {
+ if let (Value::Number(n1), Value::Number(n2)) = (n1, n2) {
+ output_channels["Result"].send(Value::Number(n1 + n2)).unwrap();
+ }
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send> {
+ Box::new(self.clone())
+ }
+}
+
+#[derive(Clone)]
+pub struct RepeatValue {
+ pub(crate) r#type: Type,
+}
+
+impl Actorful for RepeatValue {
+ fn inputs(&self) -> Vec<Slot> {
+ let nonnegative_integer = Type::NumberInRange {
+ min: Some(Number::from(0)),
+ max: None,
+ };
+ vec![
+ Slot { name: "Value".to_string(), r#type: self.r#type.clone() },
+ Slot { name: "Count".to_string(), r#type: nonnegative_integer },
+ ]
+ }
+
+ fn outputs(&self) -> Vec<Slot> {
+ vec![Slot { name: "List".to_string(), r#type: Type::List { contents: Box::new(self.r#type.clone()) } }]
+ }
+
+ fn launch(&self, mut input_channels: HashMap<String, Receiver<Value>>, output_channels: HashMap<String, Sender<Value>>, _world: &mut World) {
+ let value = input_channels.remove("Value").unwrap();
+ let count = input_channels.remove("Count").unwrap();
+ for (value, count) in value.iter().zip(count.iter()) {
+ if let Value::Number(count) = count {
+ // TODO figure out what a smart thing to do would be here instead
+ // this API design is deliberately suboptimal because i'd like to not do it
+ let count: usize = format!("{}", count).parse().unwrap();
+ let vec = vec![value; count];
+ output_channels["List"].send(Value::List(vec)).unwrap();
+ }
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send> {
+ Box::new(self.clone())
+ }
+}
+
+#[derive(Clone)]
+pub struct DeconstructRecord {
+ pub record_type: Type,
+}
+
+impl Actorful for DeconstructRecord {
+ fn inputs(&self) -> Vec<Slot> {
+ vec![Slot { name: "Record".to_string(), r#type: self.record_type.clone() }]
+ }
+
+ fn outputs(&self) -> Vec<Slot> {
+ if let Type::Record { name: _, fields } = &self.record_type {
+ fields.clone()
+ } else {
+ panic!("bruh that's the wrong goddamn type")
+ }
+ }
+
+ fn launch(&self, mut input_channels: HashMap<String, Receiver<Value>>, output_channels: HashMap<String, Sender<Value>>, _world: &mut World) {
+ let record = input_channels.remove("Record").unwrap();
+ for record in record.iter() {
+ if let Value::Record(fields) = record {
+ for (label, value) in fields {
+ output_channels[&label].send(value).unwrap();
+ }
+ }
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Actorful + Send> {
+ Box::new(self.clone())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 0f87d5b..b4adbd3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,9 @@ use std::time::Duration;
use minifb::{Key, Window, WindowOptions};
mod actor;
+mod basic_actors;
mod number;
-mod system;
+mod world;
const WIDTH: usize = 1600;
const HEIGHT: usize = 900;
@@ -21,6 +22,82 @@ fn main() {
window.limit_update_rate(Some(Duration::from_millis(16)));
+ // hard code this for now
+ let mut world = world::World::default();
+
+ let nonnegative_int = actor::Type::NumberInRange {
+ min: Some(number::Number::from(0)),
+ max: None,
+ };
+ let screen_position_type = actor::Type::Record {
+ name: "Screen Position".to_string(),
+ fields: vec![
+ actor::Slot { name: "X".to_string(), r#type: nonnegative_int.clone() },
+ actor::Slot { name: "Y".to_string(), r#type: nonnegative_int.clone() },
+ ],
+ };
+ let system_inputs = vec![actor::Slot { name: "mouse_position".to_string(), r#type: screen_position_type.clone() }];
+
+ let u24 = actor::Type::NumberInRange {
+ min: Some(number::Number::from(0)),
+ max: Some(number::Number::from(1 << 24 - 1)),
+ };
+ let system_outputs = vec![actor::Slot { name: "screen_buffer".to_string(), r#type: actor::Type::List { contents: Box::new(u24.clone()) }}];
+
+ let mut system = actor::ProgrammableActor {
+ inputs: system_inputs,
+ outputs: system_outputs,
+ ..Default::default()
+ };
+
+ let deconstruct_record = basic_actors::DeconstructRecord {
+ record_type: screen_position_type,
+ };
+ world.upsert_actor_type("Deconstruct Screen Position".to_string(), Box::new(deconstruct_record));
+ let deconstruct_record = system.add_child("Deconstruct Screen Position".to_string());
+ system.add_cable(
+ actor::ProducerSlotID::Input("mouse_position".to_string()),
+ actor::ConsumerSlotID::ChildInput(deconstruct_record, "Record".to_string())
+ );
+
+ world.upsert_actor_type("Add".to_string(), Box::new(basic_actors::Add));
+ let add = system.add_child("Add".to_string());
+ system.add_cable(
+ actor::ProducerSlotID::ChildOutput(deconstruct_record, "X".to_string()),
+ actor::ConsumerSlotID::ChildInput(add, "N1".to_string())
+ );
+ system.add_cable(
+ actor::ProducerSlotID::ChildOutput(deconstruct_record, "Y".to_string()),
+ actor::ConsumerSlotID::ChildInput(add, "N2".to_string())
+ );
+
+ let buffer_size_constant = basic_actors::Constant {
+ r#type: nonnegative_int.clone(),
+ value: actor::Value::Number(number::Number::from(WIDTH * HEIGHT)),
+ };
+ world.upsert_actor_type("Buffer Size Constant".to_string(), Box::new(buffer_size_constant));
+ let buffer_size_constant = system.add_child("Buffer Size Constant".to_string());
+
+ let repeat_u24 = basic_actors::RepeatValue {
+ r#type: u24,
+ };
+ world.upsert_actor_type("Repeat u24".to_string(), Box::new(repeat_u24));
+ let repeat_u24 = system.add_child("Repeat u24".to_string());
+ system.add_cable(
+ actor::ProducerSlotID::ChildOutput(add, "Result".to_string()),
+ actor::ConsumerSlotID::ChildInput(repeat_u24, "Value".to_string())
+ );
+ system.add_cable(
+ actor::ProducerSlotID::ChildOutput(buffer_size_constant, "Value".to_string()),
+ actor::ConsumerSlotID::ChildInput(repeat_u24, "Count".to_string())
+ );
+ system.add_cable(
+ actor::ProducerSlotID::ChildOutput(repeat_u24, "List".to_string()),
+ actor::ConsumerSlotID::Output("screen_buffer".to_string())
+ );
+
+ world.upsert_actor_type("System".to_string(), Box::new(system));
+
while window.is_open() && !window.is_key_down(Key::Escape) {
window
.update_with_buffer(&buffer, WIDTH, HEIGHT)
diff --git a/src/number.rs b/src/number.rs
index a71399c..94a67f1 100644
--- a/src/number.rs
+++ b/src/number.rs
@@ -1,7 +1,10 @@
+use std::cmp::max;
+use std::fmt::{Display, Formatter, Result as FormatResult};
+use std::ops::Add;
+
#[derive(Clone)]
pub struct Number {
pub integer_part: String,
- pub fractional_part: String,
}
macro_rules! int_conv {
@@ -10,7 +13,6 @@ macro_rules! int_conv {
fn from(x: $t) -> Number {
Number {
integer_part: x.to_string(),
- fractional_part: "".to_string(),
}
}
}
@@ -18,3 +20,36 @@ macro_rules! int_conv {
}
int_conv!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
+
+impl Display for Number {
+ fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
+ f.write_str(&self.integer_part)
+ }
+}
+
+impl Add for Number {
+ type Output = Number;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ let longest_ipart_len = max(self.integer_part.len(), rhs.integer_part.len());
+ let self_padded = format!("{:0>len$}", self.integer_part, len=longest_ipart_len);
+ let rhs_padded = format!("{:0>len$}", rhs.integer_part, len=longest_ipart_len);
+ let mut result_ipart_reverse = "".to_string();
+ let mut carry: u8 = 0;
+ for (self_char, rhs_char) in self_padded.chars().rev().zip(rhs_padded.chars().rev()) {
+ let self_char_val = self_char.to_digit(10).unwrap() as u8;
+ let rhs_char_val = rhs_char.to_digit(10).unwrap() as u8;
+ let sum = self_char_val + rhs_char_val + carry;
+ let sum_last_digit = (sum % 10).to_string().chars().next().unwrap();
+ carry = sum / 10;
+ result_ipart_reverse.push(sum_last_digit);
+ }
+ if carry > 0 {
+ result_ipart_reverse.push_str(&carry.to_string());
+ }
+ let result_ipart = result_ipart_reverse.chars().rev().collect();
+ Number {
+ integer_part: result_ipart,
+ }
+ }
+}
diff --git a/src/system.rs b/src/system.rs
deleted file mode 100644
index 7df8af8..0000000
--- a/src/system.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use crate::actor::{Actorful, Slot, Type};
-use crate::number::Number;
-
-pub struct System {
- pub screen_buffer: Vec<u32>,
-}
-
-impl Actorful for System {
- fn inputs() -> Vec<Slot> {
- let nonnegative_int = Type::NumberInRange {
- min: Some(Number::from(0)),
- max: None,
- };
- let screen_position_type = Type::Record {
- name: "Screen Position".to_string(),
- fields: vec![
- Slot { name: "X".to_string(), r#type: nonnegative_int.clone() },
- Slot { name: "Y".to_string(), r#type: nonnegative_int },
- ],
- };
- vec![Slot { name: "mouse_position".to_string(), r#type: screen_position_type }]
- }
-
- fn outputs() -> Vec<Slot> {
- let u24 = Type::NumberInRange {
- min: Some(Number::from(0)),
- max: Some(Number::from(1 << 24 - 1)),
- };
- vec![Slot { name: "screen_buffer".to_string(), r#type: Type::List { contents: Box::new(u24) }}]
- }
-}
diff --git a/src/world.rs b/src/world.rs
new file mode 100644
index 0000000..53217e2
--- /dev/null
+++ b/src/world.rs
@@ -0,0 +1,47 @@
+use std::collections::HashMap;
+use std::sync::mpsc::{self, Receiver, Sender};
+use std::thread;
+
+use uuid::Uuid;
+
+use crate::actor::{Actorful, Value, Slot};
+
+pub struct ActorThreadId(Uuid);
+
+#[derive(Default)]
+pub struct World {
+ actor_types: HashMap<String, Box<dyn Actorful + Send>>,
+ actors: HashMap<ActorThreadId, String>,
+}
+
+impl World {
+ pub fn upsert_actor_type(&mut self, name: String, actor: Box<dyn Actorful + Send>) {
+ self.actor_types.insert(name, actor);
+ }
+
+ pub fn spawn_actor(&mut self, r#type: &str) -> (Uuid, HashMap<String, Sender<Value>>, HashMap<String, Receiver<Value>>) {
+ fn make_channels(slot: Slot) -> ((String, Sender<Value>), (String, Receiver<Value>)) {
+ let (sender, receiver) = mpsc::channel::<Value>();
+ ((slot.name.clone(), sender), (slot.name, receiver))
+ }
+
+ let actor = self.actor_types.get(r#type).unwrap();
+ let actor = actor.boxed_clone();
+ let input_slots = actor.inputs();
+ let (input_senders, input_receivers) = input_slots
+ .into_iter()
+ .map(make_channels)
+ .unzip();
+ let output_slots = actor.outputs();
+ let (output_senders, output_receivers) = output_slots
+ .into_iter()
+ .map(make_channels)
+ .unzip();
+ let id = Uuid::new_v4();
+ thread::Builder::new()
+ .name(id.to_string())
+ .spawn(move || actor.launch(input_receivers, output_senders, self))
+ .unwrap();
+ (id, input_senders, output_receivers)
+ }
+}