use aery::{prelude::*, tuple_traits::RelationEntries};
use bevy::prelude::*;
use prelude::*;
use ron_loader::loader::TalksLoader;
use traverse::{choice_handler, next_handler, set_has_started};
pub mod actors;
pub mod builder;
pub mod errors;
pub mod events;
pub mod prelude;
pub mod ron_loader;
pub mod talk;
pub mod talk_asset;
mod traverse;
pub struct TalksPlugin;
impl Plugin for TalksPlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<Aery>() {
app.add_plugins(Aery);
}
app.add_plugins(TalksEventsPlugin)
.register_asset_loader(TalksLoader)
.init_asset::<TalkData>()
.configure_sets(PreUpdate, TalksSet)
.add_systems(
PreUpdate,
(
next_handler.pipe(error_logger),
choice_handler.pipe(error_logger),
refire_handler.pipe(error_logger),
set_has_started.after(next_handler),
)
.in_set(TalksSet),
);
}
}
#[derive(SystemSet, Debug, Default, Clone, PartialEq, Eq, Hash)]
struct TalksSet;
fn error_logger(In(result): In<Result<(), NextActionError>>) {
if let Err(err) = result {
error!("Error: {err}");
}
}
fn refire_handler(
mut cmd: Commands,
mut reqs: EventReader<RefireNodeRequest>,
current_nodes: Query<(Entity, &Parent), With<CurrentNode>>,
start: Query<Entity, With<StartNode>>,
end: Query<Entity, With<EndNode>>,
all_actors: Query<&Actor>,
performers: Query<Relations<PerformedBy>>,
emitters: Query<&dyn NodeEventEmitter>,
type_registry: Res<AppTypeRegistry>,
mut start_ev_writer: EventWriter<StartEvent>,
mut end_ev_writer: EventWriter<EndEvent>,
) -> Result<(), NextActionError> {
if let Some(event) = reqs.read().next() {
for (current_node, talk_parent) in ¤t_nodes {
let this_talk = talk_parent.get();
if this_talk == event.talk {
maybe_emit_start_event(&start, current_node, &mut start_ev_writer, event.talk);
maybe_emit_end_event(&end, current_node, &mut end_ev_writer, event.talk);
let actors_in_node = retrieve_actors(&performers, current_node, &all_actors);
emit_events(
&mut cmd,
&emitters,
current_node,
&type_registry,
actors_in_node,
);
return Ok(());
}
}
return Err(NextActionError::NoTalk);
}
Ok(())
}
#[inline]
pub(crate) fn maybe_emit_start_event(
start: &Query<Entity, With<StartNode>>,
current_node: Entity,
start_ev_writer: &mut EventWriter<StartEvent>,
requested_talk: Entity,
) {
if start.get(current_node).is_ok() {
start_ev_writer.send(StartEvent(requested_talk));
}
}
#[inline]
pub(crate) fn maybe_emit_end_event(
end: &Query<Entity, With<EndNode>>,
next_node: Entity,
end_ev_writer: &mut EventWriter<EndEvent>,
requested_talk: Entity,
) {
if end.get(next_node).is_ok() {
end_ev_writer.send(EndEvent(requested_talk));
}
}
#[inline]
pub(crate) fn retrieve_actors(
performers: &Query<Relations<PerformedBy>>,
next_node: Entity,
all_actors: &Query<&Actor>,
) -> Vec<Actor> {
let mut actors_in_node = Vec::<Actor>::new();
if let Ok(actor_edges) = &performers.get(next_node) {
for actor in actor_edges.targets(PerformedBy) {
actors_in_node.push(all_actors.get(*actor).expect("Actor").clone());
}
}
actors_in_node
}
#[inline]
pub(crate) fn emit_events(
cmd: &mut Commands,
emitters: &Query<&dyn NodeEventEmitter>,
next_node: Entity,
type_registry: &Res<AppTypeRegistry>,
actors_in_node: Vec<Actor>,
) {
if let Ok(emitters) = emitters.get(next_node) {
let type_registry = type_registry.read();
for emitter in &emitters {
let emitted_event = emitter.make(&actors_in_node);
let event_type_id = emitted_event.type_id();
let reflect_event = type_registry
.get_type_data::<ReflectEvent>(event_type_id)
.expect("Event not registered for event type")
.clone();
cmd.add(move |world: &mut World| {
reflect_event.send(&*emitted_event, world);
});
}
}
}
#[cfg(test)]
mod tests {
use bevy::ecs::{
query::{ROQueryItem, WorldQuery},
system::Command,
};
use indexmap::indexmap;
use super::*;
pub fn talks_minimal_app() -> App {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), TalksPlugin));
app
}
#[inline]
#[track_caller]
pub fn get_comp<C: Component>(e: Entity, world: &mut World) -> &C {
world.entity(e).get::<C>().expect("Component")
}
#[inline]
#[track_caller]
pub fn count<Q: WorldQuery>(world: &mut World) -> usize {
world.query::<Q>().iter(&world).count()
}
#[inline]
#[track_caller]
pub fn single<Q: WorldQuery>(world: &mut World) -> ROQueryItem<Q> {
world.query::<Q>().single(world)
}
#[track_caller]
pub fn setup_and_next(talk_data: &TalkData) -> App {
let mut app = talks_minimal_app();
let builder = TalkBuilder::default().fill_with_talk_data(talk_data);
BuildTalkCommand::new(app.world.spawn(Talk::default()).id(), builder).apply(&mut app.world);
let (talk_ent, _) = single::<(Entity, With<Talk>)>(&mut app.world);
let (edges, _) = single::<(Relations<FollowedBy>, With<CurrentNode>)>(&mut app.world);
assert_eq!(edges.targets(FollowedBy).len(), 1);
let start_following_ent = edges.targets(FollowedBy)[0];
app.world.send_event(NextNodeRequest::new(talk_ent));
app.update();
let (next_e, _) = single::<(Entity, With<CurrentNode>)>(&mut app.world);
assert_eq!(next_e, start_following_ent);
app
}
#[test]
fn refire_request_sends_events() {
let script = indexmap! {
0 => Action { text: "Hello".to_string(), actors: vec!["actor_1".to_string()], ..default() }, };
let mut app = setup_and_next(&TalkData::new(script, vec![Actor::new("actor_1", "Actor")]));
let evs = app.world.resource::<Events<TextNodeEvent>>();
assert_eq!(evs.get_reader().read(evs).len(), 1);
let (talk_ent, _) = single::<(Entity, With<Talk>)>(&mut app.world);
app.world.send_event(RefireNodeRequest::new(talk_ent));
app.update();
let evs = app.world.resource::<Events<TextNodeEvent>>();
assert_eq!(evs.get_reader().read(evs).len(), 2);
}
}