From 50024fa8fa083255e960eb63004ef3a14193be2b Mon Sep 17 00:00:00 2001 From: Alex Bethel Date: Sun, 2 Jan 2022 12:56:42 -0600 Subject: [PATCH] Add ECS stuff --- Cargo.lock | 295 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +- src/components.rs | 23 ++++ src/game.rs | 3 - src/main.rs | 29 ++++- src/systems.rs | 73 ++++++++++++ 6 files changed, 418 insertions(+), 10 deletions(-) create mode 100644 src/components.rs create mode 100644 src/systems.rs diff --git a/Cargo.lock b/Cargo.lock index f168ab7..88ad5e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,29 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atom" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9ff149ed9780025acfdb36862d35b28856bb693ceb451259a7164442f22fdc3" + [[package]] name = "autocfg" version = "1.0.1" @@ -20,15 +43,72 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "dungeon_game" version = "0.1.0" dependencies = [ "float-ord", "grid", + "lazy_static", "pancurses", "pathfinding", "rand", + "specs", + "specs-derive", ] [[package]] @@ -74,6 +154,28 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hibitset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a1bb8316a44459a7d14253c4d28dd7395cbd23cc04a68c46e851b8e46d64b1" +dependencies = [ + "atom", + "rayon", +] [[package]] name = "indexmap" @@ -103,6 +205,12 @@ dependencies = [ "either", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.112" @@ -118,6 +226,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mopa" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" + [[package]] name = "ncurses" version = "5.101.0" @@ -135,6 +264,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -144,6 +283,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + [[package]] name = "pancurses" version = "0.17.0" @@ -193,6 +348,24 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.4" @@ -233,12 +406,134 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "shred" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb0210289d693217926314867c807e0b7b42f7e23c136adb31f8697f5bf242d3" +dependencies = [ + "arrayvec", + "hashbrown", + "mopa", + "rayon", + "smallvec", + "tynm", +] + +[[package]] +name = "shrev" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5752e017e03af9d735b4b069f53b7a7fd90fefafa04d8bd0c25581b0bff437f" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "specs" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcc1e4ba7ab1f08ecb3d7e2f693defc3907e2c03bb0924f9978be45b364f83f" +dependencies = [ + "crossbeam-queue", + "hashbrown", + "hibitset", + "log", + "rayon", + "shred", + "shrev", + "tuple_utils", +] + +[[package]] +name = "specs-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e23e09360f3d2190fec4222cd9e19d3158d5da948c0d1ea362df617dd103511" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tuple_utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1" + +[[package]] +name = "tynm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4df2caa2dc9c3d1f7641ba981f4cd40ab229775aa7aeb834c9ab2850d50623d" +dependencies = [ + "nom", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 9fb3a7a..6e9b039 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +specs = "0.17.0" +specs-derive = "0.4.1" +lazy_static = "1" pancurses = "0.17.0" rand = "0.8.4" grid = "0.6.0" float-ord = "0.3.2" -pathfinding = "3.0.5" +pathfinding = "3" diff --git a/src/components.rs b/src/components.rs new file mode 100644 index 0000000..bdcbdf0 --- /dev/null +++ b/src/components.rs @@ -0,0 +1,23 @@ +//! ECS components. + +use specs::prelude::*; +use specs_derive::Component; + +/// Entities that have a physical position in the world. +#[derive(Component)] +pub struct Position { + pub x: usize, + pub y: usize, +} + +/// Entities that need to be drawn as a single character. +#[derive(Component)] +pub struct CharRender { + pub glyph: char, +} + +/// Registers every existing component with the given ECS world. +pub fn register_all(world: &mut World) { + world.register::(); + world.register::(); +} diff --git a/src/game.rs b/src/game.rs index 6bf40cc..b488d6b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -57,9 +57,6 @@ impl DungeonLevel { win.addch(self.render_tile(x, y)); } } - - // Leave the cursor at the lower-left. - win.mv(0, 0); } /// Renders the tile at the given coordinates. diff --git a/src/main.rs b/src/main.rs index 70cc2e5..c62920d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,36 @@ +use components::{register_all, Position, CharRender}; use game::{BranchConfig, DungeonLevel}; -use pancurses::{endwin, initscr}; +use specs::prelude::*; +use systems::IOSystem; + +mod components; mod game; mod rooms; +mod systems; mod util; fn main() { - let window = initscr(); + let mut world = World::new(); + + register_all(&mut world); let cfg = BranchConfig; let level = DungeonLevel::new(&cfg); - level.draw(&window); - window.refresh(); - window.getch(); + world.insert(level); - endwin(); + world + .create_entity() + .with(Position { x: 5, y: 6 }) + .with(CharRender { glyph: '@' }) + .build(); + + let mut dispatcher = DispatcherBuilder::new() + .with(IOSystem::new(), "render_system", &[]) + .build(); + + loop { + dispatcher.dispatch(&mut world); + } } diff --git a/src/systems.rs b/src/systems.rs new file mode 100644 index 0000000..5f3c6d6 --- /dev/null +++ b/src/systems.rs @@ -0,0 +1,73 @@ +//! ECS systems. + +use std::sync::Mutex; + +use lazy_static::lazy_static; +use pancurses::{Window, endwin, initscr}; +use specs::prelude::*; + +use crate::{components::{Position, CharRender}, game::DungeonLevel}; + +/// System for drawing the state of the game, and potentially waiting +/// (blocking) for user input. +pub struct IOSystem { + window: Window, +} + +lazy_static! { + static ref WINDOW_INITIALIZED: Mutex = Mutex::new(false); +} + +impl IOSystem { + pub fn new() -> Self { + let mut init = WINDOW_INITIALIZED.lock().unwrap(); + if *init { + // See the note on `impl Send for IOSystem`. + panic!("Refusing to initialize the renderer twice"); + } + + *init = true; + Self { window: initscr() } + } +} + +impl Drop for IOSystem { + fn drop(&mut self) { + endwin(); + *WINDOW_INITIALIZED.lock().unwrap() = false; + } +} + +// The `Window` type from pancurses contains a raw pointer, and as a +// result Rust isn't convinced that it's safe to send between threads. +// Since we guarantee that only one `Window` object is ever created at +// a time, it is in fact safe to send the render system's data between +// threads. +unsafe impl Send for IOSystem {} + +impl<'a> System<'a> for IOSystem { + type SystemData = ( + ReadExpect<'a, DungeonLevel>, + ReadStorage<'a, CharRender>, + ReadStorage<'a, Position>, + ); + + fn run(&mut self, (level, renderables, positions): Self::SystemData) { + // Draw the base level. + level.draw(&self.window); + + // Draw all renderable entities in the ECS. + for (render, pos) in (&renderables, &positions).join() { + self.window.mvaddch(pos.y as _, pos.x as _, render.glyph); + } + + // Leave the cursor at the lower-left. + self.window.mv(0, 0); + self.window.refresh(); + + // For now, just get a character to avoid redrawing over and + // over. + self.window.getch(); + } +} +