mod actions;
mod click;
mod components;
mod cpu_physics;
mod determinism;
mod events;
mod game_feel;
pub(crate) mod hierarchy;
mod hybrid_physics;
mod input;
mod physics_benchmark;
pub mod picking;
mod player;
mod render_benchmark;
mod render_world;
mod replay;
mod scene_file;
pub mod sim_math;
mod snapshot;
mod state_hash;
#[cfg(test)]
mod tests;
mod time;
mod two_d;
#[cfg(feature = "ui")]
mod ui;
pub use actions::{ActionMap, InputBinding};
pub use components::*;
pub(crate) use cpu_physics::gpu_shape_words;
pub use cpu_physics::{
CharacterMove, CollisionEvent, Contact, GpuCollider, PhysicsWorld, RayHit,
Sleeping, SLEEP_STEPS,
};
pub use determinism::*;
pub use events::EventQueue;
pub use game_feel::*;
pub use hierarchy::{propagate_transforms, HierarchyDiagnostics};
pub use hybrid_physics::*;
pub use input::{KeyCode, MouseButton, RuntimeInput};
pub use physics_benchmark::{
BenchmarkBody, PhysicsBenchmark, BENCHMARK_TOWER_HEIGHT,
};
pub use player::*;
pub use render_benchmark::*;
pub use render_world::*;
pub use replay::*;
pub use rusting_core::app::AppError;
pub use rusting_core::input::ClickEvent;
pub use rusting_core::schedule::{CpuFrameTimings, FrameReport, ScheduleStage};
pub use scene_file::*;
pub use snapshot::{SnapshotError, WorldSnapshot};
pub use state_hash::*;
pub use time::{FrameTime, RandomSeed, TimeControl};
pub use two_d::*;
#[cfg(feature = "ui")]
pub use ui::RuntimeUi;
use std::hash::Hasher;
use std::time::{Duration, Instant};
use bevy_ecs::bundle::Bundle;
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::{IntoScheduleConfigs, Resource, Schedule, World};
use bevy_ecs::system::ScheduleSystem;
pub(super) struct FastHasher(u64);
impl Default for FastHasher {
fn default() -> Self {
Self(0xcbf2_9ce4_8422_2325)
}
}
impl Hasher for FastHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 ^= u64::from(*byte);
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
}
}
fn write_u8(&mut self, value: u8) {
self.write_u64(u64::from(value));
}
fn write_u32(&mut self, value: u32) {
self.write_u64(u64::from(value));
}
fn write_u64(&mut self, value: u64) {
self.0 ^= value;
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
}
}
pub trait Plugin: Send + Sync + 'static {
fn build(&self, app: &mut App) -> Result<(), AppError>;
fn name(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
#[derive(Resource, Clone, Default)]
struct ExitState {
requested: bool,
}
pub struct App {
world: World,
startup: Schedule,
fixed_update: Schedule,
update: Schedule,
post_update: Schedule,
render_extract: Schedule,
startup_complete: bool,
event_maintenance: Vec<fn(&mut World)>,
plugins: Vec<&'static str>,
replay_recorder: Option<ReplayRecorder>,
snapshot_types: snapshot::SnapshotTypes,
}
impl Default for App {
fn default() -> Self {
let mut world = World::new();
world.insert_resource(FrameTime::default());
world.insert_resource(TimeControl::default());
world.insert_resource(RandomSeed::default());
world.insert_resource(ExitState::default());
world.insert_resource(HierarchyDiagnostics::default());
world.insert_resource(RenderSettings::default());
world.insert_resource(PhysicsSettings::default());
world.insert_resource(PhysicsBackendStatus {
gameplay_available: true,
..PhysicsBackendStatus::default()
});
world.init_resource::<PhysicsWorld>();
world.insert_resource(SceneComponentRegistry::default());
world.init_resource::<CpuFrameTimings>();
world.init_resource::<bevy_ecs::schedule::Schedules>();
input::install(&mut world);
actions::install(&mut world);
player::bind_default_actions(&mut world.resource_mut::<ActionMap>());
#[cfg(feature = "ui")]
world.init_resource::<RuntimeUi>();
let mut fixed_update = Schedule::default();
fixed_update
.set_executor(bevy_ecs::schedule::SingleThreadedExecutor::new());
let mut post_update = Schedule::default();
post_update.add_systems(propagate_transforms);
let mut app = Self {
world,
startup: Schedule::default(),
fixed_update,
update: Schedule::default(),
post_update,
render_extract: Schedule::default(),
startup_complete: false,
event_maintenance: Vec::new(),
plugins: Vec::new(),
replay_recorder: None,
snapshot_types: snapshot::SnapshotTypes::default(),
};
snapshot::register_engine_types(&mut app.snapshot_types);
app.add_event::<ClickEvent>();
app.add_system(ScheduleStage::Update, click::route_click_events);
app.add_event::<CollisionEvent>();
app.add_systems(
ScheduleStage::FixedUpdate,
(
cpu_physics::step_cpu_physics,
player::player_move,
two_d::platformer_move,
game_feel::trigger_on_contact,
game_feel::collect_pickups,
game_feel::fire_sound_cues,
game_feel::update_burst_particles,
game_feel::fire_bursts,
game_feel::advance_tweens,
)
.chain(),
);
app.add_system(ScheduleStage::Update, player::player_look);
app.add_event::<SoundEvent>();
app.add_event::<HudButtonPressed>();
#[cfg(feature = "ui")]
app.add_system(ScheduleStage::Update, game_feel::draw_hud);
app.add_system(ScheduleStage::Update, two_d::build_tile_maps);
app.add_system(
ScheduleStage::Update,
game_feel::apply_scene_background,
);
app.add_system(ScheduleStage::Update, two_d::platformer_jump);
app
}
}
impl App {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn world(&self) -> &World {
&self.world
}
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
self.world.insert_resource(resource);
self
}
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> Entity {
let mut entity = self.world.spawn(bundle);
if !entity.contains::<SceneId>() {
entity.insert(SceneId::new());
}
entity.id()
}
pub fn register_scene_component<T>(
&mut self,
name: impl Into<String>,
) -> Result<&mut Self, SceneIoError>
where
T: bevy_ecs::component::Component
+ serde::Serialize
+ serde::de::DeserializeOwned
+ Default
+ crate::reflect::Reflect,
{
self.world
.resource_mut::<SceneComponentRegistry>()
.register::<T>(name)?;
Ok(self)
}
pub fn migrate_scene_component(
&mut self,
name: &str,
migration: crate::reflect::FieldMigration,
) -> Result<&mut Self, SceneIoError> {
self.world
.resource_mut::<SceneComponentRegistry>()
.add_migration(name, migration)?;
Ok(self)
}
pub fn despawn(&mut self, entity: Entity) -> Result<(), AppError> {
if self.world.despawn(entity) {
Ok(())
} else {
Err(AppError::MissingEntity(entity))
}
}
pub fn add_systems<M>(
&mut self,
stage: ScheduleStage,
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
) -> &mut Self {
self.schedule_mut(stage).add_systems(systems);
self
}
pub fn add_system<M>(
&mut self,
stage: ScheduleStage,
system: impl IntoScheduleConfigs<ScheduleSystem, M>,
) -> &mut Self {
self.add_systems(stage, system)
}
pub fn add_plugin<P: Plugin>(
&mut self,
plugin: P,
) -> Result<&mut Self, AppError> {
let name = plugin.name();
if self.plugins.contains(&name) {
return Err(AppError::DuplicatePlugin(name));
}
plugin.build(self)?;
self.plugins.push(name);
Ok(self)
}
pub fn add_event<T: Send + Sync + 'static>(&mut self) -> &mut Self {
if !self.world.contains_resource::<EventQueue<T>>() {
self.world.insert_resource(EventQueue::<T>::default());
self.event_maintenance.push(events::begin_frame::<T>);
}
self
}
pub fn send_event<T: Send + Sync + 'static>(&mut self, event: T) {
let mut events = self
.world
.get_resource_mut::<EventQueue<T>>()
.unwrap_or_else(|| {
panic!(
"event `{}` was not registered",
std::any::type_name::<T>()
)
});
events.send(event);
}
pub fn request_exit(&mut self) {
self.world.resource_mut::<ExitState>().requested = true;
}
#[must_use]
pub fn exit_requested(&self) -> bool {
self.world.resource::<ExitState>().requested
}
pub fn set_parent(
&mut self,
child: Entity,
parent: Entity,
) -> Result<(), AppError> {
hierarchy::set_parent(&mut self.world, child, parent)
}
pub fn clear_parent(&mut self, child: Entity) -> Result<(), AppError> {
hierarchy::clear_parent(&mut self.world, child)
}
pub fn start_recording(&mut self) {
self.replay_recorder = Some(ReplayRecorder::new(&self.world));
}
pub fn finish_recording(&mut self) -> Option<Replay> {
self.replay_recorder.take().map(ReplayRecorder::finish)
}
pub fn register_snapshot_component<T>(&mut self) -> &mut Self
where
T: bevy_ecs::component::Component<
Mutability = bevy_ecs::component::Mutable,
> + Clone,
{
self.snapshot_types.register::<T>();
self
}
pub fn ignore_in_snapshots<T: 'static>(&mut self) -> &mut Self {
self.snapshot_types.ignore::<T>();
self
}
pub fn snapshot(&mut self) -> Result<WorldSnapshot, SnapshotError> {
let entities =
snapshot::capture_world(&mut self.world, &self.snapshot_types)?;
let allocator = snapshot::read_allocator(&mut self.world);
snapshot::write_allocator(&mut self.world, &allocator);
Ok(WorldSnapshot {
tick: self.world.resource::<FrameTime>().fixed_tick,
startup_complete: self.startup_complete,
entities,
allocator,
})
}
pub fn restore(
&mut self,
snapshot: &WorldSnapshot,
) -> Result<(), SnapshotError> {
snapshot::restore_world(&mut self.world, snapshot)?;
self.startup_complete = snapshot.startup_complete;
Ok(())
}
pub fn update(
&mut self,
real_delta: Duration,
) -> Result<FrameReport, AppError> {
if !self.startup_complete {
self.startup.run(&mut self.world);
self.startup_complete = true;
}
for maintain in &self.event_maintenance {
maintain(&mut self.world);
}
if let Some(recorder) = &mut self.replay_recorder {
recorder.frame_start(&self.world, real_delta);
}
let fixed_steps = time::advance(&mut self.world, real_delta)?;
let start = Instant::now();
let end_tick = self.world.resource::<FrameTime>().fixed_tick;
let first_tick = end_tick.saturating_sub(u64::from(fixed_steps));
for step in 0..u64::from(fixed_steps) {
self.world.resource_mut::<FrameTime>().fixed_tick =
first_tick + step;
self.fixed_update.run(&mut self.world);
hybrid_physics::stamp_gpu_commands(&mut self.world);
state_hash::record_state_hash(&mut self.world);
}
self.world.resource_mut::<FrameTime>().fixed_tick = end_tick;
if let Some(recorder) = &mut self.replay_recorder {
recorder.frame_hashes(&self.world, fixed_steps);
}
let physics = start.elapsed();
#[cfg(feature = "ui")]
if let Some(mut ui) = self.world.get_resource_mut::<RuntimeUi>() {
ui.begin_pass();
}
self.update.run(&mut self.world);
self.post_update.run(&mut self.world);
#[cfg(feature = "ui")]
if let Some(mut ui) = self.world.get_resource_mut::<RuntimeUi>() {
ui.end_pass();
}
let start = Instant::now();
self.render_extract.run(&mut self.world);
let extraction = start.elapsed();
if let Some(mut timings) =
self.world.get_resource_mut::<CpuFrameTimings>()
{
timings.physics = physics;
timings.extraction = extraction;
}
Ok(FrameReport {
fixed_steps,
exit_requested: self.exit_requested(),
})
}
pub fn run(mut self) -> Result<(), AppError> {
let mut previous = Instant::now();
while !self.exit_requested() {
let now = Instant::now();
self.update(now.saturating_duration_since(previous))?;
previous = now;
std::thread::yield_now();
}
Ok(())
}
fn schedule_mut(&mut self, stage: ScheduleStage) -> &mut Schedule {
match stage {
ScheduleStage::Startup => &mut self.startup,
ScheduleStage::FixedUpdate => &mut self.fixed_update,
ScheduleStage::Update => &mut self.update,
ScheduleStage::PostUpdate => &mut self.post_update,
ScheduleStage::RenderExtract => &mut self.render_extract,
}
}
}
pub struct EngineBuilder {
fixed_delta: Duration,
max_fixed_steps: u32,
plugins: Vec<Box<dyn Plugin>>,
}
impl Default for EngineBuilder {
fn default() -> Self {
Self {
fixed_delta: Duration::from_secs_f64(1.0 / 60.0),
max_fixed_steps: 8,
plugins: Vec::new(),
}
}
}
impl EngineBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn fixed_delta(mut self, fixed_delta: Duration) -> Self {
self.fixed_delta = fixed_delta;
self
}
#[must_use]
pub fn max_fixed_steps(mut self, max_fixed_steps: u32) -> Self {
self.max_fixed_steps = max_fixed_steps.max(1);
self
}
#[must_use]
pub fn plugin<P: Plugin>(mut self, plugin: P) -> Self {
self.plugins.push(Box::new(plugin));
self
}
pub fn build(self) -> Result<App, AppError> {
if self.fixed_delta.is_zero() {
return Err(AppError::InvalidFixedDelta);
}
let mut app = App::new();
{
let mut control = app.world.resource_mut::<TimeControl>();
control.fixed_delta = self.fixed_delta;
control.max_fixed_steps = self.max_fixed_steps;
}
for plugin in self.plugins {
let name = plugin.name();
if app.plugins.contains(&name) {
return Err(AppError::DuplicatePlugin(name));
}
plugin.build(&mut app)?;
app.plugins.push(name);
}
Ok(app)
}
}