Skip to main content

entities/
entities.rs

1// SPDX-FileCopyrightText: 2026 undoredo contributors
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::ops::{Add, AddAssign};
6
7use undoredo::{Delta, Recorder, UndoRedo};
8
9// No need for `#[derive(Delta)]` for types stored in containers, only the
10// containers themselves need this.
11#[derive(Clone, Copy, Debug, PartialEq)]
12struct Vector2<T> {
13    x: T,
14    y: T,
15}
16
17impl<T: Add<T, Output = T>> Add for Vector2<T> {
18    type Output = Self;
19
20    fn add(self, rhs: Self) -> Self {
21        Self {
22            x: self.x + rhs.x,
23            y: self.y + rhs.y,
24        }
25    }
26}
27
28impl<T: AddAssign<T>> AddAssign for Vector2<T> {
29    fn add_assign(&mut self, rhs: Self) {
30        self.x += rhs.x;
31        self.y += rhs.y;
32    }
33}
34
35// `#[derive(Delta)]` generates the `EntitiesDelta` type, which is needed to
36// store deltas as edits in an `UndoRedo` bistack.
37#[derive(Delta)]
38// You can choose the name for the generated delta edit type through the
39// `undoredo` attribute with key `delta`. If you omit this, the default is
40// the name of the input type followed by the word `Delta`. The name chosen in
41// this example, `EntitiesDelta`, happens to be the same as the default.
42#[undoredo(delta = EntitiesDelta)]
43// Each delta is made of two collections of elements called "half-deltas".
44// `#[derive(Delta)]` generates a type for them as well, named similarly to full
45// deltas. If that name does not suit you, you can analogously use the
46// `undoredo` attribute with key `half_delta` to rename them.,
47#[undoredo(half_delta = EntitiesHalfDelta)]
48struct Entities<T> {
49    positions: Recorder<Vec<Vector2<T>>>,
50    velocities: Recorder<Vec<Vector2<T>>>,
51    healths: Recorder<Vec<i64>>,
52    // You can record changes to primitive types too, not just collections.
53    turn_counter: Recorder<u64>,
54    // You can make fields not be subject to undo-redo by marking them with
55    // `#[undoredo(skip)]`.
56    // Note that this skipping only works for delta-based undo-redo because
57    // snapshots and commands rely on different mechanisms.
58    #[undoredo(skip)]
59    #[allow(unused)]
60    not_in_delta: String,
61}
62
63impl<T: Add<T, Output = T> + AddAssign<T> + Copy + PartialOrd> Entities<T> {
64    pub fn new_entity(&mut self, position: Vector2<T>, velocity: Vector2<T>, health: i64) -> usize {
65        self.positions.push(position);
66        self.velocities.push(velocity);
67        self.healths.push(health)
68    }
69
70    pub fn update(&mut self) {
71        self.turn_counter.assign(*self.turn_counter.container() + 1);
72
73        for i in 0..self.positions.container().len() {
74            self.update_entity(i);
75        }
76    }
77
78    fn update_entity(&mut self, index: usize) {
79        // Add velocities to positions on every update.
80
81        let velocity = self.velocities[index];
82        self.positions
83            .modify(index, |position| *position += velocity);
84
85        // Alternatively, you could update them more verbosely this way:
86
87        /*self.positions
88        .set(index, self.positions[index] + self.velocities[index]);*/
89
90        // Decrease health by 1 on every update.
91        self.healths.set(index, self.healths[index] - 1);
92    }
93}
94
95fn assert_entity(entities: &Entities<f64>, index: usize, pos: Vector2<f64>, health: i64) {
96    assert_eq!(entities.positions[index], pos);
97    assert_eq!(entities.healths[index], health);
98}
99
100fn main() {
101    let mut entities = Entities::<f64> {
102        positions: Recorder::new(Vec::new()),
103        velocities: Recorder::new(Vec::new()),
104        healths: Recorder::new(Vec::new()),
105        turn_counter: Recorder::new(0),
106        not_in_delta: "not_in_delta".to_string(),
107    };
108
109    // `EntitiesDelta` was generated by `#[derive(Delta)]` on `Entities`.
110    let mut undoredo: UndoRedo<EntitiesDelta<f64>> = UndoRedo::new();
111
112    entities.new_entity(Vector2 { x: 0.0, y: 0.0 }, Vector2 { x: 1.0, y: 0.0 }, 100);
113    entities.new_entity(Vector2 { x: 0.0, y: 0.0 }, Vector2 { x: 0.0, y: 2.0 }, 100);
114    undoredo.commit(&mut entities);
115
116    entities.new_entity(
117        Vector2 { x: 100.0, y: 100.0 },
118        Vector2 { x: -1.0, y: -1.0 },
119        50,
120    );
121    undoredo.commit(&mut entities);
122
123    assert_eq!(entities.positions.container().len(), 3);
124
125    // Perform three simulation updates, committing after each tick.
126    for _ in 0..3 {
127        entities.update();
128        undoredo.commit(&mut entities);
129    }
130
131    // Perform five more simulation updates, this time committing only once afterwards.
132    for _ in 0..5 {
133        entities.update();
134    }
135    undoredo.commit(&mut entities);
136
137    assert!(entities.positions.container().len() == 3);
138    assert_entity(&entities, 0, Vector2 { x: 8.0, y: 0.0 }, 92);
139    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 16.0 }, 92);
140    assert_entity(&entities, 2, Vector2 { x: 92.0, y: 92.0 }, 42);
141    assert_eq!(*entities.turn_counter.container(), 8);
142
143    undoredo.undo(&mut entities);
144
145    assert!(entities.positions.container().len() == 3);
146    assert_entity(&entities, 0, Vector2 { x: 3.0, y: 0.0 }, 97);
147    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 6.0 }, 97);
148    assert_entity(&entities, 2, Vector2 { x: 97.0, y: 97.0 }, 47);
149    assert_eq!(*entities.turn_counter.container(), 3);
150
151    undoredo.undo(&mut entities);
152
153    assert!(entities.positions.container().len() == 3);
154    assert_entity(&entities, 0, Vector2 { x: 2.0, y: 0.0 }, 98);
155    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 4.0 }, 98);
156    assert_entity(&entities, 2, Vector2 { x: 98.0, y: 98.0 }, 48);
157
158    undoredo.undo(&mut entities);
159
160    assert!(entities.positions.container().len() == 3);
161    assert_entity(&entities, 0, Vector2 { x: 1.0, y: 0.0 }, 99);
162    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 2.0 }, 99);
163    assert_entity(&entities, 2, Vector2 { x: 99.0, y: 99.0 }, 49);
164
165    undoredo.undo(&mut entities);
166
167    assert!(entities.positions.container().len() == 3);
168    assert_entity(&entities, 0, Vector2 { x: 0.0, y: 0.0 }, 100);
169    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 0.0 }, 100);
170    assert_entity(&entities, 2, Vector2 { x: 100.0, y: 100.0 }, 50);
171    assert_eq!(*entities.turn_counter.container(), 0);
172
173    undoredo.undo(&mut entities);
174
175    assert!(entities.positions.container().len() == 2);
176    assert_entity(&entities, 0, Vector2 { x: 0.0, y: 0.0 }, 100);
177    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 0.0 }, 100);
178
179    undoredo.redo(&mut entities);
180
181    assert!(entities.positions.container().len() == 3);
182    assert_entity(&entities, 0, Vector2 { x: 0.0, y: 0.0 }, 100);
183    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 0.0 }, 100);
184    assert_entity(&entities, 2, Vector2 { x: 100.0, y: 100.0 }, 50);
185
186    undoredo.redo(&mut entities);
187
188    assert_entity(&entities, 0, Vector2 { x: 1.0, y: 0.0 }, 99);
189    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 2.0 }, 99);
190    assert_entity(&entities, 2, Vector2 { x: 99.0, y: 99.0 }, 49);
191
192    undoredo.redo(&mut entities);
193
194    assert_entity(&entities, 0, Vector2 { x: 2.0, y: 0.0 }, 98);
195    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 4.0 }, 98);
196    assert_entity(&entities, 2, Vector2 { x: 98.0, y: 98.0 }, 48);
197
198    undoredo.redo(&mut entities);
199
200    assert_entity(&entities, 0, Vector2 { x: 3.0, y: 0.0 }, 97);
201    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 6.0 }, 97);
202    assert_entity(&entities, 2, Vector2 { x: 97.0, y: 97.0 }, 47);
203
204    undoredo.redo(&mut entities);
205
206    assert!(entities.positions.container().len() == 3);
207    assert_entity(&entities, 0, Vector2 { x: 8.0, y: 0.0 }, 92);
208    assert_entity(&entities, 1, Vector2 { x: 0.0, y: 16.0 }, 92);
209    assert_entity(&entities, 2, Vector2 { x: 92.0, y: 92.0 }, 42);
210    assert_eq!(*entities.turn_counter.container(), 8);
211}
212
213#[test]
214fn test() {
215    main();
216}