use crate::ontology::OntologyRegistry;
use crate::runtime::{Command, Frame, Model};
use super::protocol::{AgentRequest, AgentResponse, RequestEnvelope};
use super::session::AgentSession;
pub struct HeadlessDriver<M: Model> {
model: M,
session: AgentSession,
ontology: OntologyRegistry,
running: bool,
window_size: crate::core::Size,
hit_map: crate::event::HitMap,
}
impl<M: Model> HeadlessDriver<M> {
pub fn new(model: M, width: f32, height: f32) -> Self {
let mut ontology = OntologyRegistry::new();
model.register_ontology(&mut ontology);
Self {
model,
session: AgentSession::new(),
ontology,
running: true,
window_size: crate::core::Size::new(width, height),
hit_map: crate::event::HitMap::new(),
}
}
pub fn is_running(&self) -> bool {
self.running
}
pub fn model(&self) -> &M {
&self.model
}
pub fn ontology(&self) -> &OntologyRegistry {
&self.ontology
}
pub fn session(&self) -> &AgentSession {
&self.session
}
pub fn window_size(&self) -> crate::core::Size {
self.window_size
}
pub fn process_request(&mut self, request: &AgentRequest) -> AgentResponse {
self.render();
let (response, should_quit) = self.session.process_request(request, &self.ontology);
if let AgentRequest::ExecuteAction {
agent_id,
action,
params,
} = request
{
let cmd = Command::AgentAction {
agent_id: agent_id.clone(),
action: action.clone(),
params: params.clone(),
};
self.process_command(cmd);
}
if let AgentRequest::BatchActions { actions } = request {
for entry in actions {
let cmd = Command::AgentAction {
agent_id: entry.agent_id.clone(),
action: entry.action.clone(),
params: entry.params.clone(),
};
self.process_command(cmd);
}
}
if let AgentRequest::InjectEvent { event } = request {
if let Some(ev) = AgentSession::convert_injected_event(event) {
if let Some(msg) = self.model.handle_event(ev) {
let cmd = self.model.update(msg);
self.process_command(cmd);
}
}
}
if should_quit {
self.running = false;
}
response
}
pub fn process_envelope(&mut self, envelope: &RequestEnvelope) -> AgentResponse {
let mut response = self.process_request(&envelope.request);
if let Some(ref id) = envelope.id {
response = response.with_id(id.clone());
}
response
}
pub fn tick(&mut self) {
if let Some(msg) = self.model.handle_event(crate::event::Event::Tick) {
let cmd = self.model.update(msg);
self.process_command(cmd);
}
}
pub fn init(&mut self) {
let cmd = self.model.init();
self.process_command(cmd);
}
pub fn compute_state_diffs(&mut self) -> Vec<super::protocol::AgentEvent> {
self.session.compute_state_diffs(&self.ontology)
}
fn render(&mut self) {
let area =
crate::core::Rect::new(0.0, 0.0, self.window_size.width, self.window_size.height);
self.hit_map.clear();
let mut backend =
crate::backend::test::TestBackend::new(self.window_size.width, self.window_size.height);
let mut frame = Frame::new(area, &mut self.hit_map, &mut backend);
self.model.view(&mut frame);
let nodes = frame.take_nodes();
if !nodes.is_empty() {
let mut root =
crate::ontology::UiNode::new("root", crate::ontology::SemanticRole::Container);
root.children = nodes;
self.ontology.set_tree(crate::ontology::UiTree::new(root));
}
}
fn process_command(&mut self, cmd: Command<M::Msg>) {
match cmd {
Command::None => {}
Command::Quit => {
self.running = false;
}
Command::Batch(cmds) => {
for c in cmds {
self.process_command(c);
}
}
Command::Message(msg) => {
let cmd = self.model.update(msg);
self.process_command(cmd);
}
Command::SetTickRate(_) => {
}
Command::ExportOntology => {
self.model.register_ontology(&mut self.ontology);
}
Command::AgentAction {
agent_id,
action,
params,
} => {
log::debug!(
"HeadlessDriver: AgentAction {agent_id}.{action}({})",
params
);
}
Command::Task(task) => {
let msg = task();
let cmd = self.model.update(msg);
self.process_command(cmd);
}
Command::TaskWithTimeout {
task,
timeout,
on_timeout,
} => {
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let result = task();
let _ = tx.send(result);
});
let msg = match rx.recv_timeout(timeout) {
Ok(result) => result,
Err(_) => on_timeout,
};
let cmd = self.model.update(msg);
self.process_command(cmd);
}
Command::TaskCancellable { task, token } => {
let msg = task(token);
let cmd = self.model.update(msg);
self.process_command(cmd);
}
}
}
}