1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Extension system utilities.
//!
//! This modules contains an extension trait for the System trait which adds useful transformation
//! functions.

use crate::{
    ecs::prelude::{Read, System, World},
    shred::{RunningTime, SystemData},
};

#[cfg(feature = "profiler")]
use thread_profiler::profile_scope;

/// Extension functionality associated systems.
pub trait SystemExt {
    /// Make a system pausable by tying it to a specific value of a resource.
    ///
    /// When the value of the resource differs from `value` the system is considered "paused",
    /// and the `run` method of the pausable system will not be called.
    ///
    /// # Notes
    ///
    /// Special care must be taken not to read from an `EventChannel` with pausable systems.
    /// Since `run` is never called, there is no way to consume the reader side of a channel, and
    /// it may grow indefinitely leaking memory while the system is paused.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use amethyst::{
    ///     ecs::{System, Write},
    ///     shred::DispatcherBuilder,
    ///     prelude::*,
    /// };
    ///
    /// #[derive(PartialEq)]
    /// enum CurrentState {
    ///     Disabled,
    ///     Enabled,
    /// }
    ///
    /// impl Default for CurrentState {
    ///     fn default() -> Self {
    ///         CurrentState::Disabled
    ///     }
    /// }
    ///
    /// struct AddNumber(u32);
    ///
    /// impl<'s> System<'s> for AddNumber {
    ///     type SystemData = Write<'s, u32>;
    ///
    ///     fn run(&mut self, mut number: Self::SystemData) {
    ///         *number += self.0;
    ///     }
    /// }
    ///
    /// let mut world = World::new();
    ///
    /// let mut dispatcher = DispatcherBuilder::default()
    ///     .with(AddNumber(1), "set_number", &[])
    ///     .with(AddNumber(2).pausable(CurrentState::Enabled), "set_number_2", &[])
    ///     .build();
    ///
    /// dispatcher.setup(&mut world);
    ///
    /// // we only expect the u32 resource to be modified _if_ the system is enabled,
    /// // the system should only be enabled on CurrentState::Enabled.
    ///
    /// *world.write_resource() = 0u32;
    /// dispatcher.dispatch(&mut world);
    /// assert_eq!(1, *world.read_resource::<u32>());
    ///
    /// *world.write_resource() = 0u32;
    /// *world.write_resource() = CurrentState::Enabled;
    /// dispatcher.dispatch(&mut world);
    /// assert_eq!(1 + 2, *world.read_resource::<u32>());
    /// ```
    fn pausable<V: 'static>(self, value: V) -> Pausable<Self, V>
    where
        Self: Sized,
        V: Send + Sync + Default + PartialEq;
}

impl<'s, S> SystemExt for S
where
    S: System<'s>,
{
    fn pausable<V: 'static>(self, value: V) -> Pausable<Self, V>
    where
        Self: Sized,
        V: Send + Sync + Default + PartialEq,
    {
        Pausable {
            system: self,
            value,
        }
    }
}

/// A system that is enabled when `V` has a specific value.
///
/// This is created using the [`SystemExt::pausable`] method.
///
/// [`SystemExt::pausable`]: trait.SystemExt.html#tymethod.pausable
#[derive(Debug)]
pub struct Pausable<S, V> {
    system: S,
    value: V,
}

impl<'s, S, V: 'static> System<'s> for Pausable<S, V>
where
    S::SystemData: SystemData<'s>,
    S: System<'s>,
    V: Send + Sync + Default + PartialEq,
{
    type SystemData = (Read<'s, V>, S::SystemData);

    fn run(&mut self, data: Self::SystemData) {
        #[cfg(feature = "profiler")]
        profile_scope!("pauseable_system");

        if self.value != *data.0 {
            return;
        }

        self.system.run(data.1);
    }

    fn running_time(&self) -> RunningTime {
        self.system.running_time()
    }

    fn setup(&mut self, world: &mut World) {
        Self::SystemData::setup(world);

        self.system.setup(world);
    }
}