use crate::BuiltinNodes;
use crate::head;
use crate::reg::{Registry, lookup_node};
use bevy_ecs::prelude::*;
use bevy_log as log;
use gantz_ca as ca;
use gantz_core::node::{self, GetNode, graph::Graph};
use gantz_core::vm::CompileError;
use gantz_core::{Node, compile as core_compile};
use std::time::Duration;
use steel::steel_vm::engine::Engine;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvalKind {
Push,
Pull,
}
#[derive(Event)]
pub struct EvalEvent {
pub head: Entity,
pub path: Vec<node::Id>,
pub kind: EvalKind,
}
#[derive(Event)]
pub struct EvalCompleted {
pub entity: Entity,
pub duration: Duration,
}
pub fn init<N>(get_node: GetNode, graph: &Graph<N>) -> Result<(Engine, String), CompileError>
where
N: Node,
{
let (vm, module) = gantz_core::vm::init(get_node, graph)?;
Ok((vm, gantz_core::vm::fmt_module(&module)))
}
pub fn compile<N>(
get_node: GetNode,
graph: &Graph<N>,
vm: &mut Engine,
) -> Result<String, CompileError>
where
N: Node,
{
let module = gantz_core::vm::compile(get_node, graph, vm)?;
Ok(gantz_core::vm::fmt_module(&module))
}
fn init_head_vm<N>(
entity: Entity,
registry: &Registry<N>,
builtins: &BuiltinNodes<N>,
vms: &mut head::HeadVms,
cmds: &mut Commands,
graphs: &Query<&head::WorkingGraph<N>>,
) where
N: 'static + Node + Send + Sync,
{
let graph = graphs.get(entity).unwrap();
let get_node = |ca: &ca::ContentAddr| lookup_node(registry, &**builtins, ca);
let (vm, module) = match init(&get_node, &**graph) {
Ok(result) => result,
Err(e) => {
log::error!("Failed to init VM for head: {e}");
return;
}
};
cmds.entity(entity).insert(head::CompiledModule(module));
vms.insert(entity, vm);
}
pub fn on_head_opened<N>(
trigger: On<head::OpenedEvent>,
registry: Res<Registry<N>>,
builtins: Res<BuiltinNodes<N>>,
mut vms: NonSendMut<head::HeadVms>,
mut cmds: Commands,
graphs: Query<&head::WorkingGraph<N>>,
) where
N: 'static + Node + Send + Sync,
{
init_head_vm(
trigger.event().entity,
®istry,
&builtins,
&mut vms,
&mut cmds,
&graphs,
);
}
pub fn on_head_changed<N>(
trigger: On<head::ChangedEvent>,
registry: Res<Registry<N>>,
builtins: Res<BuiltinNodes<N>>,
mut vms: NonSendMut<head::HeadVms>,
mut cmds: Commands,
graphs: Query<&head::WorkingGraph<N>>,
) where
N: 'static + Node + Send + Sync,
{
init_head_vm(
trigger.event().entity,
®istry,
&builtins,
&mut vms,
&mut cmds,
&graphs,
);
}
pub fn on_eval(trigger: On<EvalEvent>, mut vms: NonSendMut<head::HeadVms>, mut cmds: Commands) {
let event = trigger.event();
let fn_name = match event.kind {
EvalKind::Push => core_compile::push_eval_fn_name(&event.path),
EvalKind::Pull => core_compile::pull_eval_fn_name(&event.path),
};
if let Some(vm) = vms.get_mut(&event.head) {
let start = web_time::Instant::now();
if let Err(e) = vm.call_function_by_name_with_args(&fn_name, vec![]) {
log::error!("{e}");
}
cmds.trigger(EvalCompleted {
entity: event.head,
duration: start.elapsed(),
});
}
}
pub fn setup<N>(world: &mut World)
where
N: 'static + Node + Send + Sync,
{
log::info!("Setting up VMs for all open heads!");
let entities: Vec<Entity> = world
.query_filtered::<Entity, With<head::OpenHead>>()
.iter(world)
.collect();
let mut vms = head::HeadVms::default();
let mut compiled_updates: Vec<(Entity, String)> = vec![];
for entity in entities {
let registry = world.resource::<Registry<N>>();
let builtins = world.resource::<BuiltinNodes<N>>();
let get_node = |ca: &ca::ContentAddr| lookup_node(registry, &**builtins, ca);
let Some(wg) = world.get::<head::WorkingGraph<N>>(entity) else {
continue;
};
let (vm, module) = match init(&get_node, &**wg) {
Ok(result) => result,
Err(e) => {
log::error!("Failed to init VM for entity {entity}: {e}");
continue;
}
};
vms.insert(entity, vm);
compiled_updates.push((entity, module));
}
for (entity, compiled_module) in compiled_updates {
if let Some(mut compiled) = world.get_mut::<head::CompiledModule>(entity) {
*compiled = head::CompiledModule(compiled_module);
}
}
world.insert_non_send_resource(vms);
}
pub fn update<N>(
mut cmds: Commands,
mut registry: ResMut<Registry<N>>,
builtins: Res<BuiltinNodes<N>>,
mut vms: NonSendMut<head::HeadVms>,
mut heads_query: Query<head::OpenHeadData<N>, With<head::OpenHead>>,
) where
N: 'static + Node + Clone + ca::CaHash + Send + Sync,
{
for mut data in heads_query.iter_mut() {
let head: &mut ca::Head = &mut *data.head_ref;
let graph: &Graph<N> = &*data.working_graph;
let new_graph_ca = ca::graph_addr(graph);
let Some(head_commit) = registry.head_commit(head) else {
continue;
};
if head_commit.graph != new_graph_ca {
let old_head = head.clone();
let old_commit_ca = registry.head_commit_ca(head).copied().unwrap();
let new_commit_ca = registry.commit_graph_to_head(
crate::reg::timestamp(),
new_graph_ca,
|| crate::clone_graph(graph),
head,
);
log::debug!(
"Graph changed: {} -> {}",
old_commit_ca.display_short(),
new_commit_ca.display_short()
);
cmds.trigger(head::CommittedEvent {
entity: data.entity,
old_head: old_head.clone(),
new_head: head.clone(),
});
if let Some(vm) = vms.get_mut(&data.entity) {
let get_node = |ca: &ca::ContentAddr| lookup_node(®istry, &**builtins, ca);
gantz_core::graph::register(&get_node, graph, &[], vm);
match compile(&get_node, graph, vm) {
Ok(module) => data.compiled.0 = module,
Err(e) => log::error!("Failed to compile graph: {e}"),
}
}
}
}
}