use bevy::prelude::*;
use issun_core::mechanics::evolution::EvolutionInput;
use issun_core::mechanics::{ExecutionHint, Mechanic};
use std::marker::PhantomData;
use super::systems::{auto_evolution_system, evolution_system, log_evolution_events};
#[cfg(test)]
use super::types::EnvironmentComponent;
use super::types::{
EvolutionConfigResource, EvolutionEventWrapper, EvolutionStateComponent, EvolutionTick,
SubjectType,
};
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub struct EvolutionSequentialSet;
pub struct EvolutionPlugin<M>
where
M: Mechanic<
Config = issun_core::mechanics::evolution::EvolutionConfig,
State = issun_core::mechanics::evolution::EvolutionState,
Input = EvolutionInput,
Event = issun_core::mechanics::evolution::EvolutionEvent,
>,
{
pub config: issun_core::mechanics::evolution::EvolutionConfig,
pub auto_tick: bool,
pub log_events: bool,
_phantom: PhantomData<M>,
}
impl<M> Default for EvolutionPlugin<M>
where
M: Mechanic<
Config = issun_core::mechanics::evolution::EvolutionConfig,
State = issun_core::mechanics::evolution::EvolutionState,
Input = EvolutionInput,
Event = issun_core::mechanics::evolution::EvolutionEvent,
>,
{
fn default() -> Self {
Self {
config: issun_core::mechanics::evolution::EvolutionConfig::default(),
auto_tick: true, log_events: true, _phantom: PhantomData,
}
}
}
impl<M> EvolutionPlugin<M>
where
M: Mechanic<
Config = issun_core::mechanics::evolution::EvolutionConfig,
State = issun_core::mechanics::evolution::EvolutionState,
Input = EvolutionInput,
Event = issun_core::mechanics::evolution::EvolutionEvent,
>,
{
pub fn new(config: issun_core::mechanics::evolution::EvolutionConfig) -> Self {
Self {
config,
auto_tick: true,
log_events: true,
_phantom: PhantomData,
}
}
pub fn with_auto_tick(mut self, auto_tick: bool) -> Self {
self.auto_tick = auto_tick;
self
}
pub fn with_log_events(mut self, log_events: bool) -> Self {
self.log_events = log_events;
self
}
}
impl<M> Plugin for EvolutionPlugin<M>
where
M: Mechanic<
Config = issun_core::mechanics::evolution::EvolutionConfig,
State = issun_core::mechanics::evolution::EvolutionState,
Input = EvolutionInput,
Event = issun_core::mechanics::evolution::EvolutionEvent,
> + Send
+ Sync
+ 'static,
{
fn build(&self, app: &mut App) {
app.insert_resource(EvolutionConfigResource::new(self.config.clone()));
app.register_type::<EvolutionConfigResource>();
app.register_type::<EvolutionStateComponent>();
app.register_type::<SubjectType>();
app.add_message::<EvolutionTick>();
app.add_message::<EvolutionEventWrapper>();
if M::Execution::PARALLEL_SAFE {
if self.auto_tick && self.log_events {
app.add_systems(
Update,
(
evolution_system::<M>,
auto_evolution_system::<M>,
log_evolution_events,
),
);
} else if self.auto_tick {
app.add_systems(Update, (evolution_system::<M>, auto_evolution_system::<M>));
} else if self.log_events {
app.add_systems(Update, (evolution_system::<M>, log_evolution_events));
} else {
app.add_systems(Update, evolution_system::<M>);
}
info!(
"EvolutionPlugin initialized with mechanic: {} (parallel-safe, auto_tick: {}, log_events: {})",
std::any::type_name::<M>(),
self.auto_tick,
self.log_events
);
} else {
app.add_systems(Update, evolution_system::<M>.in_set(EvolutionSequentialSet));
if self.auto_tick {
app.add_systems(
Update,
auto_evolution_system::<M>.in_set(EvolutionSequentialSet),
);
}
if self.log_events {
app.add_systems(Update, log_evolution_events);
}
info!(
"EvolutionPlugin initialized with mechanic: {} (sequential, auto_tick: {}, log_events: {})",
std::any::type_name::<M>(),
self.auto_tick,
self.log_events
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use issun_core::mechanics::evolution::prelude::{FoodDecay, SimpleDecay, SimpleGrowth};
type TestEvolution = SimpleDecay;
#[test]
fn test_plugin_builds() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(EvolutionPlugin::<TestEvolution>::default());
assert!(app.world().contains_resource::<EvolutionConfigResource>());
}
#[test]
fn test_plugin_with_custom_config() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
let config = issun_core::mechanics::evolution::EvolutionConfig {
base_rate: 5.0,
time_delta: 0.5,
};
app.add_plugins(EvolutionPlugin::<TestEvolution>::new(config.clone()));
let resource = app.world().resource::<EvolutionConfigResource>();
assert_eq!(resource.config.base_rate, 5.0);
assert_eq!(resource.config.time_delta, 0.5);
}
#[test]
fn test_full_evolution_flow_manual() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(
EvolutionPlugin::<TestEvolution>::default()
.with_auto_tick(false) .with_log_events(false),
);
let entity = app
.world_mut()
.spawn(EvolutionStateComponent::new(
100.0,
0.0,
100.0,
SubjectType::Food,
))
.id();
app.world_mut().write_message(EvolutionTick::new(entity));
app.update();
let state = app.world().get::<EvolutionStateComponent>(entity).unwrap();
assert_eq!(state.value, 99.0); }
#[test]
fn test_full_evolution_flow_auto() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(
EvolutionPlugin::<TestEvolution>::new(
issun_core::mechanics::evolution::EvolutionConfig {
base_rate: 5.0,
time_delta: 1.0,
},
)
.with_auto_tick(true)
.with_log_events(false),
);
let entity = app
.world_mut()
.spawn(EvolutionStateComponent::new(
100.0,
0.0,
100.0,
SubjectType::Resource,
))
.id();
app.update();
app.update();
let state = app.world().get::<EvolutionStateComponent>(entity).unwrap();
assert!(state.value < 100.0);
}
#[test]
fn test_growth_mechanic() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(
EvolutionPlugin::<SimpleGrowth>::new(
issun_core::mechanics::evolution::EvolutionConfig {
base_rate: 10.0,
time_delta: 1.0,
},
)
.with_auto_tick(false)
.with_log_events(false),
);
let entity = app
.world_mut()
.spawn(EvolutionStateComponent::new(
10.0,
0.0,
100.0,
SubjectType::Plant,
))
.id();
app.world_mut().write_message(EvolutionTick::new(entity));
app.update();
let state = app.world().get::<EvolutionStateComponent>(entity).unwrap();
assert_eq!(state.value, 20.0); }
#[test]
fn test_food_decay_preset() {
let mut app = App::new();
app.add_plugins(bevy::MinimalPlugins);
app.add_plugins(
EvolutionPlugin::<FoodDecay>::new(issun_core::mechanics::evolution::EvolutionConfig {
base_rate: 5.0,
time_delta: 1.0,
})
.with_auto_tick(false)
.with_log_events(false),
);
let entity = app
.world_mut()
.spawn((
EvolutionStateComponent::new(100.0, 0.0, 100.0, SubjectType::Food),
EnvironmentComponent::new(25.0, 0.9), ))
.id();
app.world_mut().write_message(EvolutionTick::new(entity));
app.update();
let state = app.world().get::<EvolutionStateComponent>(entity).unwrap();
assert!(state.value < 100.0);
}
}