use std::any::TypeId;
use std::collections::BTreeMap;
use std::fmt::{self, Debug};
use std::sync::Arc;
use downcast_rs::{impl_downcast, Downcast};
use crate::time::Tick;
use crate::transaction::{
CommitError, Merge, PreconditionFailed, Transaction, TransactionConflict, Transactional,
};
use crate::universe::{RefVisitor, UniverseTransaction, VisitRefs};
pub trait Behavior<H: BehaviorHost>: Debug + Send + Sync + Downcast + VisitRefs + 'static {
fn step(&self, _context: &BehaviorContext<'_, H>, _tick: Tick) -> UniverseTransaction {
UniverseTransaction::default()
}
fn alive(&self, context: &BehaviorContext<'_, H>) -> bool;
fn ephemeral(&self) -> bool;
}
impl_downcast!(Behavior<H> where H: BehaviorHost);
pub trait BehaviorHost: Transactional + 'static {
type Attachment: Debug + Clone + Eq + 'static;
}
#[non_exhaustive]
pub struct BehaviorContext<'a, H: BehaviorHost> {
pub host: &'a H,
pub attachment: &'a H::Attachment,
host_transaction_binder: &'a dyn Fn(H::Transaction) -> UniverseTransaction,
self_transaction_binder: &'a dyn Fn(Arc<dyn Behavior<H>>) -> UniverseTransaction,
}
impl<'a, H: BehaviorHost> BehaviorContext<'a, H> {
pub fn bind_host(&self, transaction: H::Transaction) -> UniverseTransaction {
(self.host_transaction_binder)(transaction)
}
pub fn replace_self<B: Behavior<H> + 'static>(&self, new_behavior: B) -> UniverseTransaction {
(self.self_transaction_binder)(Arc::new(new_behavior))
}
}
impl<'a, H: BehaviorHost + Debug> Debug for BehaviorContext<'a, H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BehaviorContext")
.field("host", &self.host)
.finish_non_exhaustive()
}
}
pub struct BehaviorSet<H: BehaviorHost> {
items: Vec<BehaviorSetEntry<H>>,
}
impl<H: BehaviorHost> BehaviorSet<H> {
pub(crate) fn new() -> Self {
BehaviorSet { items: Vec::new() }
}
pub fn query<T: Behavior<H>>(&self) -> impl Iterator<Item = QueryItem<'_, H, T>> + '_ {
self.query_any(Some(TypeId::of::<T>())).map(
|QueryItem {
attachment,
behavior,
}| QueryItem {
attachment,
behavior: behavior.downcast_ref::<T>().unwrap(),
},
)
}
pub fn query_any<'a>(
&'a self,
type_filter: Option<TypeId>,
) -> impl Iterator<Item = QueryItem<'a, H, dyn Behavior<H> + 'static>> + 'a {
self.items
.iter()
.map(
move |entry: &'a BehaviorSetEntry<H>| -> QueryItem<'a, H, dyn Behavior<H> + 'static> {
QueryItem {
attachment: &entry.attachment,
behavior: &*entry.behavior,
}
},
)
.filter(move |qi| type_filter.map_or(true, |t| (*qi.behavior).type_id() == t))
}
pub(crate) fn step(
&self,
host: &H,
host_transaction_binder: &dyn Fn(H::Transaction) -> UniverseTransaction,
set_transaction_binder: impl Fn(BehaviorSetTransaction<H>) -> H::Transaction,
tick: Tick,
) -> UniverseTransaction {
let mut transactions = Vec::new();
for (index, entry) in self.items.iter().enumerate() {
let context = &BehaviorContext {
host,
attachment: &entry.attachment,
host_transaction_binder,
self_transaction_binder: &|new_behavior| {
host_transaction_binder(set_transaction_binder(
BehaviorSetTransaction::replace(index, new_behavior),
))
},
};
if entry.behavior.alive(context) {
transactions.push(entry.behavior.step(context, tick));
} else {
}
}
let transaction = transactions
.into_iter()
.reduce(|a, b| a.merge(b).expect("TODO: handle merge failure"));
transaction.unwrap_or_default()
}
}
impl<H: BehaviorHost> std::fmt::Debug for BehaviorSet<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BehaviorSet(")?;
f.debug_list().entries(&*self.items).finish()?;
write!(f, ")")?;
Ok(())
}
}
impl<H: BehaviorHost> VisitRefs for BehaviorSet<H> {
fn visit_refs(&self, visitor: &mut dyn RefVisitor) {
let Self { items } = self;
for entry in items {
entry.behavior.visit_refs(visitor);
}
}
}
impl<H: BehaviorHost> Transactional for BehaviorSet<H> {
type Transaction = BehaviorSetTransaction<H>;
}
struct BehaviorSetEntry<H: BehaviorHost> {
attachment: H::Attachment,
behavior: Arc<dyn Behavior<H>>,
}
impl<H: BehaviorHost> Clone for BehaviorSetEntry<H> {
fn clone(&self) -> Self {
Self {
attachment: self.attachment.clone(),
behavior: self.behavior.clone(),
}
}
}
impl<H: BehaviorHost> Debug for BehaviorSetEntry<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let BehaviorSetEntry {
attachment,
behavior,
} = self;
behavior.fmt(f)?; write!(f, " @ {attachment:?}")?; Ok(())
}
}
impl<H: BehaviorHost> PartialEq for BehaviorSetEntry<H> {
#[allow(clippy::vtable_address_comparisons)] fn eq(&self, other: &Self) -> bool {
self.attachment == other.attachment && Arc::ptr_eq(&self.behavior, &other.behavior)
}
}
#[non_exhaustive]
pub struct QueryItem<'a, H: BehaviorHost, B: Behavior<H> + ?Sized> {
pub attachment: &'a H::Attachment,
pub behavior: &'a B,
}
impl<'a, H: BehaviorHost, B: Behavior<H> + ?Sized> Clone for QueryItem<'a, H, B> {
fn clone(&self) -> Self {
Self {
attachment: self.attachment,
behavior: self.behavior,
}
}
}
impl<'a, H: BehaviorHost, B: Behavior<H> + ?Sized> Debug for QueryItem<'a, H, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let QueryItem {
attachment,
behavior,
} = self;
behavior.fmt(f)?; write!(f, " @ {attachment:?}")?; Ok(())
}
}
#[derive(Debug)]
pub struct BehaviorSetTransaction<H: BehaviorHost> {
replace: BTreeMap<usize, Arc<dyn Behavior<H>>>,
insert: Vec<BehaviorSetEntry<H>>,
}
impl<H: BehaviorHost> BehaviorSetTransaction<H> {
pub(crate) fn is_empty(&self) -> bool {
self.replace.is_empty() && self.insert.is_empty()
}
fn replace(index: usize, new: Arc<dyn Behavior<H>>) -> Self {
let mut replace = BTreeMap::new();
replace.insert(index, new);
BehaviorSetTransaction {
replace,
..Default::default()
}
}
pub fn insert(attachment: H::Attachment, behavior: Arc<dyn Behavior<H>>) -> Self {
BehaviorSetTransaction {
insert: vec![BehaviorSetEntry {
attachment,
behavior,
}],
..Default::default()
}
}
}
impl<H: BehaviorHost> Transaction<BehaviorSet<H>> for BehaviorSetTransaction<H> {
type CommitCheck = ();
type Output = ();
fn check(&self, target: &BehaviorSet<H>) -> Result<Self::CommitCheck, PreconditionFailed> {
if matches!(self.replace.keys().copied().max(), Some(index) if index >= target.items.len())
{
Err(PreconditionFailed {
location: "BehaviorSet",
problem: "behavior(s) not found",
})
} else {
Ok(())
}
}
fn commit(
&self,
target: &mut BehaviorSet<H>,
(): Self::CommitCheck,
) -> Result<(), CommitError> {
for (index, new) in &self.replace {
target.items[*index].behavior = new.clone();
}
target.items.extend(self.insert.iter().cloned());
Ok(())
}
}
impl<H: BehaviorHost> Merge for BehaviorSetTransaction<H> {
type MergeCheck = ();
fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, TransactionConflict> {
if self
.replace
.keys()
.any(|slot| other.replace.contains_key(slot))
{
return Err(TransactionConflict {});
}
Ok(())
}
fn commit_merge(mut self, other: Self, (): Self::MergeCheck) -> Self {
self.replace.extend(other.replace);
self.insert.extend(other.insert);
self
}
}
impl<H: BehaviorHost> Clone for BehaviorSetTransaction<H> {
fn clone(&self) -> Self {
Self {
replace: self.replace.clone(),
insert: self.insert.clone(),
}
}
}
impl<H: BehaviorHost> Default for BehaviorSetTransaction<H> {
fn default() -> Self {
Self {
replace: Default::default(),
insert: Default::default(),
}
}
}
impl<H: BehaviorHost> PartialEq for BehaviorSetTransaction<H> {
#[allow(clippy::vtable_address_comparisons)] fn eq(&self, other: &Self) -> bool {
self.replace.iter().zip(other.replace.iter()).all(
|((a_index, a_behavior), (b_index, b_behavior))| {
a_index == b_index && Arc::ptr_eq(a_behavior, b_behavior)
},
) && self.insert == other.insert
}
}
impl<H: BehaviorHost> Eq for BehaviorSetTransaction<H> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::character::{Character, CharacterTransaction};
use crate::math::FreeCoordinate;
use crate::physics::BodyTransaction;
use crate::space::Space;
use crate::transaction::TransactionTester;
use crate::universe::Universe;
use indoc::indoc;
#[test]
fn behavior_set_debug() {
use pretty_assertions::assert_eq;
#[derive(Debug)]
struct DebugBehavior {
_x: i32,
}
impl Behavior<Character> for DebugBehavior {
fn alive(&self, _context: &BehaviorContext<'_, Character>) -> bool {
true
}
fn ephemeral(&self) -> bool {
false
}
}
impl VisitRefs for DebugBehavior {
fn visit_refs(&self, _visitor: &mut dyn RefVisitor) {}
}
let mut set = BehaviorSet::<Character>::new();
assert_eq!(format!("{set:?}"), "BehaviorSet([])");
assert_eq!(format!("{set:#?}"), "BehaviorSet([])");
BehaviorSetTransaction::insert((), Arc::new(DebugBehavior { _x: 1 }))
.execute(&mut set)
.unwrap();
assert_eq!(
format!("{set:?}"),
"BehaviorSet([DebugBehavior { _x: 1 } @ ()])"
);
assert_eq!(
format!("{set:#?}\n"),
indoc! {"
BehaviorSet([
DebugBehavior {
_x: 1,
} @ (),
])
"},
);
}
#[derive(Debug, PartialEq)]
struct SelfModifyingBehavior {
foo: u32,
}
impl Behavior<Character> for SelfModifyingBehavior {
fn step(
&self,
context: &BehaviorContext<'_, Character>,
_tick: Tick,
) -> UniverseTransaction {
context
.replace_self(SelfModifyingBehavior { foo: self.foo + 1 })
.merge(
context.bind_host(CharacterTransaction::body(BodyTransaction {
delta_yaw: FreeCoordinate::from(self.foo),
})),
)
.unwrap()
}
fn alive(&self, _context: &BehaviorContext<'_, Character>) -> bool {
true
}
fn ephemeral(&self) -> bool {
false
}
}
impl VisitRefs for SelfModifyingBehavior {
fn visit_refs(&self, _visitor: &mut dyn RefVisitor) {}
}
#[test]
fn self_transaction() {
let mut u = Universe::new();
let space = u.insert_anonymous(Space::empty_positive(1, 1, 1));
let mut character = Character::spawn_default(space);
character.add_behavior(SelfModifyingBehavior { foo: 1 });
let character = u.insert_anonymous(character);
u.step(Tick::arbitrary());
u.step(Tick::arbitrary());
assert_eq!(character.read().unwrap().body.yaw, 3.0);
}
#[test]
fn query() {
#[derive(Debug, Eq, PartialEq)]
struct Expected;
#[derive(Debug, Eq, PartialEq)]
struct Unexpected;
#[derive(Debug, Eq, PartialEq)]
struct Q<T>(T);
impl<T: Debug + Send + Sync + 'static> Behavior<Character> for Q<T> {
fn alive(&self, _context: &BehaviorContext<'_, Character>) -> bool {
true
}
fn ephemeral(&self) -> bool {
false
}
}
impl<T> VisitRefs for Q<T> {
fn visit_refs(&self, _visitor: &mut dyn RefVisitor) {}
}
let mut set = BehaviorSet::<Character>::new();
let arc_qe = Arc::new(Q(Expected));
BehaviorSetTransaction::insert((), arc_qe.clone())
.execute(&mut set)
.unwrap();
let arc_qu = Arc::new(Q(Unexpected));
BehaviorSetTransaction::insert((), arc_qu.clone())
.execute(&mut set)
.unwrap();
assert_eq!(
set.query::<Q<Expected>>()
.map(|qi| qi.behavior)
.collect::<Vec<_>>(),
vec![&Q(Expected)],
);
assert_eq!(
set.query_any(None)
.map(|qi| qi.behavior as *const dyn Behavior<Character>)
.collect::<Vec<_>>(),
vec![
Arc::as_ptr(&arc_qe) as *const dyn Behavior<Character>,
Arc::as_ptr(&arc_qu) as *const dyn Behavior<Character>
],
)
}
#[test]
fn systematic() {
let b1 = Arc::new(SelfModifyingBehavior { foo: 100 });
let b2 = Arc::new(SelfModifyingBehavior { foo: 200 });
TransactionTester::new()
.transaction(BehaviorSetTransaction::default(), |_, _| Ok(()))
.transaction(BehaviorSetTransaction::insert((), b1), |_, after| {
after
.query::<SelfModifyingBehavior>()
.map(|item| item.behavior)
.find(|b| b.foo == 100)
.ok_or("expected b1")?;
Ok(())
})
.transaction(BehaviorSetTransaction::insert((), b2), |_, after| {
after
.query::<SelfModifyingBehavior>()
.map(|item| item.behavior)
.find(|b| b.foo == 200)
.ok_or("expected b2")?;
Ok(())
})
.target(BehaviorSet::new)
.test()
}
}