use crate::reg::Registry;
use bevy_ecs::{prelude::*, query::QueryData};
use bevy_log as log;
use gantz_ca as ca;
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
};
use steel::steel_vm::engine::Engine;
#[derive(QueryData)]
#[query_data(mutable)]
pub struct OpenHeadData<N: 'static + Send + Sync> {
pub entity: Entity,
pub head_ref: &'static mut HeadRef,
pub working_graph: &'static mut WorkingGraph<N>,
pub compiled: &'static mut CompiledModule,
}
#[derive(Component)]
pub struct OpenHead;
#[derive(Component, Clone)]
pub struct HeadRef(pub ca::Head);
#[derive(Component)]
pub struct WorkingGraph<N>(pub gantz_core::node::graph::Graph<N>);
#[derive(Component, Default, Clone)]
pub struct CompiledModule(pub String);
#[derive(Event)]
pub struct OpenEvent(pub ca::Head);
#[derive(Event)]
pub struct CloseEvent(pub ca::Head);
#[derive(Event)]
pub struct ReplaceEvent(pub ca::Head);
#[derive(Event)]
pub struct BranchEvent {
pub original: ca::Head,
pub new_name: String,
}
#[derive(Event)]
pub struct MoveBranchEvent {
pub entity: Entity,
pub name: ca::Branch,
pub target: ca::CommitAddr,
}
#[derive(Event)]
pub struct OpenedEvent {
pub entity: Entity,
pub head: ca::Head,
}
#[derive(Event)]
pub struct ClosedEvent {
pub entity: Entity,
pub head: ca::Head,
}
#[derive(Event)]
pub struct ChangedEvent {
pub entity: Entity,
pub old_head: ca::Head,
pub new_head: ca::Head,
}
#[derive(Event)]
pub struct BranchedEvent {
pub entity: Entity,
pub old_head: ca::Head,
pub new_head: ca::Head,
}
#[derive(Event)]
pub struct CommittedEvent {
pub entity: Entity,
pub old_head: ca::Head,
pub new_head: ca::Head,
}
#[derive(Default)]
pub struct HeadVms(pub HashMap<Entity, Engine>);
#[derive(Resource, Default)]
pub struct FocusedHead(pub Option<Entity>);
#[derive(Resource, Default)]
pub struct HeadTabOrder(pub Vec<Entity>);
impl Deref for HeadRef {
type Target = ca::Head;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for HeadRef {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<N> Deref for WorkingGraph<N> {
type Target = gantz_core::node::graph::Graph<N>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<N> DerefMut for WorkingGraph<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for CompiledModule {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for HeadVms {
type Target = HashMap<Entity, Engine>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for HeadVms {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for FocusedHead {
type Target = Option<Entity>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for FocusedHead {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for HeadTabOrder {
type Target = Vec<Entity>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for HeadTabOrder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub fn find_entity(
head: &ca::Head,
heads: &Query<(Entity, &HeadRef), With<OpenHead>>,
) -> Option<Entity> {
heads
.iter()
.find(|(_, head_ref)| &***head_ref == head)
.map(|(entity, _)| entity)
}
pub fn is_focused(
head: &ca::Head,
heads: &Query<(Entity, &HeadRef), With<OpenHead>>,
focused: &FocusedHead,
) -> bool {
find_entity(head, heads)
.map(|entity| **focused == Some(entity))
.unwrap_or(false)
}
pub fn on_open<N>(
trigger: On<OpenEvent>,
mut cmds: Commands,
registry: Res<Registry<N>>,
mut tab_order: ResMut<HeadTabOrder>,
mut focused: ResMut<FocusedHead>,
heads: Query<(Entity, &HeadRef), With<OpenHead>>,
) where
N: 'static + Clone + Send + Sync,
{
let OpenEvent(new_head) = trigger.event();
if let Some(entity) = find_entity(new_head, &heads) {
**focused = Some(entity);
return;
}
let Some(graph) = registry.head_graph(new_head).cloned() else {
log::error!("cannot open head: graph missing from registry");
return;
};
let entity = cmds
.spawn((OpenHead, HeadRef(new_head.clone()), WorkingGraph(graph)))
.id();
tab_order.push(entity);
**focused = Some(entity);
cmds.trigger(OpenedEvent {
entity,
head: new_head.clone(),
});
}
pub fn on_replace<N>(
trigger: On<ReplaceEvent>,
mut cmds: Commands,
registry: Res<Registry<N>>,
mut focused: ResMut<FocusedHead>,
heads: Query<(Entity, &HeadRef), With<OpenHead>>,
) where
N: 'static + Clone + Send + Sync,
{
let ReplaceEvent(new_head) = trigger.event();
if let Some(entity) = find_entity(new_head, &heads) {
**focused = Some(entity);
return;
}
let Some(focused_entity) = **focused else {
return;
};
let old_head = heads.get(focused_entity).ok().map(|(_, h)| (**h).clone());
let Some(graph) = registry.head_graph(new_head).cloned() else {
log::error!("cannot replace head: graph missing from registry");
return;
};
cmds.entity(focused_entity)
.insert(HeadRef(new_head.clone()))
.insert(WorkingGraph(graph));
if let Some(old) = old_head {
cmds.trigger(ChangedEvent {
entity: focused_entity,
old_head: old,
new_head: new_head.clone(),
});
}
}
pub fn on_close<N>(
trigger: On<CloseEvent>,
mut cmds: Commands,
mut tab_order: ResMut<HeadTabOrder>,
mut focused: ResMut<FocusedHead>,
mut vms: NonSendMut<HeadVms>,
heads: Query<(Entity, &HeadRef), With<OpenHead>>,
) where
N: 'static + Send + Sync,
{
let CloseEvent(head) = trigger.event();
if tab_order.len() <= 1 {
return;
}
let Some(entity) = find_entity(head, &heads) else {
return;
};
let Some(ix) = tab_order.iter().position(|&x| x == entity) else {
return;
};
vms.remove(&entity);
cmds.entity(entity).despawn();
tab_order.retain(|&x| x != entity);
if **focused == Some(entity) {
let new_ix = ix.saturating_sub(1).min(tab_order.len().saturating_sub(1));
**focused = tab_order.get(new_ix).copied();
}
cmds.trigger(ClosedEvent {
entity,
head: head.clone(),
});
}
pub fn on_branch<N>(
trigger: On<BranchEvent>,
mut cmds: Commands,
mut registry: ResMut<Registry<N>>,
mut heads: Query<(Entity, &mut HeadRef), With<OpenHead>>,
) where
N: 'static + Send + Sync,
{
let BranchEvent { original, new_name } = trigger.event();
let Some(commit_ca) = registry.head_commit_ca(original).copied() else {
log::error!("Failed to get commit address for head: {:?}", original);
return;
};
registry.insert_name(new_name.clone(), commit_ca);
let new_head = ca::Head::Branch(new_name.clone());
for (entity, mut head_ref) in heads.iter_mut() {
if &**head_ref == original {
let old_head = (**head_ref).clone();
**head_ref = new_head.clone();
cmds.trigger(BranchedEvent {
entity,
old_head,
new_head,
});
break;
}
}
}
pub fn on_move_branch<N>(
trigger: On<MoveBranchEvent>,
mut cmds: Commands,
mut registry: ResMut<Registry<N>>,
) where
N: 'static + Clone + Send + Sync,
{
let event = trigger.event();
registry.insert_name(event.name.clone(), event.target);
let head = ca::Head::Branch(event.name.clone());
let Some(graph) = registry.head_graph(&head).cloned() else {
log::error!("MoveBranch: graph missing for target commit");
return;
};
cmds.entity(event.entity).insert(WorkingGraph(graph));
cmds.trigger(ChangedEvent {
entity: event.entity,
old_head: head.clone(),
new_head: head,
});
}