From 36d5ff553a1d65d70da5251b8865d62294c929f6 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sun, 7 Mar 2021 09:12:47 -0700 Subject: tear it all up and implement super basic actors --- Cargo.lock | 10 ++++ Cargo.toml | 1 + src/actor.rs | 93 ++++++++++++++++++++++++++++++++++- src/basic_actors.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 79 +++++++++++++++++++++++++++++- src/number.rs | 39 ++++++++++++++- src/system.rs | 31 ------------ src/world.rs | 47 ++++++++++++++++++ 8 files changed, 400 insertions(+), 36 deletions(-) create mode 100644 src/basic_actors.rs delete mode 100644 src/system.rs create mode 100644 src/world.rs 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]] @@ -638,6 +639,15 @@ dependencies = [ "regex", ] +[[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" 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 { @@ -17,6 +27,14 @@ pub enum Type { }, } +#[derive(Clone)] +pub enum Value { + String(String), + Record(Vec<(String, Value)>), + Number(Number), + List(Vec), +} + #[derive(Clone)] pub struct Slot { pub name: String, @@ -24,6 +42,77 @@ pub struct Slot { } pub trait Actorful { - fn inputs() -> Vec; - fn outputs() -> Vec; + fn inputs(&self) -> Vec; + fn outputs(&self) -> Vec; + fn launch(&self, input_channels: HashMap>, output_channels: HashMap>, world: &mut World); + fn boxed_clone(&self) -> Box; +} + +#[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, + pub outputs: Vec, + pub children: HashMap, + 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 { + self.inputs.clone() + } + + fn outputs(&self) -> Vec { + self.outputs.clone() + } + + fn launch(&self, mut input_channels: HashMap>, mut output_channels: HashMap>, 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 { + 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 { + vec![] + } + + fn outputs(&self) -> Vec { + vec![Slot { name: "Value".to_string(), r#type: self.r#type.clone() }] + } + + fn launch(&self, _input_channels: HashMap>, output_channels: HashMap>, _world: &mut World) { + loop { + output_channels["Value"].send(self.value.clone()).unwrap() + } + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct Add; + +impl Actorful for Add { + fn inputs(&self) -> Vec { + vec![ + Slot { name: "N1".to_string(), r#type: Type::AnyNumber }, + Slot { name: "N2".to_string(), r#type: Type::AnyNumber }, + ] + } + + fn outputs(&self) -> Vec { + vec![Slot { name: "Result".to_string(), r#type: Type::AnyNumber }] + } + + fn launch(&self, mut input_channels: HashMap>, output_channels: HashMap>, _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 { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct RepeatValue { + pub(crate) r#type: Type, +} + +impl Actorful for RepeatValue { + fn inputs(&self) -> Vec { + 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 { + vec![Slot { name: "List".to_string(), r#type: Type::List { contents: Box::new(self.r#type.clone()) } }] + } + + fn launch(&self, mut input_channels: HashMap>, output_channels: HashMap>, _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 { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct DeconstructRecord { + pub record_type: Type, +} + +impl Actorful for DeconstructRecord { + fn inputs(&self) -> Vec { + vec![Slot { name: "Record".to_string(), r#type: self.record_type.clone() }] + } + + fn outputs(&self) -> Vec { + 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>, output_channels: HashMap>, _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 { + 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, -} - -impl Actorful for System { - fn inputs() -> Vec { - 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 { - 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>, + actors: HashMap, +} + +impl World { + pub fn upsert_actor_type(&mut self, name: String, actor: Box) { + self.actor_types.insert(name, actor); + } + + pub fn spawn_actor(&mut self, r#type: &str) -> (Uuid, HashMap>, HashMap>) { + fn make_channels(slot: Slot) -> ((String, Sender), (String, Receiver)) { + let (sender, receiver) = mpsc::channel::(); + ((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) + } +} -- cgit v1.2.3