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
use bevy::prelude::*;

pub(crate) struct BatteryPlugin;

impl Plugin for BatteryPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Update, discharge_battery);
    }
}

/// The rate at which the battery discharges in Joules per second.
const DISCHARGE_RATE: f64 = 30_000.;
/// The default capacity of the battery in Joules.
const DEFAULT_CAPACITY: f64 = 100_000_000.; // 100 Mj

/// The battery component is used to store the energy level of an entity.
#[derive(Component, Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Battery {
    /// The maximum capacity of the battery in joules.
    capacity: f64,

    /// The current energy level of the battery in joules.
    energy: f64,
}

impl Default for Battery {
    fn default() -> Self {
        Self::new(DEFAULT_CAPACITY, DEFAULT_CAPACITY)
    }
}

impl Battery {
    fn new(capacity: f64, energy: f64) -> Self {
        debug_assert!(capacity.is_finite());
        debug_assert!(capacity > 0.);
        debug_assert!(energy.is_finite());
        debug_assert!(energy >= 0.);
        debug_assert!(energy <= capacity);

        Self { capacity, energy }
    }

    /// The maximum capacity of the battery in joules.
    pub fn capacity(&self) -> f64 {
        self.capacity
    }

    /// The current energy level of the battery in joules.
    pub fn energy(&self) -> f64 {
        self.energy
    }

    /// Directly changes the energy level of the battery by the given amount of energy.
    fn change(&mut self, delta: f64) {
        debug_assert!(delta.is_finite());

        self.energy = (self.energy + delta).clamp(0., self.capacity);
    }
}

/// Discharges the batteries of all units.
///
/// # Arguments
///
/// * `time` - The time since the last update.
///
/// * `battery` - The battery.
pub(crate) fn discharge_battery(time: Res<Time>, mut battery: Query<&mut Battery>) {
    let delta = time.delta_seconds();
    let discharge_delta = DISCHARGE_RATE * delta as f64;
    for mut battery in battery.iter_mut() {
        let energy = battery.energy();
        if energy == 0. {
            continue;
        }

        battery.change(-discharge_delta);
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use bevy::prelude::*;

    use super::*;
    use crate::battery::{Battery, DEFAULT_CAPACITY, DISCHARGE_RATE};

    #[test]
    fn test_discharge() {
        // make new bevy app
        let mut app = App::new();
        // add entity and battery
        let entity = app
            .world
            .spawn((
                Battery::default(), // 100 kJ capacity, 100 kJ energy
            ))
            .id();

        app.init_resource::<Time>();
        app.add_plugins(BatteryPlugin);

        // run the app for 1 second
        app.update();
        app.world
            .get_resource_mut::<Time>()
            .unwrap()
            .advance_by(Duration::from_secs(1));
        app.update();

        // check that the battery has discharged by at least 1*rate and 1.5*rate at most
        let battery = app.world.get::<Battery>(entity).unwrap();
        println!("battery: {:?}", battery);

        assert!(battery.energy() == DEFAULT_CAPACITY - DISCHARGE_RATE);
    }
}