Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valence tick #377

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bevy_app = { version = "0.10.1", default-features = false }
bevy_ecs = { version = "0.10.1", default-features = false, features = [
"trace",
] }
bevy_reflect = "0.10.1"
bevy_hierarchy = { version = "0.10.1", default-features = false }
bevy_mod_debugdump = "0.7.0"
bitfield-struct = "0.3.1"
Expand Down Expand Up @@ -96,6 +97,7 @@ valence_network.path = "crates/valence_network"
valence_player_list.path = "crates/valence_player_list"
valence_registry.path = "crates/valence_registry"
valence_world_border.path = "crates/valence_world_border"
valence_tick.path = "crates/valence_tick"
valence.path = "crates/valence"

zip = "0.6.3"
Expand Down
13 changes: 11 additions & 2 deletions crates/valence/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ keywords = ["minecraft", "gamedev", "server", "ecs"]
categories = ["game-engines"]

[features]
default = ["network", "player_list", "inventory", "anvil", "advancement", "world_border"]
default = [
"network",
"player_list",
"inventory",
"anvil",
"advancement",
"world_border",
"tick",
]
network = ["dep:valence_network"]
player_list = ["dep:valence_player_list"]
inventory = ["dep:valence_inventory"]
anvil = ["dep:valence_anvil"]
advancement = ["dep:valence_advancement"]
world_border = ["dep:valence_world_border"]
tick = ["dep:valence_tick"]

[dependencies]
bevy_app.workspace = true
Expand All @@ -39,7 +48,7 @@ valence_inventory = { workspace = true, optional = true }
valence_anvil = { workspace = true, optional = true }
valence_advancement = { workspace = true, optional = true }
valence_world_border = { workspace = true, optional = true }

valence_tick = { workspace = true, optional = true }

[dev-dependencies]
anyhow.workspace = true
Expand Down
74 changes: 74 additions & 0 deletions crates/valence/examples/fixed_tick.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#![allow(clippy::type_complexity)]

use valence::prelude::*;
use valence_client::message::SendMessage;

const SPAWN_Y: i32 = 64;

pub fn main() {
tracing_subscriber::fmt().init();

App::new()
.add_plugins(DefaultPlugins)
.add_plugin(TickSystem)
.add_startup_system(setup)
.add_system(init_clients)
.add_system(despawn_disconnected_clients)
.add_system(run_every_20_ticks.in_schedule(CoreSchedule::FixedUpdate))
.add_system(manual_tick_interval)
.insert_resource(FixedTick::new(20))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean this only allows for one timer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally it works the same as bevy_timer, so yeah, i think so, which is why I added the other example, or want to run it on a seperate scheduler, or maybe even make it some form of dynamic scheduler if thats possible?

.run();
}

fn setup(
mut commands: Commands,
server: Res<Server>,
dimensions: Res<DimensionTypeRegistry>,
biomes: Res<BiomeRegistry>,
) {
let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server);

for z in -5..5 {
for x in -5..5 {
instance.insert_chunk([x, z], Chunk::default());
}
}

for z in -25..25 {
for x in -25..25 {
instance.set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK);
}
}

commands.spawn(instance);
}

fn init_clients(
mut clients: Query<(&mut Position, &mut Location, &mut GameMode), Added<Client>>,
instances: Query<Entity, With<Instance>>,
) {
for (mut pos, mut loc, mut game_mode) in &mut clients {
pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into();
loc.0 = instances.single();
*game_mode = GameMode::Creative;
}
}

fn run_every_20_ticks(mut clients: Query<&mut Client>, tick: Res<Tick>) {
for mut client in &mut clients {
client.send_chat_message(format!("20 ticks have passed, tick: {}", tick.elapsed()));
}
}

fn manual_tick_interval(
mut clients: Query<&mut Client>,
mut last_time: Local<usize>,
tick: Res<Tick>,
) {
if tick.elapsed() - *last_time >= 40 {
*last_time = tick.elapsed();
for mut client in &mut clients {
client.send_chat_message(format!("40 ticks have passed, tick: {}", tick.elapsed()));
}
}
}
5 changes: 5 additions & 0 deletions crates/valence/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub use valence_inventory as inventory;
pub use valence_network as network;
#[cfg(feature = "player_list")]
pub use valence_player_list as player_list;
#[cfg(feature = "tick")]
pub use valence_tick as tick;
#[cfg(feature = "world_border")]
pub use valence_world_border as world_border;
pub use {
Expand Down Expand Up @@ -112,6 +114,9 @@ pub mod prelude {
pub use valence_core::{translation_key, CoreSettings, Server};
pub use valence_entity::hitbox::{Hitbox, HitboxShape};

#[cfg(feature = "tick")]
pub use valence_tick::{fixed_tickstep::FixedTick, Tick, TickSystem};

pub use super::DefaultPlugins;
use super::*;
}
Expand Down
14 changes: 14 additions & 0 deletions crates/valence_tick/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "valence_tick"
version.workspace = true
edition.workspace = true

[dependencies]
anyhow.workspace = true
bevy_app.workspace = true
bevy_ecs.workspace = true
bevy_reflect.workspace = true
tokio.workspace = true
valence_core.workspace = true
thiserror.workspace = true
flume.workspace = true
3 changes: 3 additions & 0 deletions crates/valence_tick/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# valence_tick

Everything related to Minecraft ticks.
121 changes: 121 additions & 0 deletions crates/valence_tick/src/fixed_tickstep.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use bevy_app::CoreSchedule;
use bevy_ecs::{system::Resource, world::World};
use thiserror::Error;

/// The amount of tick that must pass before the fixed tickstep schedule is run again.
#[derive(Resource, Debug)]
pub struct FixedTick {
accumulated: usize,
period: usize,
}

impl FixedTick {
/// Creates a new [`FixedTick`] struct
pub fn new(period: usize) -> Self {
FixedTick {
accumulated: 0,
period,
}
}

/// Adds 1 to the accumulated tick so far.
pub fn tick(&mut self) {
self.accumulated += 1;
}

/// Returns the current amount of accumulated tick
pub fn accumulated(&self) -> usize {
self.accumulated
}

/// Expends one `period` of accumulated tick.
///
/// [`Err(FixedUpdateError`)] will be returned
pub fn expend(&mut self) -> Result<(), FixedUpdateError> {
if let Some(new_value) = self.accumulated.checked_sub(self.period) {
self.accumulated = new_value;
Ok(())
} else {
Err(FixedUpdateError::NotEnoughTick {
accumulated: self.accumulated,
period: self.period,
})
}
}
}

impl Default for FixedTick {
fn default() -> Self {
FixedTick {
accumulated: 0,
period: 1,
}
}
}

/// An error returned when working with [`FixedTick`].
#[derive(Debug, Error)]
pub enum FixedUpdateError {
#[error("At least one period worth of ticks must be accumulated.")]
NotEnoughTick { accumulated: usize, period: usize },
}

/// Ticks the [`FixedTick`] resource then runs the [`CoreSchedule::FixedUpdate`].
pub fn run_fixed_update_schedule(world: &mut World) {
// Tick the time
let mut fixed_time = world.resource_mut::<FixedTick>();
fixed_time.tick();

// Run the schedule until we run out of accumulated time
let mut check_again = true;
while check_again {
let mut fixed_time = world.resource_mut::<FixedTick>();
let fixed_time_run = fixed_time.expend().is_ok();
if fixed_time_run {
world.run_schedule(CoreSchedule::FixedUpdate);
} else {
check_again = false;
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn fixed_time_starts_at_zero() {
let new_time = FixedTick::new(42);
assert_eq!(new_time.accumulated(), 0);

let default_time = FixedTick::default();
assert_eq!(default_time.accumulated(), 0);
}

#[test]
fn fixed_time_ticks_up() {
let mut fixed_time = FixedTick::default();
fixed_time.tick();
assert_eq!(fixed_time.accumulated(), 1);
}

#[test]
fn enough_accumulated_time_is_required() {
let mut fixed_time = FixedTick::new(2);
fixed_time.tick();
assert!(fixed_time.expend().is_err());
assert_eq!(fixed_time.accumulated(), 1);

fixed_time.tick();
assert!(fixed_time.expend().is_ok());
assert_eq!(fixed_time.accumulated(), 0);
}

#[test]
fn repeatedly_expending_time() {
let mut fixed_time = FixedTick::new(1);
fixed_time.tick();
assert!(fixed_time.expend().is_ok());
assert!(fixed_time.expend().is_err());
}
}
55 changes: 55 additions & 0 deletions crates/valence_tick/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![doc = include_str!("../README.md")]
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
rustdoc::invalid_html_tags
)]
#![warn(
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_import_braces,
unreachable_pub,
clippy::dbg_macro
)]

pub mod fixed_tickstep;
#[allow(clippy::module_inception)]
mod tick;

use fixed_tickstep::{run_fixed_update_schedule, FixedTick};
pub use tick::*;

use bevy_ecs::system::ResMut;

use bevy_app::prelude::*;
use bevy_ecs::prelude::*;

/// Adds tick functionality to Apps.
#[derive(Default)]
pub struct TickPlugin;

#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
/// Updates the elapsed ticks. Any system that interacts with [Tick] component should run after
/// this.
pub struct TickSystem;

impl Plugin for TickSystem {
fn build(&self, app: &mut App) {
app.init_resource::<Tick>()
.register_type::<Tick>()
.init_resource::<FixedTick>()
.configure_set(TickSystem.in_base_set(CoreSet::First))
.add_system(tick_system.in_set(TickSystem))
.add_system(run_fixed_update_schedule.in_base_set(CoreSet::FixedUpdate));
}
}

/// The system used to update the [`Tick`] used by app logic.
fn tick_system(mut tick: ResMut<Tick>) {
tick.update();
}
31 changes: 31 additions & 0 deletions crates/valence_tick/src/tick.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{FromReflect, Reflect};

/// A counter that tracks how many ticks has advanced
#[derive(Resource, Default, Reflect, FromReflect, Debug, Clone)]
#[reflect(Resource)]
pub struct Tick {
elapsed: usize,
}

impl Tick {
/// Constructs a new `Tick` instance
pub fn new() -> Self {
Self::default()
}

/// Updates the internal tick measurements.
pub fn update(&mut self) {
self.update_with_tick(1);
}

pub fn update_with_tick(&mut self, tick: usize) {
self.elapsed += tick;
}

/// Returns how many tick have advanced since [`startup`](#method.startup), as [`usize`].
#[inline]
pub fn elapsed(&self) -> usize {
self.elapsed
}
}
1 change: 1 addition & 0 deletions tools/playground/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ glam.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
valence_core.workspace = true
valence_tick.workspace = true
valence.workspace = true