use std::time::{Duration, Instant};
use crate::base::{RgrowError, Tile};
use crate::painter::{blit_sprite, render_blockers, render_mismatches, render_outlines};
use crate::state::State;
use crate::ui::find_gui_command;
use super::core::System;
use super::types::*;
pub(super) fn evolve_in_window_impl<S: System, St: State>(
sys: &mut S,
state: &mut St,
block: Option<usize>,
start_paused: bool,
mut bounds: EvolveBounds,
initial_timescale: Option<f64>,
initial_max_events_per_sec: Option<u64>,
) -> Result<EvolveOutcome, RgrowError> {
use crate::ui::ipc::{ControlMessage, InitMessage, UpdateNotification};
use crate::ui::ipc_server::IpcClient;
use std::process::{Command, Stdio};
let debug_perf = std::env::var("RGROW_DEBUG_PERF").is_ok();
let (width, height) = state.draw_size();
let tile_colors_vec = sys.tile_colors().clone();
let scale = block.unwrap_or(12);
let socket_path =
std::env::temp_dir().join(format!("rgrow-gui-{}.sock", std::process::id()));
let socket_path_str = socket_path.to_string_lossy().to_string();
let (gui_exe, extra_args) = find_gui_command().ok_or_else(|| {
RgrowError::IO(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!(
"rgrow GUI binary (version {}) not found. The GUI functionality requires an rgrow binary built with the 'gui' feature.\n\nFor Rust installations, ensure rgrow is built with GUI support:\n cargo build --package rgrow --features gui\n\nFor Python installations, ensure the rgrow binary is available on PATH.",
env!("CARGO_PKG_VERSION")
)
))
})?;
let mut gui_process = Command::new(&gui_exe)
.args(&extra_args)
.arg(&socket_path_str)
.stdout(Stdio::null())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| {
RgrowError::IO(std::io::Error::other(format!(
"Failed to spawn GUI process: {}. Make sure rgrow is built with the 'gui' feature.",
e
)))
})?;
std::thread::sleep(Duration::from_millis(100));
let mut ipc_client = IpcClient::connect(&socket_path).map_err(|e| {
RgrowError::IO(std::io::Error::other(format!(
"Failed to connect to GUI: {}",
e
)))
})?;
let shm_size = (width * height * scale as u32 * scale as u32 * 4) as usize;
#[cfg(all(unix, not(target_os = "macos")))]
let shm_path = format!("/dev/shm/rgrow-frame-{}", std::process::id());
#[cfg(any(windows, target_os = "macos"))]
let shm_path = std::env::temp_dir()
.join(format!("rgrow-frame-{}", std::process::id()))
.to_string_lossy()
.to_string();
let has_temperature = sys.get_param("temperature").is_ok();
let model_name = S::extract_model_name(&sys.system_info());
let initial_temperature = if has_temperature {
sys.get_param("temperature")
.ok()
.and_then(|v| v.downcast_ref::<f64>().copied())
} else {
None
};
let mut parameters = sys.list_parameters();
for param in &mut parameters {
if let Ok(value) = sys.get_param(¶m.name) {
if let Some(f64_value) = value.downcast_ref::<f64>() {
param.current_value = *f64_value;
}
}
}
let init_msg = InitMessage {
width,
height,
tile_colors: tile_colors_vec.clone(),
block,
shm_path: shm_path.clone(),
shm_size,
start_paused,
model_name,
has_temperature,
initial_temperature,
parameters,
initial_timescale,
initial_max_events_per_sec,
};
ipc_client.send_init(&init_msg).map_err(|e| {
RgrowError::IO(std::io::Error::other(format!(
"Failed to send init message: {}",
e
)))
})?;
ipc_client
.wait_for_ready(Duration::from_secs(10))
.map_err(|e| {
RgrowError::IO(std::io::Error::other(format!(
"GUI failed to become ready: {}",
e
)))
})?;
let mut paused = start_paused;
let mut remaining_step_events: Option<u64> = None;
let mut max_events_per_sec: Option<u64> = initial_max_events_per_sec;
let mut timescale: Option<f64> = initial_timescale;
let mut show_mismatches = true;
let mut evres: EvolveOutcome = EvolveOutcome::ReachedZeroRate;
let mut frame_buffer = vec![0u8; shm_size];
let mut last_frame_time = Instant::now();
let mut events_this_second: u64 = 0;
let mut second_start = Instant::now();
loop {
while let Some(ctrl) = ipc_client.try_recv_control() {
if debug_perf {
eprintln!("[Sim] Received control message: {:?}", ctrl);
}
match ctrl {
ControlMessage::Pause => {
paused = true;
remaining_step_events = None;
}
ControlMessage::Resume => {
paused = false;
remaining_step_events = None;
}
ControlMessage::Step { events } => {
paused = false;
remaining_step_events = Some(events);
}
ControlMessage::SetMaxEventsPerSec(max) => {
max_events_per_sec = max;
}
ControlMessage::SetTimescale(ts) => {
timescale = ts;
}
ControlMessage::SetTemperature(temp) => {
if let Ok(needed) = sys.set_param("temperature", Box::new(temp)) {
sys.update_state(state, &needed);
}
}
ControlMessage::SetParameter { name, value } => {
if let Ok(needed) = sys.set_param(&name, Box::new(value)) {
sys.update_state(state, &needed);
}
}
ControlMessage::SetShowMismatches(v) => {
show_mismatches = v;
}
}
}
if second_start.elapsed() >= Duration::from_secs(1) {
events_this_second = 0;
second_start = Instant::now();
}
let should_run = !paused || remaining_step_events.is_some();
if should_run {
let events_before = state.total_events();
if let Some(ts) = timescale {
let real_elapsed = last_frame_time.elapsed().as_secs_f64();
let target_sim_time = real_elapsed * ts;
bounds.for_time = Some(target_sim_time);
bounds.for_wall_time = None;
bounds.for_events = remaining_step_events;
} else if let Some(ref mut step_events) = remaining_step_events {
bounds.for_events = Some(*step_events);
bounds.for_wall_time = Some(Duration::from_millis(16));
bounds.for_time = None;
} else {
bounds.for_wall_time = Some(Duration::from_millis(16));
bounds.for_events = None;
bounds.for_time = None;
}
if let Some(max_eps) = max_events_per_sec {
if events_this_second >= max_eps {
std::thread::sleep(Duration::from_millis(10));
} else {
let remaining_allowed = max_eps - events_this_second;
if let Some(ref mut be) = bounds.for_events {
*be = (*be).min(remaining_allowed);
} else {
bounds.for_events = Some(remaining_allowed);
}
evres = sys.evolve(state, bounds)?;
}
} else {
evres = sys.evolve(state, bounds)?;
}
let events_this_frame = state.total_events() - events_before;
events_this_second += events_this_frame;
if let Some(ref mut step_events) = remaining_step_events {
if events_this_frame >= *step_events {
remaining_step_events = None;
paused = true;
} else {
*step_events -= events_this_frame;
}
}
}
last_frame_time = Instant::now();
let frame_width = (width * scale as u32) as usize;
let frame_height = (height * scale as u32) as usize;
frame_buffer.resize(frame_width * frame_height * 4, 0);
let pixel_frame = &mut frame_buffer[..];
let tiles = state.raw_array();
let max_tile = tiles.iter().copied().max().unwrap_or(0) as usize;
let sprites: Vec<_> = (0..=max_tile)
.map(|t| sys.tile_pixels(t as Tile, scale))
.collect();
let blocker_masks: Vec<u8> = (0..=max_tile)
.map(|t| sys.tile_blocker_mask(t as Tile))
.collect();
for ((y, x), &tileid) in tiles.indexed_iter() {
if let Some(sprite) = sprites.get(tileid as usize) {
blit_sprite(pixel_frame, sprite, x, y, frame_width);
}
}
if scale >= 12 {
render_outlines(pixel_frame, tiles, scale, frame_width);
}
render_blockers(
pixel_frame,
tiles,
&blocker_masks,
scale,
frame_width,
frame_height,
);
let (mismatch_count, mismatch_locs) = if show_mismatches {
let locs = sys.calc_mismatch_locations(state);
let count: u32 = locs
.iter()
.map(|x| ((x & 0b01) + ((x & 0b10) >> 1)) as u32)
.sum();
(count, Some(locs))
} else {
(sys.calc_mismatches(state) as u32, None)
};
if let Some(ref locs) = mismatch_locs {
render_mismatches(pixel_frame, &locs.view(), scale, frame_width);
}
let notification = UpdateNotification {
frame_width: frame_width as u32,
frame_height: frame_height as u32,
time: state.time().into(),
total_events: state.total_events(),
n_tiles: state.n_tiles(),
mismatches: mismatch_count,
energy: state.energy(),
scale,
data_len: pixel_frame.len(),
};
let t_send = Instant::now();
if ipc_client.send_frame(pixel_frame, notification).is_err() {
break;
}
let t_send_elapsed = t_send.elapsed();
if debug_perf {
eprintln!(
"[IPC-send] shm write + notify: {:?}, size: {} bytes",
t_send_elapsed,
frame_buffer.len()
);
}
std::thread::sleep(Duration::from_millis(16));
if !paused && remaining_step_events.is_none() {
match evres {
EvolveOutcome::ReachedWallTimeMax => {}
EvolveOutcome::ReachedTimeMax => {}
EvolveOutcome::ReachedEventsMax => {}
EvolveOutcome::ReachedZeroRate => {}
_ => {
break;
}
}
}
}
let _ = ipc_client.send_close();
let _ = gui_process.wait();
let _ = std::fs::remove_file(&socket_path);
Ok(evres)
}