ecs/
main.rs

1//! A simple example of an ECS (Entity-Component-System) in teng.
2//!
3//! Arbitrary structs can be registered and used as components, and systems are just
4//! types that implement `teng::Component`.
5
6use anymap::AnyMap;
7use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
8use std::any::{Any, TypeId};
9use std::collections::HashMap;
10use std::io;
11use std::io::stdout;
12use teng::components::Component as TengComponent;
13use teng::rendering::pixel::Pixel;
14use teng::rendering::renderer::Renderer;
15use teng::{BreakingAction, Game, SetupInfo, SharedState};
16
17/// An ECS-component that holds the position of an entity.
18struct Position {
19    x: usize,
20    y: usize,
21}
22
23/// An ECS-component that holds the display character of an entity.
24struct Draw {
25    ch: char,
26}
27
28/// An ECS-entity.
29#[derive(Hash, Eq, PartialEq, Clone, Copy)]
30struct Entity(usize);
31
32/// An ECS-system that draws entities with a `Position` and `Draw` component.
33struct DrawSystem;
34
35impl TengComponent<Ecs> for DrawSystem {
36    fn render(
37        &self,
38        renderer: &mut dyn Renderer,
39        shared_state: &SharedState<Ecs>,
40        depth_base: i32,
41    ) {
42        let ecs = &shared_state.custom;
43        for (position, draw) in ecs.entities.iter().filter_map(|entity| {
44            let position = ecs.get_component::<Position>(*entity)?;
45            let draw = ecs.get_component::<Draw>(*entity)?;
46            Some((position, draw))
47        }) {
48            renderer.render_pixel(position.x, position.y, Pixel::new(draw.ch), depth_base);
49        }
50    }
51}
52
53/// An ECS-system that applies rudimentary physics to entities with a `Position` component.
54struct PhysicsSystem;
55
56impl TengComponent<Ecs> for PhysicsSystem {
57    fn update(&mut self, _update_info: teng::UpdateInfo, shared_state: &mut SharedState<Ecs>) {
58        let ecs = &mut shared_state.custom;
59        for &entity in &ecs.entities {
60            let Some(position) = ecs.components.get_mut_from_entity::<Position>(entity) else {
61                continue;
62            };
63            position.y += 1;
64            if position.y >= shared_state.display_info.height() {
65                position.y = 0;
66            }
67        }
68    }
69}
70
71fn main() -> io::Result<()> {
72    teng::terminal_setup()?;
73    teng::install_panic_handler();
74
75    let mut game = Game::new(stdout());
76    game.install_recommended_components();
77    game.add_component(Box::new(EcsComponent::default()));
78    game.add_component(Box::new(DrawSystem));
79    game.add_component(Box::new(PhysicsSystem));
80    game.run()?;
81
82    teng::terminal_cleanup()?;
83    Ok(())
84}
85
86struct ComponentList {
87    inner: AnyMap,
88}
89
90impl Default for ComponentList {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96impl ComponentList {
97    fn new() -> Self {
98        Self {
99            inner: AnyMap::new(),
100        }
101    }
102
103    fn add_to_entity<T: 'static>(&mut self, entity: Entity, component: T) {
104        let map = self.get_mut::<T>().expect("Component not registered");
105        map.insert(entity, component);
106    }
107
108    fn get_from_entity<T: 'static>(&self, entity: Entity) -> Option<&T> {
109        let map = self.get::<T>()?;
110        map.get(&entity)
111    }
112
113    fn get_mut_from_entity<T: 'static>(&mut self, entity: Entity) -> Option<&mut T> {
114        let map = self.get_mut::<T>()?;
115        map.get_mut(&entity)
116    }
117
118    fn get<T: 'static>(&self) -> Option<&HashMap<Entity, T>> {
119        self.inner.get::<HashMap<Entity, T>>()
120    }
121
122    fn get_mut<T: 'static>(&mut self) -> Option<&mut HashMap<Entity, T>> {
123        self.inner.get_mut::<HashMap<Entity, T>>()
124    }
125
126    fn register<T: 'static>(&mut self) {
127        self.inner.insert::<HashMap<Entity, T>>(HashMap::new());
128    }
129}
130
131/// Shared state for ECS.
132#[derive(Default)]
133struct Ecs {
134    entities: Vec<Entity>,
135    max_key: usize,
136    components: ComponentList,
137}
138
139impl Ecs {
140    fn new() -> Self {
141        Self {
142            entities: Vec::new(),
143            max_key: 0,
144            components: ComponentList::new(),
145        }
146    }
147
148    fn create_entity(&mut self) -> Entity {
149        let entity = Entity(self.max_key);
150        self.entities.push(entity);
151        self.max_key += 1;
152        entity
153    }
154
155    fn add_component<T: 'static>(&mut self, entity: Entity, component: T) {
156        self.components.add_to_entity(entity, component);
157    }
158
159    fn get_component<T: 'static>(&self, entity: Entity) -> Option<&T> {
160        self.components.get_from_entity(entity)
161    }
162
163    fn get_mut_component<T: 'static>(&mut self, entity: Entity) -> Option<&mut T> {
164        self.components.get_mut_from_entity(entity)
165    }
166}
167
168/// A wrapper component that sets up the ECS and creates new entities.
169#[derive(Default)]
170struct EcsComponent {
171    width: usize,
172    height: usize,
173}
174
175impl TengComponent<Ecs> for EcsComponent {
176    fn setup(&mut self, setup_info: &SetupInfo, shared_state: &mut SharedState<Ecs>) {
177        self.width = setup_info.display_info.width();
178        self.height = setup_info.display_info.height();
179
180        let ecs = &mut shared_state.custom;
181        ecs.components.register::<Position>();
182        ecs.components.register::<Draw>();
183    }
184
185    fn on_event(
186        &mut self,
187        event: Event,
188        shared_state: &mut SharedState<Ecs>,
189    ) -> Option<BreakingAction> {
190        let ecs = &mut shared_state.custom;
191        if let Event::Key(KeyEvent {
192            kind: KeyEventKind::Press,
193            code: KeyCode::Char(ch),
194            ..
195        }) = event
196        {
197            // Create a new entity with a random position and the pressed key as display character.
198            let entity = ecs.create_entity();
199            let x = rand::random::<usize>() % self.width;
200            let y = rand::random::<usize>() % self.height;
201            ecs.add_component(entity, Position { x, y });
202            ecs.add_component(entity, Draw { ch });
203        }
204
205        None
206    }
207}