bevy_stat_query 0.5.0

Blazing fast and versatile RPG stat system for the bevy engine.
Documentation
use bevy_ecs::{component::Component, world::World};
use bevy_reflect::TypePath;
use bevy_serde_lens::{BevyObject, DefaultInit, WorldExtension};
use bevy_stat_query::{
    operations::StatOperation, types::*, Fraction, Qualifier, Stat, StatExtension, StatMap,
};
use bevy_stat_query::{StatDeserializers, StatVTable, STAT_DESERIALIZERS};
use serde::{Deserialize, Serialize};

bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, TypePath)]
    pub struct MyFlags: u32 {
        const A = 1;
        const B = 2;
        const C = 4;
        const D = 8;
        const E = 16;
        const F = 32;
        const G = 64;
    }
}

macro_rules! impl_stat {
    ($($name: ident: $ty: ty),* $(,)?) => {
        $(#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $name;

        impl Stat for $name {
            type Value = $ty;

            fn name(&self) -> &'static str {
                stringify!($name)
            }

            fn values() -> impl IntoIterator<Item = Self> {
                [Self]
            }

            fn vtable() -> &'static StatVTable<$name> {
                static VTABLE: StatVTable<$name> = StatVTable::of::<$name>();
                &VTABLE
            }

            fn as_index(&self) -> u64 {
                0
            }

            fn from_index(_: u64) -> Self {
                Self
            }
        })*
    };
}

impl_stat!(
    SInt: StatInt<i32>,
    SUInt: StatInt<u32>,
    SFloat32: StatFloat<f32>,
    SFlags: StatFlags<MyFlags>,
    SString: Prioritized<Box<str>>,
    SIntPct: StatIntPercent<i32>,
    SIntFrac: StatIntRounded<i8, Fraction<i8>>,
    SMul: StatMult<f32>,
    SFracMul: StatMult<Fraction<i32>>
);

#[derive(Debug, Component, Serialize, Deserialize, Default, TypePath)]
pub struct BaseMarker;

#[derive(Debug, BevyObject)]
#[serde(transparent)]
pub struct Base {
    #[serde(skip)]
    marker: DefaultInit<BaseMarker>,
    map: StatMap<bool>,
}

#[derive(Debug, Component, Serialize, Deserialize, Default, TypePath)]
pub struct OpMarker;

#[derive(Debug, BevyObject)]
#[serde(transparent)]
pub struct Op {
    #[serde(skip)]
    marker: DefaultInit<OpMarker>,
    map: StatMap<bool>,
}

#[derive(Debug, Component, Serialize, Deserialize, Default, TypePath)]
pub struct FullMarker;

#[derive(Debug, BevyObject)]
#[serde(transparent)]
pub struct Full {
    #[serde(skip)]
    marker: DefaultInit<FullMarker>,
    map: StatMap<bool>,
}

#[test]
pub fn serde_test() {
    let mut world = World::new();
    world.register_deserialize_resource_cx::<StatDeserializers>(|res, callback| {
        STAT_DESERIALIZERS.set(res, callback)
    });
    world.register_stat::<SInt>();
    world.register_stat::<SUInt>();
    world.register_stat::<SFloat32>();
    world.register_stat::<SFlags>();
    world.register_stat::<SString>();
    world.register_stat::<SIntPct>();
    world.register_stat::<SIntFrac>();
    world.register_stat::<SMul>();
    world.register_stat::<SFracMul>();

    let q_false = Qualifier::all_of(false);
    world.spawn((BaseMarker, {
        let mut map = StatMap::new();
        map.insert_base(q_false, SInt, -4);
        map.insert_base(q_false, SUInt, 7);
        map.insert_base(q_false, SFloat32, 3.5);
        map.insert_base(q_false, SFlags, MyFlags::F);
        map.insert_base(q_false, SString, "Ferris the Rustacean".into());
        map.insert_base(q_false, SIntFrac, 69);
        map.insert_base(q_false, SIntPct, 420);
        map.insert_base(q_false, SMul, 1.5);
        map.insert_base(q_false, SFracMul, Fraction::new(44, 57));
        map
    }));
    let value = world
        .save::<BaseMarker, _>(serde_json::value::Serializer)
        .unwrap();
    world.despawn_bound_objects::<BaseMarker>();
    world.load::<BaseMarker, _>(&value).unwrap();
    let value2 = world
        .save::<BaseMarker, _>(serde_json::value::Serializer)
        .unwrap();
    assert_eq!(value, value2);

    world.spawn((OpMarker, {
        let mut map = StatMap::new();
        use StatOperation::*;
        map.modify(q_false, SInt, Add(-4));
        map.modify(q_false, SUInt, Max(7));
        map.modify(q_false, SFloat32, Min(3.5));
        map.modify(q_false, SFlags, Or(MyFlags::F));
        map.modify(
            q_false,
            SString,
            Or(Prioritized::new("Ferris the Rustacean".into(), 0)),
        );
        map.modify(q_false, SIntFrac, Mul(Fraction::new(43, -47)));
        map.modify(q_false, SIntPct, Mul(32));
        map.modify(q_false, SMul, Mul(102.125));
        map.modify(q_false, SFracMul, Mul(Fraction::new(0, 1)));
        map
    }));
    let value = world.save::<Op, _>(serde_json::value::Serializer).unwrap();
    world.despawn_bound_objects::<Op>();
    world.load::<Op, _>(&value).unwrap();

    let value2 = world.save::<Op, _>(serde_json::value::Serializer).unwrap();
    assert_eq!(value, value2);

    world.spawn((FullMarker, {
        let mut map = StatMap::new();
        map.insert(q_false, SInt, Default::default());
        map.insert(q_false, SUInt, Default::default());
        map.insert(q_false, SString, Default::default());
        map.insert(q_false, SIntFrac, Default::default());
        map.insert(q_false, SIntPct, Default::default());
        map.insert(q_false, SFracMul, Default::default());
        map.insert(q_false, SFloat32, Default::default());
        map.insert(q_false, SFlags, Default::default());
        map.insert(q_false, SMul, Default::default());
        map
    }));
    let value = world
        .save::<Full, _>(serde_json::value::Serializer)
        .unwrap();
    world.despawn_bound_objects::<Full>();
    world.load::<Full, _>(&value).unwrap();
    let value2 = world
        .save::<Full, _>(serde_json::value::Serializer)
        .unwrap();
    assert_eq!(value, value2);
    world.despawn_bound_objects::<Full>();

    world.spawn((FullMarker, {
        let mut map = StatMap::new();
        map.insert(q_false, SInt, Default::default());
        map.insert(q_false, SUInt, Default::default());
        map.insert(q_false, SString, Default::default());
        map.insert(q_false, SIntFrac, Default::default());
        map.insert(q_false, SIntPct, Default::default());
        map.insert(q_false, SFracMul, Default::default());
        map.insert(q_false, SFloat32, Default::default());
        map.insert(q_false, SFlags, Default::default());
        map.insert(q_false, SMul, Default::default());
        map
    }));
    use postcard::ser_flavors::Flavor;
    let mut vec = postcard::Serializer {
        output: postcard::ser_flavors::AllocVec::new(),
    };
    world.save::<Full, _>(&mut vec).unwrap();
    let result = vec.output.finalize().unwrap();
    world.despawn_bound_objects::<Full>();
    world
        .load::<Full, _>(&mut postcard::Deserializer::from_bytes(&result))
        .unwrap();

    let mut vec2 = postcard::Serializer {
        output: postcard::ser_flavors::AllocVec::new(),
    };
    world.save::<Full, _>(&mut vec2).unwrap();
    let result2 = vec2.output.finalize().unwrap();
    assert_eq!(result, result2);
}