mod actions;
mod animation;
mod audio;
mod classes;
mod click;
mod components;
mod cpu_physics;
mod determinism;
mod effect_presets;
mod events;
pub mod fluid;
pub mod fluid_surface;
mod game_feel;
pub(crate) mod hierarchy;
mod hybrid_physics;
mod ik;
mod input;
mod particles;
mod physics_benchmark;
pub mod picking;
mod player;
mod ragdoll;
mod render_benchmark;
mod render_world;
mod replay;
mod retarget;
mod scene_file;
mod scene_instance;
mod scene_tree;
mod signals;
pub mod sim_math;
mod skinning;
mod snapshot;
mod state_hash;
#[cfg(test)]
mod tests;
mod time;
mod two_d;
#[cfg(feature = "ui")]
mod ui;
pub mod water;
pub use actions::{
bind_input_actions, parse_input, ActionMap, InputAction, InputBinding,
};
pub use animation::*;
pub use audio::{
route_sound_events, AudioCommand, AudioQueue, BeatClock, Sound, SoundId,
QUEUE_LIMIT,
};
pub use classes::ClassIndex;
pub use components::*;
pub(crate) use cpu_physics::{gpu_shape_words, next_spawn_order};
pub use cpu_physics::{
Articulation, AxisMotion, CharacterMove, CollisionEvent, Contact,
GpuCollider, Joint, JointAxis, JointBroken, JointKind, JointMotor,
JointSpring, NextSpawnOrder, PhysicsWorld, RayHit, Sleeping, SpawnOrder,
SLEEP_STEPS,
};
pub use determinism::*;
pub use effect_presets::*;
pub use events::EventQueue;
pub use fluid::{
capture_fluids, restore_fluids, Fluid, FluidBlock, FluidParticle,
FluidSettings, FluidSurface, FluidVolume,
};
pub use game_feel::*;
pub use hierarchy::{propagate_transforms, HierarchyDiagnostics};
pub use hybrid_physics::*;
pub use ik::*;
#[cfg(feature = "window")]
pub use input::Gamepads;
pub use input::{KeyCode, MouseButton, PadButton, RuntimeInput, Stick};
pub use particles::*;
pub use physics_benchmark::{
BenchmarkBody, PhysicsBenchmark, BENCHMARK_TOWER_HEIGHT,
};
pub use player::*;
pub use ragdoll::*;
pub use render_benchmark::*;
pub use render_world::*;
pub use replay::*;
pub use retarget::*;
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 scene_instance::{
edit_instance, member_id, spawn_prefab, InstanceEdit, InstanceExpanded,
InstanceMember, Prefab, SceneInstance, INSTANCE_MEMBER_KEY,
SCENE_INSTANCE_COMPONENT,
};
pub use scene_tree::SceneTree;
pub use signals::{
Added, Connection, Connections, Removed, Signal, SignalError, SignalEvent,
};
pub use skinning::{
joint_matrices, morph_mesh, skin_mesh, update_skins, Deformers, Morph,
Skin, SkinnedMesh,
};
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;
pub use water::{WaterBody, WaterMesh};
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)]
pub(crate) struct ExitState {
pub(crate) 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,
overloaded: bool,
overload_reported: bool,
}
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>();
classes::install(&mut world);
world.init_resource::<crate::assets::DataAssetTypes>();
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(),
overloaded: false,
overload_reported: false,
};
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_event::<JointBroken>();
app.add_event::<AnimationEvent>();
app.add_systems(
ScheduleStage::FixedUpdate,
(
cpu_physics::step_cpu_physics,
player::player_move,
player::player_face,
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,
(
particles::restart_on_contact,
particles::add_particle_systems,
particles::update_particles,
)
.chain(),
game_feel::advance_tweens,
animation::advance_animations,
fluid::spawn_fluid_volumes,
fluid::couple_fluids,
fluid::step_fluids,
fluid::sync_fluid_visuals,
fluid::reap_surfaces,
fluid::sync_fluid_surfaces,
water::float_in_water,
water::sync_water,
)
.chain(),
);
app.add_system(ScheduleStage::Update, player::player_look);
app.add_system(ScheduleStage::Update, actions::bind_input_actions);
app.add_event::<SoundEvent>();
app.insert_resource(AudioQueue::default());
app.add_event::<HudButtonPressed>();
#[cfg(feature = "ui")]
app.add_system(ScheduleStage::PostUpdate, 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, game_feel::load_environment_maps);
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 register_data_asset<T: crate::assets::DataAsset>(
&mut self,
) -> &mut Self {
self.world
.resource_mut::<crate::assets::DataAssetTypes>()
.register::<T>();
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 add_signal_handler<E: SignalEvent, M>(
&mut self,
name: impl Into<String>,
handler: impl bevy_ecs::system::IntoSystem<
bevy_ecs::system::In<Signal<E>>,
(),
M,
>,
) -> Result<&mut Self, SignalError> {
signals::SignalHandlers::register(
&mut self.world,
name.into(),
handler,
)?;
Ok(self)
}
pub fn connect(
&mut self,
source: Entity,
handler: &str,
target: Entity,
) -> Result<&mut Self, SignalError> {
if !signals::SignalHandlers::contains(&self.world, handler) {
return Err(SignalError::UnknownHandler(handler.to_owned()));
}
if self.world.get_entity(target).is_err() {
return Err(SignalError::MissingEntity(target));
}
let mut source = self
.world
.get_entity_mut(source)
.map_err(|_| SignalError::MissingEntity(source))?;
let connection = Connection {
handler: handler.to_owned(),
target,
};
if let Some(mut connections) = source.get_mut::<Connections>() {
connections.list.push(connection);
} else {
source.insert(Connections {
list: vec![connection],
});
}
Ok(self)
}
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 class_members(
&self,
class: &str,
) -> impl Iterator<Item = Entity> + '_ {
self.world.resource::<ClassIndex>().members(class)
}
pub fn restore(
&mut self,
snapshot: &WorldSnapshot,
) -> Result<(), SnapshotError> {
let mute = |world: &mut World, muted| {
if let Some(mut handlers) =
world.get_resource_mut::<signals::SignalHandlers>()
{
handlers.muted = muted;
}
};
mute(&mut self.world, true);
let restored = snapshot::restore_world(&mut self.world, snapshot);
mute(&mut self.world, false);
restored?;
classes::rebuild(&mut self.world);
self.startup_complete = snapshot.startup_complete;
Ok(())
}
#[cfg_attr(not(feature = "window"), allow(dead_code))]
pub(crate) fn skip_startup(&mut self) {
self.startup_complete = true;
}
pub fn update(
&mut self,
real_delta: Duration,
) -> Result<FrameReport, AppError> {
let control = self.world.resource::<TimeControl>();
let real_delta = match control.time_scale {
scale if self.overloaded && scale > 0.0 && scale.is_finite() => {
Duration::try_from_secs_f64(
control.fixed_delta.as_secs_f64() / scale,
)
.map_or(real_delta, |step| real_delta.min(step))
}
_ => real_delta,
};
self.update_exact(real_delta)
}
pub(crate) fn update_exact(
&mut self,
real_delta: Duration,
) -> Result<FrameReport, AppError> {
let fixed_delta = self.world.resource::<TimeControl>().fixed_delta;
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();
let cap = self.world.resource::<TimeControl>().max_fixed_steps;
let slow = physics > fixed_delta.saturating_mul(fixed_steps);
if fixed_steps > 0 {
self.overloaded =
slow && (self.overloaded || fixed_steps >= cap.max(2));
}
if self.overloaded && !self.overload_reported {
self.overload_reported = true;
eprintln!(
"warning: {fixed_steps} fixed steps took {physics:.0?} but simulate {:.0?}; the game now runs in slow motion. A debug build of the engine is the usual cause: add `[profile.dev.package.\"*\"] opt-level = 3` to the game's Cargo.toml, or use fewer bodies.",
fixed_delta.saturating_mul(fixed_steps)
);
}
#[cfg(feature = "ui")]
if self.world.contains_resource::<RuntimeUi>() {
self.world.resource_scope(
|world, mut ui: bevy_ecs::world::Mut<RuntimeUi>| {
ui.begin_pass(world.resource::<RuntimeInput>());
},
);
}
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)
}
}
#[derive(bevy_ecs::prelude::Resource)]
struct EditModeSystems([bevy_ecs::system::SystemId; 3]);
pub fn run_edit_mode_systems(world: &mut World) {
let systems = match world.get_resource::<EditModeSystems>() {
Some(systems) => systems.0,
None => {
let systems = [
world.register_system(two_d::build_tile_maps),
world.register_system(game_feel::apply_scene_background),
world.register_system(game_feel::load_environment_maps),
];
world.insert_resource(EditModeSystems(systems));
systems
}
};
for system in systems {
if let Err(error) = world.run_system(system) {
eprintln!("edit mode system: {error}");
}
}
}