use std::marker::PhantomData;
use bevy::prelude::{
apply_deferred, App, FixedUpdate, IntoSystemConfigs, IntoSystemSetConfigs, Plugin, PreUpdate,
Res, SystemSet,
};
use crate::client::components::SyncComponent;
use crate::client::prediction::despawn::{
remove_component_for_despawn_predicted, remove_despawn_marker,
};
use crate::client::prediction::predicted_history::update_prediction_history;
use crate::prelude::Named;
use crate::protocol::component::ComponentProtocol;
use crate::protocol::Protocol;
use crate::shared::sets::{FixedUpdateSet, MainSet};
use super::predicted_history::{add_component_history, apply_confirmed_update};
use super::rollback::{client_rollback_check, increment_rollback_tick, run_rollback};
use super::{spawn_predicted_entity, ComponentSyncMode, Rollback, RollbackState};
#[derive(Debug, Clone, Copy, Default)]
pub struct PredictionConfig {
disable: bool,
always_rollback: bool,
}
impl PredictionConfig {
pub fn disable(mut self, disable: bool) -> Self {
self.disable = disable;
self
}
pub fn always_rollback(mut self, always_rollback: bool) -> Self {
self.always_rollback = always_rollback;
self
}
}
pub struct PredictionPlugin<P: Protocol> {
config: PredictionConfig,
_marker: PhantomData<P>,
}
impl<P: Protocol> PredictionPlugin<P> {
pub(crate) fn new(config: PredictionConfig) -> Self {
Self {
config,
_marker: PhantomData,
}
}
}
impl<P: Protocol> Default for PredictionPlugin<P> {
fn default() -> Self {
Self {
config: PredictionConfig::default(),
_marker: PhantomData,
}
}
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum PredictionSet {
SpawnPrediction,
SpawnPredictionFlush,
SpawnHistory,
SpawnHistoryFlush,
CheckRollback,
CheckRollbackFlush,
Rollback,
IncrementRollbackTick,
EntityDespawn,
EntityDespawnFlush,
UpdateHistory,
}
pub fn is_eligible_for_rollback<C: SyncComponent>() -> bool {
matches!(C::mode(), ComponentSyncMode::Full)
}
pub fn is_in_rollback(rollback: Res<Rollback>) -> bool {
matches!(rollback.state, RollbackState::ShouldRollback { .. })
}
pub fn add_prediction_systems<C: SyncComponent + Named, P: Protocol>(app: &mut App) {
app.add_systems(
PreUpdate,
(
(add_component_history::<C, P>).in_set(PredictionSet::SpawnHistory),
(apply_confirmed_update::<C, P>).in_set(PredictionSet::CheckRollback),
(client_rollback_check::<C, P>.run_if(is_eligible_for_rollback::<C>))
.in_set(PredictionSet::CheckRollback),
),
);
app.add_systems(
FixedUpdate,
update_prediction_history::<C, P>
.run_if(is_eligible_for_rollback::<C>)
.in_set(PredictionSet::UpdateHistory),
);
app.add_systems(
FixedUpdate,
remove_component_for_despawn_predicted::<C>.in_set(PredictionSet::EntityDespawn),
);
}
impl<P: Protocol> Plugin for PredictionPlugin<P> {
fn build(&self, app: &mut App) {
if self.config.disable {
return;
}
P::Components::add_prediction_systems(app);
app.insert_resource(Rollback {
state: RollbackState::Default,
});
app.configure_sets(
PreUpdate,
(
MainSet::ReceiveFlush,
PredictionSet::SpawnPrediction,
PredictionSet::SpawnPredictionFlush,
PredictionSet::SpawnHistory,
PredictionSet::SpawnHistoryFlush,
PredictionSet::CheckRollback,
PredictionSet::CheckRollbackFlush,
PredictionSet::Rollback.run_if(is_in_rollback),
)
.chain(),
);
app.add_systems(
PreUpdate,
(
apply_deferred.in_set(PredictionSet::SpawnPredictionFlush),
apply_deferred.in_set(PredictionSet::SpawnHistoryFlush),
apply_deferred.in_set(PredictionSet::CheckRollbackFlush),
),
);
app.add_systems(
FixedUpdate,
(apply_deferred.in_set(PredictionSet::EntityDespawnFlush),),
);
app.add_systems(
PreUpdate,
spawn_predicted_entity.in_set(PredictionSet::SpawnPrediction),
);
app.add_systems(
PreUpdate,
(run_rollback::<P>).in_set(PredictionSet::Rollback),
);
app.configure_sets(
FixedUpdate,
(
FixedUpdateSet::Main,
FixedUpdateSet::MainFlush,
PredictionSet::EntityDespawn,
PredictionSet::EntityDespawnFlush,
PredictionSet::UpdateHistory,
PredictionSet::IncrementRollbackTick.run_if(is_in_rollback),
)
.chain(),
);
app.add_systems(
FixedUpdate,
increment_rollback_tick.in_set(PredictionSet::IncrementRollbackTick),
);
app.add_systems(
FixedUpdate,
remove_despawn_marker.in_set(PredictionSet::EntityDespawn),
);
}
}