#[cfg(feature = "bevy_many_relationship_edges")]
mod many_relationship_edges {
use bevy::prelude::*;
use bevy_many_relationships::{ManyRelatedEntityCommands, ManyRelationshipsPlugin};
use bevy_persistence_database::bevy::components::Guid;
use bevy_persistence_database::core::session::commit_sync;
use crate::common::*;
use bevy_persistence_database_derive::db_matrix_test;
#[db_matrix_test]
fn test_relationship_persists_new_edge() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.add_plugins(ManyRelationshipsPlugin);
let a = app.world_mut().spawn(Health { value: 10 }).id();
let b = app.world_mut().spawn(Health { value: 20 }).id();
app.world_mut()
.commands()
.entity(a)
.set_outgoing_to::<Friendship>(b, Friendship { strength: 0.9 });
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("commit failed");
let guid_a = app
.world()
.get::<Guid>(a)
.expect("library should auto-assign a Guid to entity a")
.id()
.to_string();
let guid_b = app
.world()
.get::<Guid>(b)
.expect("library should auto-assign a Guid to entity b")
.id()
.to_string();
let edges = run_async(db.query_edges(
&bevy_persistence_database::core::query::EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["Friendship".to_string()],
from_guids: vec![guid_a],
to_guids: vec![guid_b],
depth: 1,
},
))
.expect("query_edges failed");
assert_eq!(edges.len(), 1);
let payload = edges[0].payload.as_ref().expect("payload missing");
let strength = payload
.get("strength")
.and_then(|v| v.as_f64())
.expect("strength missing");
assert!((strength - 0.9).abs() < 1e-6);
}
#[db_matrix_test]
fn test_many_relationships_of_same_type_from_one_entity() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.add_plugins(ManyRelationshipsPlugin);
let a = app.world_mut().spawn(Health { value: 1 }).id();
let b = app.world_mut().spawn(Health { value: 2 }).id();
let c = app.world_mut().spawn(Health { value: 3 }).id();
app.world_mut()
.commands()
.entity(a)
.add_outgoing_to::<Friendship>(b, Friendship { strength: 0.8 })
.add_outgoing_to::<Friendship>(c, Friendship { strength: 0.5 });
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("commit failed");
let guid_a = app.world().get::<Guid>(a).unwrap().id().to_string();
let guid_b = app.world().get::<Guid>(b).unwrap().id().to_string();
let guid_c = app.world().get::<Guid>(c).unwrap().id().to_string();
let edges = run_async(db.query_edges(
&bevy_persistence_database::core::query::EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["Friendship".to_string()],
from_guids: vec![guid_a.clone()],
to_guids: vec![],
depth: 1,
},
))
.expect("query_edges failed");
assert_eq!(edges.len(), 2, "expected two outgoing Friendship edges from a");
let to_b = edges.iter().find(|e| e.to_guid == guid_b).expect("a→b edge missing");
let to_c = edges.iter().find(|e| e.to_guid == guid_c).expect("a→c edge missing");
let strength_b = to_b.payload.as_ref().and_then(|p| p.get("strength")).and_then(|v| v.as_f64()).unwrap();
let strength_c = to_c.payload.as_ref().and_then(|p| p.get("strength")).and_then(|v| v.as_f64()).unwrap();
assert!((strength_b - 0.8).abs() < 1e-6);
assert!((strength_c - 0.5).abs() < 1e-6);
let outgoing = app
.world()
.get::<bevy_many_relationships::OutgoingRelationships<Friendship>>(a)
.expect("a should have OutgoingRelationships");
assert!(outgoing.contains(b), "a should have outgoing edge to b");
assert!(outgoing.contains(c), "a should have outgoing edge to c");
}
#[db_matrix_test]
fn test_relationship_set_via_system_commands() {
#[derive(Component)]
struct FriendshipSource;
#[derive(Component)]
struct FriendshipTarget;
#[derive(Resource)]
struct EntitiesReady;
fn establish_friendship(
source_q: Query<Entity, With<FriendshipSource>>,
target_q: Query<Entity, With<FriendshipTarget>>,
mut commands: Commands,
) {
let source = source_q.single().unwrap();
let target = target_q.single().unwrap();
commands
.entity(source)
.set_outgoing_to::<Friendship>(target, Friendship { strength: 0.75 });
}
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.add_plugins(ManyRelationshipsPlugin);
app.add_systems(
Update,
establish_friendship.run_if(resource_exists::<EntitiesReady>),
);
let a = app
.world_mut()
.spawn((Health { value: 1 }, FriendshipSource))
.id();
let b = app
.world_mut()
.spawn((Health { value: 2 }, FriendshipTarget))
.id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("entity commit failed");
app.insert_resource(EntitiesReady);
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("relationship commit failed");
let guid_a = app
.world()
.get::<Guid>(a)
.expect("Guid for a")
.id()
.to_string();
let guid_b = app
.world()
.get::<Guid>(b)
.expect("Guid for b")
.id()
.to_string();
let edges = run_async(db.query_edges(
&bevy_persistence_database::core::query::EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["Friendship".to_string()],
from_guids: vec![guid_a],
to_guids: vec![guid_b],
depth: 1,
},
))
.expect("query_edges failed");
assert_eq!(
edges.len(),
1,
"edge should be persisted from system-Commands usage"
);
let strength = edges[0]
.payload
.as_ref()
.and_then(|p| p.get("strength"))
.and_then(|v| v.as_f64())
.expect("strength payload missing");
assert!((strength - 0.75).abs() < 1e-6);
}
#[db_matrix_test]
fn test_relationship_removal_deletes_edge() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.add_plugins(ManyRelationshipsPlugin);
let a = app.world_mut().spawn(Health { value: 1 }).id();
let b = app.world_mut().spawn(Health { value: 2 }).id();
app.world_mut()
.commands()
.entity(a)
.set_outgoing_to::<Friendship>(b, Friendship { strength: 0.7 });
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("insert commit failed");
let guid_a = app.world().get::<Guid>(a).unwrap().id().to_string();
let guid_b = app.world().get::<Guid>(b).unwrap().id().to_string();
let edges = run_async(db.query_edges(
&bevy_persistence_database::core::query::EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["Friendship".to_string()],
from_guids: vec![guid_a.clone()],
to_guids: vec![guid_b.clone()],
depth: 1,
},
))
.expect("first query failed");
assert_eq!(edges.len(), 1);
app.world_mut()
.commands()
.entity(a)
.remove_outgoing_to::<Friendship>(b);
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("relationship removal commit failed");
let edges_after = run_async(db.query_edges(
&bevy_persistence_database::core::query::EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["Friendship".to_string()],
from_guids: vec![guid_a],
to_guids: vec![guid_b],
depth: 1,
},
))
.expect("second query failed");
assert_eq!(edges_after.len(), 0, "edge should be removed after relationship removal");
}
}
#[cfg(not(feature = "bevy_many_relationship_edges"))]
mod bevy_native {
use bevy::prelude::*;
use bevy_persistence_database::bevy::components::Guid;
use bevy_persistence_database::core::query::EdgeQuerySpecification;
use bevy_persistence_database::core::session::commit_sync;
use crate::common::*;
use bevy_persistence_database_derive::db_matrix_test;
#[db_matrix_test]
fn test_relationship_persists_edge() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let source = app.world_mut().spawn(Health { value: 1 }).id();
let target = app.world_mut().spawn(Health { value: 2 }).id();
app.world_mut().entity_mut(source).insert(MemberOf(target));
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("commit failed");
let guid_source = app.world().get::<Guid>(source).expect("auto-guid for source").id().to_string();
let guid_target = app.world().get::<Guid>(target).expect("auto-guid for target").id().to_string();
let edges = run_async(db.query_edges(&EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["MemberOf".to_string()],
from_guids: vec![guid_source.clone()],
to_guids: vec![guid_target.clone()],
depth: 1,
}))
.expect("query_edges failed");
assert_eq!(edges.len(), 1, "expected one MemberOf edge");
assert_eq!(edges[0].relationship_type, "MemberOf");
assert_eq!(edges[0].from_guid, guid_source);
assert_eq!(edges[0].to_guid, guid_target);
assert!(
edges[0].payload.is_none(),
"native Bevy relationship should have no payload"
);
}
#[db_matrix_test]
fn test_relationship_removal_deletes_edge() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let source = app.world_mut().spawn(Health { value: 10 }).id();
let target = app.world_mut().spawn(Health { value: 20 }).id();
app.world_mut().entity_mut(source).insert(MemberOf(target));
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("insert commit failed");
let guid_source = app.world().get::<Guid>(source).expect("auto-guid for source").id().to_string();
let guid_target = app.world().get::<Guid>(target).expect("auto-guid for target").id().to_string();
let edges = run_async(db.query_edges(&EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["MemberOf".to_string()],
from_guids: vec![guid_source.clone()],
to_guids: vec![guid_target.clone()],
depth: 1,
}))
.expect("first query failed");
assert_eq!(edges.len(), 1);
app.world_mut().entity_mut(source).remove::<MemberOf>();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("relationship removal commit failed");
let edges_after = run_async(db.query_edges(&EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["MemberOf".to_string()],
from_guids: vec![guid_source],
to_guids: vec![guid_target],
depth: 1,
}))
.expect("second query failed");
assert_eq!(
edges_after.len(),
0,
"edge should be removed after relationship removal"
);
}
#[db_matrix_test]
fn test_relationship_set_via_system_commands() {
#[derive(Component)]
struct RelSource;
#[derive(Component)]
struct RelTarget;
#[derive(Resource)]
struct EntitiesReady;
fn establish_membership(
source_q: Query<Entity, With<RelSource>>,
target_q: Query<Entity, With<RelTarget>>,
mut commands: Commands,
) {
let source = source_q.single().unwrap();
let target = target_q.single().unwrap();
commands.entity(source).insert(MemberOf(target));
}
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.add_systems(Update, establish_membership.run_if(resource_exists::<EntitiesReady>));
let source = app.world_mut().spawn((Health { value: 1 }, RelSource)).id();
let target = app.world_mut().spawn((Health { value: 2 }, RelTarget)).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("entity commit failed");
app.insert_resource(EntitiesReady);
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("relationship commit failed");
let guid_source = app.world().get::<Guid>(source).expect("Guid for source").id().to_string();
let guid_target = app.world().get::<Guid>(target).expect("Guid for target").id().to_string();
let edges = run_async(db.query_edges(&EdgeQuerySpecification {
store: TEST_STORE.to_string(),
relationship_types: vec!["MemberOf".to_string()],
from_guids: vec![guid_source],
to_guids: vec![guid_target],
depth: 1,
}))
.expect("query_edges failed");
assert_eq!(edges.len(), 1, "edge should be persisted from system-Commands usage");
assert_eq!(edges[0].relationship_type, "MemberOf");
}}