Compare commits

...

5 commits

Author SHA1 Message Date
dbb52ccbb2 RustyLee: Starts Heart organ processing
Activates the Heart organ's main command processing loop by calling its `start` method.

Replaces the previous placeholder `run_loop` method with the functional `start` method. The `start` method now takes a mutable reference to `self`, which enables it to operate as a long-running process without consuming the `Heart` instance.
2026-03-13 15:55:19 +00:00
7633985535 Rust: Establishes core organ communication framework
Introduces `OrganFactory` to manage the creation and spawning of organs, running each in its own thread. `OrganSocket` provides the Brain's interface for sending timestamped commands to specific organs.

Configures new MPSC channels within the Brain to handle bidirectional communication with its organs, enabling command dispatch and feedback reception. The Heart is integrated as the first example organ within this architecture.
2026-03-13 15:47:48 +00:00
33e7de18cf Adds initial organ system and protocols
Establishes the core `organs` module, including a basic `Heart` implementation, to structure internal components.

Introduces a `protocols` layer to define message passing and communication between the `Brain` and various `organs`.

Adds a `coordinates` module with a `Point3D` struct for spatial definitions.

Increases the `Brain`'s resting sleep duration for minor tuning.
2026-03-13 09:06:26 +00:00
ee01746956 Removes redundant brain state definition
The `State` enum, along with its variants (Starting, Running, Stopping, Stopped), is no longer required for managing the brain's operational stages. This change simplifies the codebase by removing an unnecessary abstraction.
2026-03-03 09:39:35 +00:00
58e1197ff4 Integrates signal handling with lifecycle
Refactors the application shutdown mechanism to use lifecycle commands.
OS signals (SIGTERM/SIGINT) now sends a `Dying` command directly to the brain.
The brain processes this command, transitioning through `Dying` to `Buried` state.
The main loop now explicitly waits for the brain to report a `Dead` or `Buried` state before exiting, ensuring a graceful shutdown.
Increases the brain's rest duration and adds debug logging.
2026-03-03 09:37:41 +00:00
12 changed files with 263 additions and 25 deletions

View file

@ -1 +1 @@
136
146

View file

@ -1,24 +1,41 @@
use tracing::info;
use tracing::{debug, info};
use std::sync::mpsc::{Receiver, Sender};
use std::thread::JoinHandle;
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 {
state:LifeState,
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 {
pub fn new(divine_rx: Receiver<LifecycleCommand>, divine_tx: Sender<LifecycleReceipt>) -> Self {
let (organ_tx, organ_rx) = std::sync::mpsc::channel::<BrainMessage>();
Self {
state: LifeState::Dead,
divine_rx,
divine_tx,
organ_rx,
organ_tx,
organ_sockets: Vec::new()
}
}
pub fn run(&mut self) {
self.execute_brain_loop();
self.build_organs();
}
fn execute_brain_loop(&mut self) {
loop {
while let Ok(command) = self.divine_rx.try_recv() {
self.handle_divine_command(command)
@ -36,14 +53,22 @@ impl Brain {
}
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) {
info!("God has commanded {:?}", 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;
}
@ -60,7 +85,7 @@ impl Brain {
fn can_transition_lifecycle(&self, command: &LifecycleCommand) -> bool
{
if (command.required_state == Buried) {
if command.required_state == Buried || command.required_state == Dying {
return true
}
@ -70,17 +95,26 @@ impl Brain {
false
}
fn set_lifecycle_state(&mut self, command: &LifecycleCommand) {
self.state = command.required_state;
fn set_lifecycle_state(&mut self, command: &LifecycleCommand, new_state: LifeState) {
self.state = new_state;
self.report_to_god(LifecycleReceipt{
command: command.clone(),
response: LifecycleCommandResponse::Ok,
new_state: self.state
});
}
fn report_to_god(&self, receipt: LifecycleReceipt) {
info!("Reporting to God: Status = {:?}, NewState={:?}", receipt.response, receipt.new_state);
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());
}
}

View file

@ -1,7 +0,0 @@
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum State {
Starting,
Running,
Stopping,
Stopped,
}

View file

@ -0,0 +1,6 @@
#[derive(Debug, Clone, Copy)]
pub struct Point3D {
pub x: f32,
pub y: f32,
pub z: f32
}

View file

@ -12,7 +12,7 @@ pub enum LifeState {
Flight,
Panic,
Dying,
Buried
Buried,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -34,3 +34,4 @@ pub struct LifecycleReceipt {
pub response: LifecycleCommandResponse,
pub new_state: LifeState
}

View file

@ -1,27 +1,43 @@
pub mod lifecycle;
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::Arc;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
use std::time::{Duration, Instant};
use tracing_subscriber::registry;
use crate::brain::Brain;
use crate::lifecycle::{LifeState, LifecycleCommand, LifecycleReceipt};
fn main() {
let shutdown_requested = Arc::new(AtomicBool::new(false));
setup_logging();
info!("RustyLee: {}", env!("FULL_VERSION"));
setup_os_signal_handler(shutdown_requested.clone());
let (tx, rx) = spawn_brain();
setup_os_signal_handler(tx.clone());
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.");
}
@ -69,11 +85,14 @@ fn spawn_brain() -> (Sender<LifecycleCommand>, Receiver<LifecycleReceipt>) {
(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.");
ctrlc::set_handler(move || {
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.");
}

View file

@ -0,0 +1,4 @@
pub mod heart;
pub mod organ;
pub mod organ_socket;
pub mod organ_factory;

View 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);
}
}
}

View 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;
}

View 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>()
}
}

View 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");
}
}
}

View 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,
}