Compare commits
5 commits
500b26703e
...
dbb52ccbb2
| Author | SHA1 | Date | |
|---|---|---|---|
| dbb52ccbb2 | |||
| 7633985535 | |||
| 33e7de18cf | |||
| ee01746956 | |||
| 58e1197ff4 |
12 changed files with 263 additions and 25 deletions
|
|
@ -1 +1 @@
|
||||||
136
|
146
|
||||||
|
|
@ -1,24 +1,41 @@
|
||||||
use tracing::info;
|
use tracing::{debug, info};
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
use std::thread::JoinHandle;
|
||||||
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleCommandResponse, LifecycleReceipt};
|
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleCommandResponse, LifecycleReceipt};
|
||||||
use crate::lifecycle::LifeState::{Buried, Genisys};
|
use crate::lifecycle::LifeState::{Dying, Buried, Genisys};
|
||||||
|
use crate::organs::organ_factory::OrganFactory;
|
||||||
|
use crate::organs::organ_socket::OrganSocket;
|
||||||
|
use crate::protocols::{BrainMessage, OrganCommand, OrganCommandEnvelope};
|
||||||
|
|
||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
state:LifeState,
|
state:LifeState,
|
||||||
divine_rx : Receiver<LifecycleCommand>,
|
divine_rx : Receiver<LifecycleCommand>,
|
||||||
divine_tx: Sender<LifecycleReceipt>
|
divine_tx: Sender<LifecycleReceipt>,
|
||||||
|
organ_rx: Receiver<BrainMessage>,
|
||||||
|
organ_tx: Sender<BrainMessage>,
|
||||||
|
organ_sockets: Vec<OrganSocket>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Brain {
|
impl Brain {
|
||||||
pub fn new(divine_rx: Receiver<LifecycleCommand>, divine_tx: Sender<LifecycleReceipt>) -> Self {
|
pub fn new(divine_rx: Receiver<LifecycleCommand>, divine_tx: Sender<LifecycleReceipt>) -> Self {
|
||||||
|
let (organ_tx, organ_rx) = std::sync::mpsc::channel::<BrainMessage>();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
state: LifeState::Dead,
|
state: LifeState::Dead,
|
||||||
divine_rx,
|
divine_rx,
|
||||||
divine_tx,
|
divine_tx,
|
||||||
|
organ_rx,
|
||||||
|
organ_tx,
|
||||||
|
organ_sockets: Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
|
self.execute_brain_loop();
|
||||||
|
self.build_organs();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_brain_loop(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
while let Ok(command) = self.divine_rx.try_recv() {
|
while let Ok(command) = self.divine_rx.try_recv() {
|
||||||
self.handle_divine_command(command)
|
self.handle_divine_command(command)
|
||||||
|
|
@ -36,14 +53,22 @@ impl Brain {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rest() {
|
fn rest() {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1))
|
debug!("Brain is resting.");
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(200))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_divine_command(&mut self, command: LifecycleCommand) {
|
fn handle_divine_command(&mut self, command: LifecycleCommand) {
|
||||||
info!("God has commanded {:?}", command);
|
info!("God has commanded {:?}", command);
|
||||||
|
|
||||||
if self.can_transition_lifecycle(&command) {
|
if self.can_transition_lifecycle(&command) {
|
||||||
self.set_lifecycle_state(&command);
|
if command.required_state == Dying
|
||||||
|
{
|
||||||
|
self.set_lifecycle_state(&command, Buried);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
self.set_lifecycle_state(&command, command.required_state);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +85,7 @@ impl Brain {
|
||||||
fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool
|
fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool
|
||||||
{
|
{
|
||||||
|
|
||||||
if (command.required_state == Buried) {
|
if command.required_state == Buried || command.required_state == Dying {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,17 +95,26 @@ impl Brain {
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
fn set_lifecycle_state(&mut self, command: &LifecycleCommand) {
|
fn set_lifecycle_state(&mut self, command: &LifecycleCommand, new_state: LifeState) {
|
||||||
self.state = command.required_state;
|
self.state = new_state;
|
||||||
self.report_to_god(LifecycleReceipt{
|
self.report_to_god(LifecycleReceipt{
|
||||||
command: command.clone(),
|
command: command.clone(),
|
||||||
response: LifecycleCommandResponse::Ok,
|
response: LifecycleCommandResponse::Ok,
|
||||||
new_state: self.state
|
new_state: self.state
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_to_god(&self, receipt: LifecycleReceipt) {
|
fn report_to_god(&self, receipt: LifecycleReceipt) {
|
||||||
info!("Reporting to God: Status = {:?}, NewState={:?}", receipt.response, receipt.new_state);
|
info!("Reporting to God: Status = {:?}, NewState={:?}", receipt.response, receipt.new_state);
|
||||||
let _ = self.divine_tx.send(receipt);
|
let _ = self.divine_tx.send(receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_organs(& mut self) {
|
||||||
|
info!("Building organs...");
|
||||||
|
|
||||||
|
let factory = OrganFactory::new(self.organ_tx.clone());
|
||||||
|
|
||||||
|
info!("{} organs have been built and wired in.", self.organ_sockets.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
|
||||||
pub enum State {
|
|
||||||
Starting,
|
|
||||||
Running,
|
|
||||||
Stopping,
|
|
||||||
Stopped,
|
|
||||||
}
|
|
||||||
6
src/rustylee/src/coordinates.rs
Normal file
6
src/rustylee/src/coordinates.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Point3D {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ pub enum LifeState {
|
||||||
Flight,
|
Flight,
|
||||||
Panic,
|
Panic,
|
||||||
Dying,
|
Dying,
|
||||||
Buried
|
Buried,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -33,4 +33,5 @@ pub struct LifecycleReceipt {
|
||||||
pub command: LifecycleCommand,
|
pub command: LifecycleCommand,
|
||||||
pub response: LifecycleCommandResponse,
|
pub response: LifecycleCommandResponse,
|
||||||
pub new_state: LifeState
|
pub new_state: LifeState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,43 @@
|
||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
pub mod brain;
|
pub mod brain;
|
||||||
|
pub mod organs;
|
||||||
|
pub mod coordinates;
|
||||||
|
pub mod protocols;
|
||||||
|
|
||||||
use tracing::info;
|
use std::fmt::Alignment::Left;
|
||||||
|
use tracing::{info, debug};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
use tracing_subscriber::registry;
|
||||||
use crate::brain::Brain;
|
use crate::brain::Brain;
|
||||||
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleReceipt};
|
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleReceipt};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let shutdown_requested = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
setup_logging();
|
setup_logging();
|
||||||
info!("RustyLee: {}", env!("FULL_VERSION"));
|
info!("RustyLee: {}", env!("FULL_VERSION"));
|
||||||
|
|
||||||
setup_os_signal_handler(shutdown_requested.clone());
|
|
||||||
let (tx, rx) = spawn_brain();
|
let (tx, rx) = spawn_brain();
|
||||||
|
setup_os_signal_handler(tx.clone());
|
||||||
let_there_be_life(&tx, &rx);
|
let_there_be_life(&tx, &rx);
|
||||||
wait_for_death(tx, rx);
|
|
||||||
|
info!("God is watching.");
|
||||||
|
loop{
|
||||||
|
if let Ok(receipt) = rx.recv() {
|
||||||
|
debug!("Brain status: {:?}", receipt.new_state);
|
||||||
|
|
||||||
|
if receipt.new_state == LifeState::Dead || receipt.new_state == LifeState::Buried {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// wait_for_death(tx, rx);
|
||||||
|
|
||||||
info!("God: Brain has been buried. Shutting down.");
|
info!("God: Brain has been buried. Shutting down.");
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +85,14 @@ fn spawn_brain() -> (Sender<LifecycleCommand>, Receiver<LifecycleReceipt>) {
|
||||||
(to_brain_tx, from_brain_rx)
|
(to_brain_tx, from_brain_rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_os_signal_handler(s: Arc<AtomicBool>) {
|
fn setup_os_signal_handler(tx: Sender<LifecycleCommand>) {
|
||||||
info!("Setting up SIGTERM/SIGINT handling.");
|
info!("Setting up SIGTERM/SIGINT handling.");
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
info!("[Signal] Shutdown signal caught!");
|
info!("[Signal] Shutdown signal caught!");
|
||||||
s.store(true, Ordering::SeqCst);
|
let _ = tx.send(LifecycleCommand {
|
||||||
|
required_state: LifeState::Dying,
|
||||||
|
command_time: Instant::now(),
|
||||||
|
});
|
||||||
}).expect("Error setting signal handler.");
|
}).expect("Error setting signal handler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4
src/rustylee/src/organs.rs
Normal file
4
src/rustylee/src/organs.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod heart;
|
||||||
|
pub mod organ;
|
||||||
|
pub mod organ_socket;
|
||||||
|
pub mod organ_factory;
|
||||||
48
src/rustylee/src/organs/heart.rs
Normal file
48
src/rustylee/src/organs/heart.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use tracing::{info, instrument};
|
||||||
|
use crate::protocols::{BrainMessage, OrganCommandEnvelope, OrganResponse};
|
||||||
|
|
||||||
|
pub struct Heart {
|
||||||
|
id: u32,
|
||||||
|
brain_command_rx: mpsc::Receiver<OrganCommandEnvelope>,
|
||||||
|
feedback_to_brain_tx: mpsc::Sender<BrainMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Heart {
|
||||||
|
pub(crate) fn new(id: u32, rx: Receiver<OrganCommandEnvelope>, tx: Sender<BrainMessage>) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
brain_command_rx: rx,
|
||||||
|
feedback_to_brain_tx: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self), fields(heart_id = self.id))]
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
info!("Heart listener active");
|
||||||
|
|
||||||
|
while let Ok(envelope) = self.brain_command_rx.recv() {
|
||||||
|
// 1. Process the command (Logic goes here later)
|
||||||
|
let response = OrganResponse::Ok;
|
||||||
|
|
||||||
|
// 2. Capture the "now" timestamp
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_millis() as u64;
|
||||||
|
|
||||||
|
// 3. Package the reply
|
||||||
|
let reply = BrainMessage {
|
||||||
|
organ_command: envelope, // Move the original envelope back
|
||||||
|
responded_at: now,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Trace the reply and send
|
||||||
|
info!(?reply, "Sending response to Brain");
|
||||||
|
let _ = self.feedback_to_brain_tx.send(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/rustylee/src/organs/organ.rs
Normal file
9
src/rustylee/src/organs/organ.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
use crate::coordinates::Point3D;
|
||||||
|
use crate::lifecycle::LifeState;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub trait Organ: Send {
|
||||||
|
/// Returns the immutable U32 ID assigned by the factory.
|
||||||
|
fn id(&self) -> u32;
|
||||||
|
}
|
||||||
56
src/rustylee/src/organs/organ_factory.rs
Normal file
56
src/rustylee/src/organs/organ_factory.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
use std::thread;
|
||||||
|
use crate::organs::organ_socket::OrganSocket;
|
||||||
|
use crate::organs::heart::Heart; // Assuming Heart is our first organ
|
||||||
|
use crate::protocols::{OrganCommandEnvelope, BrainMessage};
|
||||||
|
|
||||||
|
pub struct OrganFactory {
|
||||||
|
brain_tx: Sender<BrainMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrganFactory {
|
||||||
|
pub(crate) fn new(brain_sender: Sender<BrainMessage>) -> Self {
|
||||||
|
Self {
|
||||||
|
brain_tx: brain_sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrganFactory {
|
||||||
|
pub fn build_organs(
|
||||||
|
// The Brain's "Inbox" mouth - we'll clone this for each organ
|
||||||
|
brain_tx: Sender<BrainMessage>
|
||||||
|
) -> Vec<OrganSocket> {
|
||||||
|
let mut sockets = Vec::new();
|
||||||
|
|
||||||
|
// Let's spawn a Heart as an example (ID: 1)
|
||||||
|
// In a real factory, you might loop through a config list here
|
||||||
|
let heart_socket = Self::spawn_heart(1, brain_tx.clone());
|
||||||
|
sockets.push(heart_socket);
|
||||||
|
|
||||||
|
// Add more organs here...
|
||||||
|
// let lung_socket = Self::spawn_lung(2, brain_tx.clone());
|
||||||
|
|
||||||
|
tracing::info!(count = sockets.len(), "Organ collection built and threads spawned");
|
||||||
|
|
||||||
|
sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_heart(id: u32, feedback_to_brain_tx: Sender<BrainMessage>) -> OrganSocket {
|
||||||
|
let (brain_command_to_organ_tx, brain_command_to_organ_rx) =
|
||||||
|
Self::get_organ_channels();
|
||||||
|
|
||||||
|
let socket = OrganSocket::new(id, brain_command_to_organ_tx);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut heart = Heart::new(id, brain_command_to_organ_rx, feedback_to_brain_tx);
|
||||||
|
heart.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_organ_channels() -> (Sender<OrganCommandEnvelope>, Receiver<OrganCommandEnvelope>) {
|
||||||
|
mpsc::channel::<OrganCommandEnvelope>()
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/rustylee/src/organs/organ_socket.rs
Normal file
32
src/rustylee/src/organs/organ_socket.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use crate::protocols::{OrganCommand, OrganCommandEnvelope};
|
||||||
|
|
||||||
|
pub struct OrganSocket {
|
||||||
|
pub id: u32,
|
||||||
|
tx: Sender<OrganCommandEnvelope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrganSocket {
|
||||||
|
pub fn new(id: u32, tx: Sender<OrganCommandEnvelope>) -> Self {
|
||||||
|
Self { id, tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, command: OrganCommand) {
|
||||||
|
// Here's the "Stamping" logic.
|
||||||
|
// The Brain just says "Beat", the Socket adds the "Metadata".
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_millis() as u64;
|
||||||
|
|
||||||
|
let envelope = OrganCommandEnvelope {
|
||||||
|
command,
|
||||||
|
issued_at: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = self.tx.send(envelope) {
|
||||||
|
tracing::error!(organ_id = self.id, error = %e, "Socket delivery failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/rustylee/src/protocols.rs
Normal file
36
src/rustylee/src/protocols.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::coordinates::Point3D;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OrganCommand {
|
||||||
|
Sleep,
|
||||||
|
Waken,
|
||||||
|
Pause,
|
||||||
|
Resume,
|
||||||
|
Beat (u32),
|
||||||
|
ChangeSpeed (u32),
|
||||||
|
GoFaster(u32),
|
||||||
|
GoSlower(u32),
|
||||||
|
MoveTo {
|
||||||
|
relative_to_center: Point3D,
|
||||||
|
speed: u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OrganResponse {
|
||||||
|
Ok,
|
||||||
|
Rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OrganCommandEnvelope {
|
||||||
|
pub command: OrganCommand,
|
||||||
|
pub issued_at: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BrainMessage {
|
||||||
|
pub organ_command: OrganCommandEnvelope,
|
||||||
|
pub responded_at: u64,
|
||||||
|
pub response: OrganResponse,
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue