Skip to content
Snippets Groups Projects
Unverified Commit 2c1d07bc authored by Kat Marchán's avatar Kat Marchán Committed by GitHub
Browse files

move big-brain to bevy (#1)

parent 47850ab2
No related branches found
No related tags found
No related merge requests found
# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
# NOTE: For maximum performance, build using a nightly compiler
# If you are using rust stable, remove the "-Zshare-generics=y" below (as well as "-Csplit-debuginfo=unpacked" when building on macOS).
[target.x86_64-unknown-linux-gnu]
linker = "/usr/bin/clang"
rustflags = ["-Clink-arg=-fuse-ld=lld"]
# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
# `brew install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
rustflags = []
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
#[profile.dev]
#debug = 1
......@@ -6,18 +6,16 @@ edition = "2018"
description = "Rusty Utility AI library"
license-file = "LICENSE.md"
readme = "README.md"
keywords = ["utility-ai", "specs", "ecs"]
keywords = ["utility-ai", "bevy", "ai", "ecs"]
categories = ["game-development"]
repository = "https://github.com/zkat/big-brain"
homepage = "https://github.com/zkat/big-brain"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
big-brain-derive = "0.1"
specs = { version = "0.16.1", features = ["parallel", "shred-derive", "specs-derive"] }
big-brain-derive = { path = "./big-brain-derive", version = "0.1" }
serde = "1.0.111"
typetag = "0.1.5"
specs-derive = "0.4.1"
ron = "0.6.0"
bevy = "0.5.0"
`big-brain` is a [Utility AI](https://en.wikipedia.org/wiki/Utility_system)
library for games, built on the [`specs` ECS](https://docs.rs/specs).
library for games, built for the [Bevy Game Engine](https://bevyengine.org/)
It lets you define complex, intricate AI behaviors for your entities based on
their perception of the world. Definitions are almost entirely data-driven,
......@@ -17,39 +17,30 @@ First, you define actions and considerations, which are just plain old `specs`
### Considerations
`Consideration`s are entities that look at the world and evaluate into `Utility`s.
`Consideration`s are entities that look at the world and evaluate into `Utility` values.
```rust
use specs::{Component, Entity, ReadStorage, System, WriteStorage};
use big_brain::{Consideration, Utility};
use bevy::prelude::*;
use big_brain::*;
use crate::components;
#[derive(Debug, Component, Consideration)]
pub struct Hunger {
pub actor: Entity,
#[derive(Debug, Consideration)]
pub struct ThirstConsideration {
#[consideration(default)]
pub evaluator: PowerEvaluator,
#[consideration(param)]
pub weight: f32,
}
pub struct ConsiderHunger;
impl<'a> System<'a> for ConsiderHunger {
type SystemData = (
ReadStorage<'a, components::Hunger>,
WriteStorage<'a, Hunger>,
WriteStorage<'a, Utility>,
);
fn run(&mut self, (hungers, mut considerers, mut utilities): Self::SystemData) {
for (conser, util) in (&mut considerers, &mut utilities).join() {
if let Some(hunger) = hungers.get(conser.actor.clone()) {
*util = Utility {
value: conser.evaluator.evaluate(hunger.hunger),
weight: conser.weight,
};
}
pub fn thirst_consideration_system(
thirsts: Query<&Thirst>,
mut query: Query<(&Parent, &ThirstConsideration, &mut Utility)>,
) {
for (Parent(actor), conser, mut util) in query.iter_mut() {
if let Ok(thirst) = thirsts.get(*actor) {
*util = Utility {
value: conser.evaluator.evaluate(thirst.thirst),
weight: conser.weight,
};
}
}
}
......@@ -60,40 +51,25 @@ impl<'a> System<'a> for ConsiderHunger {
`Action`s are the actual things your entities will _do_.
```rust
use specs::{Component, Entity, System, WriteStorage};
use big_brain::{Action, ActionState};
use crate::components;
#[derive(Debug, Clone, Component, Action)]
pub struct Eat {
pub actor: Entity,
#[action(default)]
pub foo: f32,
#[action(param)]
pub reduce_by: f32,
}
pub struct EatSystem;
impl<'a> System<'a> for EatSystem {
type SystemData = (
WriteStorage<'a, components::Hunger>,
WriteStorage<'a, Eat>,
WriteStorage<'a, ActionState>,
);
fn run(&mut self, (mut hungers, mut eat_actions, mut states): Self::SystemData) {
for (state, eat_action) in (&mut states, &mut eat_actions).join() {
if let Some(hunger) = hungers.get_mut(eat_action.actor.clone()) {
match state {
ActionState::Requested => {
hunger.hunger -= eat_action.reduce_by;
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
#[derive(Debug, Action)]
pub struct DrinkAction {}
fn drink_action_system(
mut thirsts: Query<&mut Thirst>,
mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
) {
for (Parent(actor), _drink_action, mut state) in query.iter_mut() {
if let Ok(mut thirst) = thirsts.get_mut(*actor) {
match *state {
ActionState::Requested => {
thirst.thirst = 10.0;
println!("drank some water");
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
......@@ -102,7 +78,7 @@ impl<'a> System<'a> for EatSystem {
### Thinker Definition
Finally, you can define the `Thinker`:
Finally, you can use it when define the `Thinker`:
```ron
// behavior.ron
......
......@@ -16,6 +16,6 @@ proc-macro = true
[dependencies]
syn = "1.0.33"
quote = "1.0.7"
big-brain = "0.1.0"
# big-brain = { path = "../", version = "0.1.0" }
proc-macro2 = "1.0.18"
darling = "0.10.2"
use darling::{ast, FromDeriveInput, FromField, ToTokens};
use proc_macro2::TokenStream;
use quote::quote;
use syn;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(action), supports(struct_named))]
......@@ -70,10 +69,10 @@ impl ToTokens for Action {
}
});
let ts = quote! {
mod big_brain_builder {
mod big_brain_action_builder {
use super::#ident as Comp;
use big_brain::{typetag, serde::Deserialize, Action, ActionManager, ecs::{Entity, Entities, LazyUpdate}, ActionEnt};
use big_brain::{typetag, serde::Deserialize, Action, ActionManager, bevy::prelude::{Entity, Commands}, ActionEnt};
#[derive(Debug, Deserialize)]
struct #ident {
......@@ -82,19 +81,19 @@ impl ToTokens for Action {
#[typetag::deserialize]
impl Action for #ident {
fn build(self: Box<Self>, actor: Entity, action_ent: ActionEnt, ents: &Entities, lazy: &LazyUpdate) -> Box<dyn ActionManager> {
fn build(self: Box<Self>, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) -> Box<dyn ActionManager> {
self
}
}
impl ActionManager for #ident {
fn activate(&self, actor: Entity, action_ent: ActionEnt, lazy: &LazyUpdate) {
lazy.insert(action_ent.0.clone(), Comp {
fn activate(&self, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) {
cmd.entity(action_ent.0).insert(Comp {
#(#field_assignments),*
});
}
fn deactivate(&self, action_ent: ActionEnt, lazy: &LazyUpdate) {
lazy.remove::<Comp>(action_ent.0);
fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) {
cmd.entity(action_ent.0).remove::<Comp>();
}
}
}
......
use darling::{ast, FromDeriveInput, FromField, ToTokens};
use proc_macro2::TokenStream;
use quote::quote;
use syn;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(consideration), supports(struct_named))]
......@@ -70,10 +69,11 @@ impl ToTokens for Consideration {
}
});
let ts = quote! {
mod big_brain_builder {
mod big_brain_cons_builder {
use super::#ident as Comp;
use big_brain::{typetag, serde::Deserialize, Consideration, ecs::{Entity, Entities, LazyUpdate}, ConsiderationEnt};
use big_brain::{typetag, serde::Deserialize, Consideration, bevy::prelude::{Entity, Commands}, ConsiderationEnt};
// use typetag;
#[derive(Debug, Deserialize)]
struct #ident {
......@@ -81,10 +81,11 @@ impl ToTokens for Consideration {
}
#[typetag::deserialize]
impl Consideration for #ident {
fn build(&self, actor: Entity, ents: &Entities, lazy: &LazyUpdate) -> ConsiderationEnt {
let ent = ConsiderationEnt(ents.create());
lazy.insert(ent.clone().0, big_brain::Utility::default());
lazy.insert(ent.clone().0, Comp {
fn build(&self, actor: Entity, cmd: &mut Commands) -> ConsiderationEnt {
let ent = ConsiderationEnt(cmd.spawn().id());
cmd.entity(ent.0)
.insert(big_brain::Utility::default())
.insert(Comp {
#(#field_assignments),*
});
ent
......
use bevy::prelude::*;
use big_brain::{evaluators::*, *};
// First, we define a "Thirst" component and associated system. This is NOT
// THE AI. It's a plain old system that just makes an entity "thirstier" over
// time. This is what the AI will later interact with.
//
// There's nothing special here. It's a plain old Bevy component.
#[derive(Debug)]
pub struct Thirst {
pub per_second: f32,
pub thirst: f32,
}
impl Thirst {
pub fn new(thirst: f32, per_second: f32) -> Self {
Self { thirst, per_second }
}
}
pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
for mut thirst in thirsts.iter_mut() {
thirst.thirst +=
thirst.per_second * (time.delta().as_micros() as f32 / 1000000.0);
println!("Getting thirstier...{}%", thirst.thirst);
}
}
// The second step is to define an action. What can the AI do, and how does it
// do it? This is the first bit involving Big Brain itself, and there's a few
// pieces you need:
// First, you need an Action struct, and derive Action.
//
// These actions will be spawned and queued by the game engine when their
// conditions trigger (we'll configure what these are later).
#[derive(Debug, Action)]
pub struct DrinkAction {}
// Associated with that DrinkAction, you then need to have a system that will
// actually execute those actions when they're "spawned" by the Big Brain
// engine.
//
// In our case, we want the Thirst components, since we'll be changing those.
// Additionally, we want to pick up the DrinkAction components, as well as
// their associated ActionState. Note that the DrinkAction belongs to a
// *separate entity* from the owner of the Thirst component!
fn drink_action_system(
mut thirsts: Query<&mut Thirst>,
// We grab the Parent here, because individual Actions are parented to the
// entity "doing" the action.
//
// ActionState is an enum that described the specific run-state the action
// is in. You can think of Actions as state machines. They get requested,
// they can be cancelled, they can run to completion, etc. Cancellations
// usually happen because the target action changed (due to a different
// Consideration winning). But you can also cancel the actions yourself by
// setting the state in the Action system.
mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
) {
for (Parent(actor), _drink_action, mut state) in query.iter_mut() {
// Use the drink_action's actor to look up the corresponding Thirst.
if let Ok(mut thirst) = thirsts.get_mut(*actor) {
match *state {
ActionState::Requested => {
thirst.thirst = 10.0;
println!("drank some water");
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
}
// Then, we have something called "Considerations". These are special
// components that run in the background, calculating a "Utility" value, which
// is what Big Brain will use to pick which actions to execute.
//
// Additionally, though, we pull in an evaluator and define a weight. Which is
// just mathy stuff you can tweak to get the behavior you want. More on this
// in the docs (later), but for now, just put them in there and trust the
// system. :)
#[derive(Debug, Consideration)]
pub struct ThirstConsideration {
#[consideration(default)]
pub evaluator: PowerEvaluator,
#[consideration(param)]
pub weight: f32,
}
// Look familiar? Similar dance to Actions here.
pub fn thirst_consideration_system(
thirsts: Query<&Thirst>,
// Same dance with the Parent here, but now we've added a Utility!
mut query: Query<(&Parent, &ThirstConsideration, &mut Utility)>,
) {
for (Parent(actor), conser, mut util) in query.iter_mut() {
if let Ok(thirst) = thirsts.get(*actor) {
// This is really what the job of a Consideration is. To calculate
// a generic Utility value that the Big Brain engine will compare
// against others, over time, and use to make decisions. This is
// generally "the higher the better", and "first across the finish
// line", but that's all configurable! You can customize that to
// your heart's extent using Measures and Pickers.
//
// A note: You don't actually *need* evaluators/weights. You can
// literally just use linear values here and set thresholds
// accordingly. The evaluator is just there to give the value a
// bit of a curve.
*util = Utility {
value: conser.evaluator.evaluate(thirst.thirst),
weight: conser.weight,
};
}
}
}
// Now that we hav eall that defined, it's time to add a Thinker to an entity!
// The Thinker is the actual "brain" behind all the AI. Every entity you want
// to have AI behavior should have one *or more* Thinkers attached to it.
// Thinkers are configured using RON right now, with a DSL that makes it easy
// to define, in data, the actual behavior you want.
pub fn init_entities(mut cmd: Commands) {
let actor = cmd.spawn().insert(Thirst::new(80.0, 2.0)).id();
// Here's a very simple one that only has one consideration and one
// associated action. But you can have more of them, and even nest them by
// using more Thinkers (which are actually themselves Actions). See
// basic.ron in examples/ for a more involved Thinker definition.
//
// Ultimately, these Thinkers are meant to be usable by non-programmers:
// You, the developer, create Actions and Considerations, and someone else
// is then able to put them all together like LEGOs into all sorts of
// intricate logic.
Thinker::load_from_str(
r#"
(
picker: {"FirstToScore": (threshold: 80.0)},
choices: [(
consider: [{"ThirstConsideration": (weight: 2.0)}],
then: {"DrinkAction": ()},
)],
)
"#,
)
.build(actor, &mut cmd);
}
fn main() {
// Once all that's done, we just add our systems and off we go!
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(init_entities.system())
.add_system(thirst_system.system())
.add_system(thirst_consideration_system.system())
.add_system(drink_action_system.system())
// Don't forget the Thinker system itself! This is the heart of it all!
.add_system(big_brain::thinker_system.system())
.run();
}
use specs::{Component, DenseVecStorage, Entities, Entity, LazyUpdate, ReadStorage, WriteStorage};
use typetag;
use bevy::prelude::*;
use crate::ActionEnt;
#[derive(Debug, Component)]
#[derive(Debug)]
pub struct ActionManagerWrapper(pub(crate) Box<dyn ActionManager>);
#[derive(Debug, Component, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ActionState {
Init,
Requested,
......@@ -21,34 +20,12 @@ impl ActionState {
Self::default()
}
pub fn get<'a>(state_ent: &ActionEnt, states: &'a ReadStorage<ActionState>) -> &'a Self {
states
.get(state_ent.0.clone())
.expect("ActionState doesn't exist?")
}
pub fn get_mut<'a>(
state_ent: &ActionEnt,
states: &'a mut WriteStorage<ActionState>,
) -> &'a mut Self {
states
.get_mut(state_ent.0.clone())
.expect("ActionState doesn't exist?")
}
pub(crate) fn build(
builder: Box<dyn Action>,
actor: Entity,
ents: &Entities,
lazy: &LazyUpdate,
) -> ActionEnt {
let action_ent = ActionEnt(ents.create());
let ent_clone = action_ent.clone();
lazy.insert(ent_clone.0, ActionState::default());
lazy.insert(
ent_clone.0,
ActionManagerWrapper(builder.build(actor, action_ent.clone(), ents, lazy)),
);
pub(crate) fn build(builder: Box<dyn Action>, actor: Entity, cmd: &mut Commands) -> ActionEnt {
let action_ent = ActionEnt(cmd.spawn().id());
let manager_wrapper = ActionManagerWrapper(builder.build(actor, action_ent, cmd));
cmd.entity(action_ent.0)
.insert(ActionState::default())
.insert(manager_wrapper);
action_ent
}
}
......@@ -68,12 +45,11 @@ pub trait Action: std::fmt::Debug + Send + Sync {
self: Box<Self>,
actor: Entity,
action_ent: ActionEnt,
ents: &Entities,
lazy: &LazyUpdate,
cmd: &mut Commands,
) -> Box<dyn ActionManager>;
}
pub trait ActionManager: std::fmt::Debug + Send + Sync {
fn activate(&self, actor: Entity, action: ActionEnt, lazy: &LazyUpdate);
fn deactivate(&self, action: ActionEnt, lazy: &LazyUpdate);
fn activate(&self, actor: Entity, action: ActionEnt, cmd: &mut Commands);
fn deactivate(&self, action: ActionEnt, cmd: &mut Commands);
}
use bevy::prelude::*;
use serde::Deserialize;
use specs::{Entities, Entity, LazyUpdate, ReadStorage};
use crate::{
actions::{Action, ActionState},
......@@ -16,13 +16,13 @@ pub struct Choice {
pub action_state: ActionEnt,
}
impl Choice {
pub fn calculate<'a>(&self, utilities: &ReadStorage<'a, Utility>) -> f32 {
pub fn calculate(&self, utilities: &Query<&Utility>) -> f32 {
self.measure.calculate(
self.utilities
.iter()
.map(|choice_cons| {
utilities
.get(choice_cons.0.clone())
.get(choice_cons.0)
.expect("Where did the utility go?")
})
.collect(),
......@@ -36,16 +36,16 @@ pub struct ChoiceBuilder {
pub then: Box<dyn Action>,
}
impl ChoiceBuilder {
pub fn build(self, actor: Entity, ents: &Entities, lazy: &LazyUpdate) -> Choice {
pub fn build(self, actor: Entity, cmd: &mut Commands) -> Choice {
let action = self.then;
Choice {
measure: Box::new(WeightedMeasure),
utilities: self
.consider
.iter()
.map(|cons| cons.build(actor.clone(), ents, lazy))
.map(|cons| cons.build(actor, cmd))
.collect(),
action_state: ActionState::build(action, actor, ents, lazy),
action_state: ActionState::build(action, actor, cmd),
}
}
}
use specs::{Component, DenseVecStorage, Entities, Entity, LazyUpdate};
use typetag;
use bevy::prelude::*;
use crate::ConsiderationEnt;
#[derive(Debug, Clone, Default, Component)]
#[derive(Debug, Clone, Default)]
pub struct Utility {
pub value: f32,
pub weight: f32,
}
/**
This trait defines new considerations. In general, you should use the [derive macro](derive.Consideration.html) instead.
*/
#[typetag::deserialize]
pub trait Consideration: std::fmt::Debug + Sync + Send {
fn build(&self, entity: Entity, ents: &Entities, lazy: &LazyUpdate) -> ConsiderationEnt;
fn build(&self, entity: Entity, cmd: &mut Commands) -> ConsiderationEnt;
}
use serde::{Deserialize, Serialize};
use typetag;
#[typetag::serde]
pub trait Evaluator: std::fmt::Debug + Sync + Send {
......
//! `big-brain` is a [Utility
//! AI](https://en.wikipedia.org/wiki/Utility_system) library for implementing
//! rich, complex artificial intelligence mainly in video games using the
//! [`specs`](https://docs.rs/specs) ECS system.
//!
//! `big-brain` not only allows you to define these complex behaviors, but it
//! allows you to define them in a data-oriented format, such as
//! [`RON`](https://docs.rs/ron), potentially allowing non-programmers to
//! define many of these behaviors themselves.
//!
//! In general, the only things that need to be programmed are
//! [Actions](derive.Action.html) and
//! [Considerations](derive.Consideration.html). Everything else is included
//! with `big-brain`.
//!
//! For example, this is what a basic thinker might look like:
//!
//! ```ignore
//! // basic_needs.ron
//! (
//! // The first Choice to score above the threshold will be executed.
//! picker: {"FirstToScore": (threshold: 80.0)},
//! // A list of choices, with their considerations evaluated in order,
//! // and picked using the Picker.
//! choices: [(
//! consider: [{"Bladder": ()}],
//! then: {"Pee": ()},
//! ), (
//! consider: [{"Hunger": ()}],
//! // Thinkers are also actions, so you can nest them indefinitely.
//! then: {"Thinker": (
//! picker: {"FirstToScore": (threshold: 80.0)},
//! choices: [(
//! consider: [{"FoodInRange": (range: 10.0)}],
//! then: {"EatFood": (range: 10.0)}
//! ), (
//! consider: [{"FoodNearby": (range: 1000.0)}],
//! then: {"WalkToFood": (range: 1000.0)}
//! )]
//! )},
//! )],
//! // If no Choice goes over the threshold, we just... wander around
//! otherwise: Some({"Meander": ()})
//! )
//! ```
//!
//! You would then load up the component into a `specs` entity like so:
//!
//! ```no_run
//! use big_brain::ThinkerBuilder;
//! use specs::{Entities, Entity, LazyUpdate, World, WorldExt};
//! let mut world = World::new();
//! let entity = world.create_entity().build();
//! world.exec(|(entities, lazy): (Entities, Read<LazyUpdate>)| {
//! ThinkerBuilder::load_from("./path/to/basic.ron").build(entity, &entities, &lazy);
//! });
//! ```
use specs::{World, WorldExt};
pub use bevy;
pub use big_brain_derive::*;
pub use serde;
pub use specs as ecs;
pub use typetag;
pub use actions::*;
......@@ -75,13 +15,4 @@ pub mod pickers;
mod actions;
mod choices;
mod considerations;
mod stopwatch;
mod thinker;
pub fn register(world: &mut World) {
world.register::<Utility>();
world.register::<ActionState>();
world.register::<ActionManagerWrapper>();
world.register::<ActiveThinker>();
world.register::<Thinker>();
}
use serde::{Deserialize, Serialize};
use typetag;
use crate::Utility;
......
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use specs::ReadStorage;
use typetag;
use crate::{choices::Choice, considerations::Utility, thinker::ActionEnt};
#[typetag::serde]
pub trait Picker: std::fmt::Debug + Sync + Send {
fn pick<'a>(
&mut self,
_choices: &Vec<Choice>,
_utilities: &ReadStorage<'a, Utility>,
) -> Option<ActionEnt>;
fn pick(&self, _choices: &[Choice], _utilities: &Query<&Utility>) -> Option<ActionEnt>;
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
......@@ -20,15 +16,11 @@ pub struct FirstToScore {
#[typetag::serde]
impl Picker for FirstToScore {
fn pick<'a>(
&mut self,
choices: &Vec<Choice>,
utilities: &ReadStorage<'a, Utility>,
) -> Option<ActionEnt> {
fn pick(&self, choices: &[Choice], utilities: &Query<&Utility>) -> Option<ActionEnt> {
for choice in choices {
let value = choice.calculate(utilities);
if value >= self.threshold {
return Some(choice.action_state.clone());
return Some(choice.action_state);
}
}
None
......
// This file is taken from amethyst_core
use std::time::{Duration, Instant};
/// A stopwatch which accurately measures elapsed time.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Stopwatch {
/// Initial state with an elapsed time value of 0 seconds.
Waiting,
/// Stopwatch has started counting the elapsed time since this `Instant`
/// and accumuluated time from previous start/stop cycles `Duration`.
Started(Duration, Instant),
/// Stopwatch has been stopped and reports the elapsed time `Duration`.
Ended(Duration),
}
impl Default for Stopwatch {
fn default() -> Stopwatch {
Stopwatch::Waiting
}
}
impl Stopwatch {
/// Creates a new stopwatch.
pub fn new() -> Stopwatch {
Default::default()
}
/// Retrieves the elapsed time.
pub fn elapsed(&self) -> Duration {
match *self {
Stopwatch::Waiting => Duration::new(0, 0),
Stopwatch::Started(dur, start) => dur + start.elapsed(),
Stopwatch::Ended(dur) => dur,
}
}
/// Stops, resets, and starts the stopwatch again.
pub fn restart(&mut self) {
*self = Stopwatch::Started(Duration::new(0, 0), Instant::now());
}
/// Starts, or resumes, measuring elapsed time. If the stopwatch has been
/// started and stopped before, the new results are compounded onto the
/// existing elapsed time value.
///
/// Note: Starting an already running stopwatch will do nothing.
pub fn start(&mut self) {
match *self {
Stopwatch::Waiting => self.restart(),
Stopwatch::Ended(dur) => {
*self = Stopwatch::Started(dur, Instant::now());
}
_ => {}
}
}
/// Stops measuring elapsed time.
///
/// Note: Stopping a stopwatch that isn't running will do nothing.
pub fn stop(&mut self) {
if let Stopwatch::Started(dur, start) = *self {
*self = Stopwatch::Ended(dur + start.elapsed());
}
}
/// Clears the current elapsed time value.
pub fn reset(&mut self) {
*self = Stopwatch::Waiting;
}
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment