use crate::site::{
Affiliation, ChangeCurrentScenario, CurrentScenario, GetModifier, Modifier, Pending,
ScenarioModifiers, Trashcan, UpdateModifier, UpdateModifierEvent,
};
use bevy::{
ecs::{component::Mutable, system::SystemState, world::OnDespawn},
prelude::*,
};
use std::{collections::HashSet, fmt::Debug};
pub trait Property: Component<Mutability = Mutable> + Debug + Default + Clone + PartialEq {
fn get_fallback(_for_element: Entity, _in_scenario: Entity, _world: &mut World) -> Self;
fn on_new_element(_for_element: Entity, _in_scenario: Entity, _value: Self, _world: &mut World);
fn on_new_scenario<E: Element>(
_in_scenario: Entity,
_affiliation: Affiliation<Entity>,
_world: &mut World,
);
fn elements_with_modifiers(
in_scenario: Entity,
children: &Query<&Children>,
modifiers: &Query<(&Modifier<Self>, &Affiliation<Entity>)>,
) -> HashSet<Entity> {
let mut have_element = HashSet::new();
if let Ok(scenario_children) = children.get(in_scenario) {
for child in scenario_children {
if let Ok((_, a)) = modifiers.get(*child) {
if let Some(a) = a.0 {
have_element.insert(a);
}
}
}
}
have_element
}
}
pub trait StandardProperty:
Component<Mutability = Mutable> + Debug + Default + Clone + PartialEq
{
}
impl<T: StandardProperty> Property for T {
fn get_fallback(for_element: Entity, _in_scenario: Entity, world: &mut World) -> Self {
let mut state: SystemState<Query<&LastSetValue<Self>>> = SystemState::new(world);
let last_set_value = state.get(world);
last_set_value
.get(for_element)
.map(|value| (**value).clone())
.unwrap_or(Self::default())
}
fn on_new_element(_for_element: Entity, _in_scenario: Entity, _value: T, _world: &mut World) {
}
fn on_new_scenario<E: Element>(
_in_scenario: Entity,
_affiliation: Affiliation<Entity>,
_world: &mut World,
) {
}
}
#[derive(Debug, Event, Clone, Copy)]
pub struct UseModifier {
pub for_element: Entity,
pub in_scenario: Entity,
}
impl UseModifier {
pub fn new(for_element: Entity, in_scenario: Entity) -> Self {
Self {
for_element,
in_scenario,
}
}
}
#[derive(Component, Clone, Debug, Deref, DerefMut)]
pub struct LastSetValue<T: Property>(pub T);
impl<T: Property> LastSetValue<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
pub trait Element: Component<Mutability = Mutable> + Debug + Clone + 'static + Send + Sync {}
pub struct PropertyPlugin<T: Property, E: Element> {
_ignore: std::marker::PhantomData<(T, E)>,
}
impl<T: Property, E: Element> Default for PropertyPlugin<T, E> {
fn default() -> Self {
Self {
_ignore: Default::default(),
}
}
}
impl<T: Property, E: Element> Plugin for PropertyPlugin<T, E> {
fn build(&self, app: &mut App) {
app.add_event::<UseModifier>()
.add_event::<UpdateModifierEvent<T>>()
.add_observer(on_update_modifier_event::<T, E>)
.add_observer(on_use_modifier::<T, E>)
.add_observer(on_add_property::<T, E>)
.add_observer(on_add_scenario::<T, E>)
.add_observer(on_remove_element::<E>)
.add_systems(Update, update_changed_property::<T, E>);
}
}
fn on_update_modifier_event<T: Property, E: Element>(
trigger: Trigger<UpdateModifierEvent<T>>,
mut commands: Commands,
mut property_modifiers: Query<&mut Modifier<T>, With<Affiliation<Entity>>>,
mut scenarios: Query<(&mut ScenarioModifiers<Entity>, &Affiliation<Entity>)>,
) {
let event = trigger.event();
let Ok((mut scenario_modifiers, parent_scenario)) = scenarios.get_mut(event.scenario) else {
return;
};
let modifier_entity = scenario_modifiers.get(&event.element);
let property_modifier = modifier_entity.and_then(|e| property_modifiers.get_mut(*e).ok());
match &event.update_mode {
UpdateModifier::<T>::Modify(new_value) => {
if let Some(mut property_modifier) = property_modifier {
**property_modifier = new_value.clone();
} else if let Some(modifier_entity) = modifier_entity {
commands
.entity(*modifier_entity)
.insert(Modifier::<T>::new(new_value.clone()));
} else {
let modifier_entity = commands
.spawn(Modifier::<T>::new(new_value.clone()))
.insert(Affiliation(Some(event.element)))
.insert(ChildOf(event.scenario))
.id();
scenario_modifiers.insert(event.element, modifier_entity);
}
commands
.entity(event.element)
.insert(LastSetValue::<T>::new(new_value.clone()));
}
UpdateModifier::<T>::Reset => {
if parent_scenario.0.is_some() {
if let Some(modifier_entity) = modifier_entity {
commands.entity(*modifier_entity).remove::<Modifier<T>>();
}
}
}
}
if event.trigger_use_modifier {
commands.trigger(UseModifier::new(event.element, event.scenario));
}
}
fn on_use_modifier<T: Property, E: Element>(
trigger: Trigger<UseModifier>,
world: &mut World,
values: &mut QueryState<&mut T, With<E>>,
scenario_state: &mut SystemState<(Commands, Res<CurrentScenario>, GetModifier<Modifier<T>>)>,
) {
let event = trigger.event();
let fallback_value = T::get_fallback(event.for_element, event.in_scenario, world);
let (mut commands, current_scenario, get_modifier) = scenario_state.get(world);
if !current_scenario.0.is_some_and(|e| e == event.in_scenario) {
return;
}
if !values.get(world, event.for_element).is_ok() {
return;
}
let new_value: T = if let Some(modifier) =
get_modifier.get(event.in_scenario, event.for_element)
{
(**modifier).clone()
} else {
if let Some(modifier_entity) = get_modifier
.scenarios
.get(event.in_scenario)
.ok()
.and_then(|(scenario_modifiers, _)| scenario_modifiers.get(&event.for_element))
{
commands
.entity(*modifier_entity)
.insert(Modifier::<T>::new(fallback_value));
scenario_state.apply(world);
} else {
let mut target_scenario = event.in_scenario;
let mut root_scenario: Option<Entity> = None;
while root_scenario.is_none() {
let Ok((_, parent_scenario)) = get_modifier.scenarios.get(target_scenario) else {
break;
};
if let Some(parent_entity) = parent_scenario.0 {
target_scenario = parent_entity;
} else {
root_scenario = Some(target_scenario);
break;
}
}
if let Some(root_scenario_entity) = root_scenario {
world.trigger(UpdateModifier::modify(
root_scenario_entity,
event.for_element,
fallback_value,
))
}
}
return;
};
let changed = values
.get_mut(world, event.for_element)
.is_ok_and(|mut value| {
*value = new_value.clone();
true
});
if changed {
world
.commands()
.entity(event.for_element)
.insert(LastSetValue::<T>::new(new_value));
}
}
fn on_add_property<T: Property, E: Element>(
trigger: Trigger<OnAdd, T>,
world: &mut World,
state: &mut SystemState<(Query<&T, With<E>>, Res<CurrentScenario>)>,
) {
let (values, current_scenario) = state.get_mut(world);
let Ok(value) = values.get(trigger.target()) else {
return;
};
let Some(scenario_entity) = current_scenario.0 else {
return;
};
T::on_new_element(trigger.target(), scenario_entity, value.clone(), world);
}
fn on_add_scenario<T: Property + 'static + Send + Sync, E: Element>(
trigger: Trigger<OnAdd, ScenarioModifiers<Entity>>,
world: &mut World,
state: &mut SystemState<Query<&Affiliation<Entity>>>,
events_state: &mut SystemState<EventWriter<ChangeCurrentScenario>>,
) {
let scenarios = state.get_mut(world);
let Ok(affiliation) = scenarios.get(trigger.target()) else {
return;
};
T::on_new_scenario::<E>(trigger.target(), *affiliation, world);
let mut change_current_scenario = events_state.get_mut(world);
change_current_scenario.write(ChangeCurrentScenario(trigger.target()));
}
pub fn on_remove_element<E: Element>(
trigger: Trigger<OnDespawn, E>,
trashcan: Res<Trashcan>,
mut commands: Commands,
mut scenarios: Query<&mut ScenarioModifiers<Entity>>,
) {
for mut scenario_modifiers in scenarios.iter_mut() {
if let Some(modifier_entity) = scenario_modifiers.remove(&trigger.target()) {
commands.entity(modifier_entity).insert(ChildOf(trashcan.0));
}
}
}
fn update_changed_property<T: Property, E: Element>(
mut commands: Commands,
mut change_current_scenario: EventReader<ChangeCurrentScenario>,
changed_values: Query<
(Entity, Ref<T>, Option<Ref<LastSetValue<T>>>),
(With<E>, Without<Pending>),
>,
current_scenario: Res<CurrentScenario>,
) {
for ChangeCurrentScenario(_) in change_current_scenario.read() {
return;
}
let Some(scenario) = current_scenario.0 else {
return;
};
for (entity, new_value, last_set_value) in changed_values.iter() {
if new_value.is_changed() {
if let Some(last_set_value) = last_set_value {
if last_set_value.is_changed() {
if **last_set_value == *new_value {
continue;
}
}
}
commands.trigger(UpdateModifier::modify_without_trigger(
scenario,
entity,
new_value.clone(),
));
}
}
}