use bevy::log::LogPlugin;
use bevy::prelude::*;
use bevy_brink::{
BrinkBindings, BrinkFlow, BrinkFlowRequest, BrinkPlugin, BrinkStoryAsset, BrinkTranscript,
LineTablesAsset, ProgramAsset, TranscriptAsset, advance_flow, capture_transcript,
render_transcript_asset,
};
use brink_runtime::{FlowInstance, transcript::read_transcript};
const STORY: &str = "\
You wake at the edge of a quiet shoreline.
The tide is unhurried; the gulls are not.
-> END
";
fn main() {
let mut app = App::new();
app.add_plugins((
LogPlugin::default(),
AssetPlugin::default(),
BrinkPlugin::<()>::default(),
));
app.init_resource::<BrinkBindings<()>>();
let Some((story, program_h, base_h)) = build_story(&mut app) else {
warn!("failed to build story");
return;
};
let flow = app
.world_mut()
.spawn((
BrinkFlowRequest::<()>::builder().story(story).build(),
BrinkTranscript::<()>::default(),
))
.id();
app.update();
drive_to_end(&mut app, flow);
app.update(); let live = app
.world()
.entity(flow)
.get::<BrinkTranscript<()>>()
.map(BrinkTranscript::text)
.unwrap_or_default();
info!("--- live transcript ---\n{live}");
let Some(bytes) = capture(&app, flow, &program_h) else {
warn!("capture failed (flow/program not ready)");
return;
};
info!("captured {} .brkt bytes", bytes.len());
let Some(reloaded) = reload_and_render(&app, &bytes, &program_h, &base_h) else {
warn!("reload/render failed");
return;
};
info!("--- reloaded transcript ---\n{reloaded}");
info!("done.");
}
fn build_story(
app: &mut App,
) -> Option<(
Handle<BrinkStoryAsset>,
Handle<ProgramAsset>,
Handle<LineTablesAsset>,
)> {
let out = brink_compiler::compile("demo.ink", |p| {
if p == "demo.ink" {
Ok(STORY.to_string())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no includes",
))
}
})
.ok()?;
let (program, tables) = brink_runtime::link(&out.data).ok()?;
let (_, initial_context) = FlowInstance::new_at_root(&program);
let world = app.world_mut();
let program_h = world
.resource_mut::<Assets<ProgramAsset>>()
.add(ProgramAsset {
program,
initial_context,
effect_rows: Vec::new(),
});
let base_h = world
.resource_mut::<Assets<LineTablesAsset>>()
.add(LineTablesAsset { tables });
let story_h = world
.resource_mut::<Assets<BrinkStoryAsset>>()
.add(BrinkStoryAsset {
program: program_h.clone(),
line_tables: base_h.clone(),
});
Some((story_h, program_h, base_h))
}
fn drive_to_end(app: &mut App, flow: Entity) {
loop {
match advance_flow::<()>(app.world_mut(), flow) {
Ok(line) => {
if line.is_terminal() {
break;
}
}
Err(err) => {
warn!("advance_flow failed: {err}");
break;
}
}
}
}
fn capture(app: &App, flow: Entity, program_h: &Handle<ProgramAsset>) -> Option<Vec<u8>> {
let flow_comp = app.world().entity(flow).get::<BrinkFlow<()>>()?;
let program = app
.world()
.resource::<Assets<ProgramAsset>>()
.get(program_h)?;
Some(capture_transcript::<()>(flow_comp, program))
}
fn reload_and_render(
app: &App,
bytes: &[u8],
program_h: &Handle<ProgramAsset>,
base_h: &Handle<LineTablesAsset>,
) -> Option<String> {
let data = read_transcript(bytes).ok()?;
let asset = TranscriptAsset { data };
let program = app
.world()
.resource::<Assets<ProgramAsset>>()
.get(program_h)?;
let base = app
.world()
.resource::<Assets<LineTablesAsset>>()
.get(base_h)?;
let lines = render_transcript_asset(&asset, program, base, None).ok()?;
Some(
lines
.iter()
.map(|(t, _)| t.as_str())
.collect::<Vec<_>>()
.join("\n"),
)
}