use std::collections::HashMap;
use bevy::prelude::*;
use crossbeam_channel::{Receiver, Sender, unbounded};
use tape::{Chunk, ChunkId, sigil, render, read_kv};
#[derive(Resource, Clone)]
pub struct StreamChannel {
pub tx: Sender<Chunk>,
pub rx: Receiver<Chunk>,
}
impl Default for StreamChannel {
fn default() -> Self {
let (tx, rx) = unbounded();
Self { tx, rx }
}
}
#[derive(Component, Default)]
pub struct StreamScrollback {
pub id_map: HashMap<ChunkId, Entity>,
pub last_status: Option<i32>,
}
pub fn dispatch(commands: &mut Commands, parent: Entity, chunk: &Chunk) -> Entity {
match (chunk.sigil, chunk.render) {
(sigil::HAX, render::TEXT) => crate::atoms::text::spawn(commands, parent, chunk),
(sigil::SIG, render::TEXT) => crate::atoms::text::spawn_annotation(commands, parent, chunk),
(sigil::PAT, render::TEXT) => crate::molecules::neuron::spawn(commands, parent, chunk),
(sigil::DOT, render::LOG) => crate::molecules::log::spawn(commands, parent, chunk),
(sigil::ZAP, render::ERROR) => crate::molecules::error::spawn(commands, parent, chunk),
(sigil::DOT, render::STATUS) => crate::molecules::status::spawn(commands, parent, chunk),
(sigil::DOT, render::PROGRESS) => crate::molecules::progress::spawn(commands, parent, chunk),
(sigil::ZAP, render::COMPONENT) => crate::molecules::action::spawn(commands, parent, chunk),
(sigil::BAR, render::COMPONENT) => crate::molecules::component::spawn(commands, parent, chunk),
(sigil::FAS, render::COMPONENT) => crate::molecules::component::spawn_scope(commands, parent, chunk),
(sigil::HAX, render::TABLE) => crate::molecules::table::spawn(commands, parent, chunk),
_ => {
let label = format!(
"[?{} {}] {}",
chunk.sigil as char, chunk.render as char,
String::from_utf8_lossy(&chunk.payload).chars().take(60).collect::<String>()
);
commands.spawn((
Text::new(label),
TextFont { font_size: crate::theme::MICRO, ..default() },
TextColor(Color::srgba(0.5, 0.5, 0.5, 1.0)),
ChildOf(parent),
)).id()
}
}
}
pub fn consume_chunks(
channel: Res<StreamChannel>,
mut scrollback_q: Query<(Entity, &mut StreamScrollback)>,
mut commands: Commands,
) {
let Ok((scrollback_entity, mut scrollback)) = scrollback_q.single_mut() else { return };
for chunk in channel.rx.try_iter() {
if chunk.sigil == sigil::DOT && chunk.render == render::STATUS {
let m = read_kv(&chunk.payload);
let code: i32 = m.get("code")
.and_then(|c| String::from_utf8_lossy(&c.payload).parse().ok())
.unwrap_or(0);
scrollback.last_status = Some(code);
}
if let Some(id) = chunk.id {
if let Some(old) = scrollback.id_map.remove(&id) {
commands.entity(old).despawn();
}
}
let entity = dispatch(&mut commands, scrollback_entity, &chunk);
if let Some(id) = chunk.id {
scrollback.id_map.insert(id, entity);
}
}
}
pub struct StreamPlugin;
impl Plugin for StreamPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<StreamChannel>()
.add_systems(Update, consume_chunks);
}
}