From 79c8b9ab441493e3b3a37a59263f66896c9c90b3 Mon Sep 17 00:00:00 2001 From: Katharina Fey Date: Sun, 7 Feb 2021 16:44:44 +0100 Subject: [PATCH] rstnode: basic asset loading and prototype sprite rendering * restructure assets directory * implement asset loading and dynamic conversion to sprites * reload sprites with unique URIs to load at runtime * provide an updated renderer API to give access to client state * use new APIs to draw a single node frame on screen * use colour APIs to dynamically change node frame colour --- games/rstnode/Cargo.lock | 92 +++++++++++- games/rstnode/assets/raw/frame/frame_l.png | Bin 0 -> 11582 bytes .../assets/raw/{ => frame}/frame_l.svg | 0 games/rstnode/assets/raw/frame/frame_m.png | Bin 0 -> 9175 bytes .../assets/raw/{ => frame}/frame_m.svg | 0 games/rstnode/assets/raw/frame/frame_s.png | Bin 0 -> 8749 bytes .../assets/raw/{ => frame}/frame_s.svg | 0 games/rstnode/assets/raw/frame/frame_xl.png | Bin 0 -> 13187 bytes .../assets/raw/{ => frame}/frame_xl.svg | 0 games/rstnode/assets/raw/relay/relay1.png | Bin 0 -> 4632 bytes .../rstnode/assets/raw/{ => relay}/relay1.svg | 0 games/rstnode/rst-client/Cargo.toml | 11 +- games/rstnode/rst-client/src/assets.rs | 138 +++++++++++++++++- games/rstnode/rst-client/src/cli.rs | 56 +++++++ games/rstnode/rst-client/src/error.rs | 24 +++ .../rst-client/src/graphics/entities/mod.rs | 18 ++- games/rstnode/rst-client/src/graphics/mod.rs | 18 ++- games/rstnode/rst-client/src/log.rs | 43 ++++++ games/rstnode/rst-client/src/main.rs | 25 +++- games/rstnode/rst-client/src/settings.rs | 7 +- games/rstnode/rst-client/src/state.rs | 20 ++- games/rstnode/rst-client/src/window.rs | 24 ++- 22 files changed, 451 insertions(+), 25 deletions(-) create mode 100644 games/rstnode/assets/raw/frame/frame_l.png rename games/rstnode/assets/raw/{ => frame}/frame_l.svg (100%) create mode 100644 games/rstnode/assets/raw/frame/frame_m.png rename games/rstnode/assets/raw/{ => frame}/frame_m.svg (100%) create mode 100644 games/rstnode/assets/raw/frame/frame_s.png rename games/rstnode/assets/raw/{ => frame}/frame_s.svg (100%) create mode 100644 games/rstnode/assets/raw/frame/frame_xl.png rename games/rstnode/assets/raw/{ => frame}/frame_xl.svg (100%) create mode 100644 games/rstnode/assets/raw/relay/relay1.png rename games/rstnode/assets/raw/{ => relay}/relay1.svg (100%) create mode 100644 games/rstnode/rst-client/src/error.rs create mode 100644 games/rstnode/rst-client/src/log.rs diff --git a/games/rstnode/Cargo.lock b/games/rstnode/Cargo.lock index 60e7e3d404b..81e017cbd20 100644 --- a/games/rstnode/Cargo.lock +++ b/games/rstnode/Cargo.lock @@ -96,6 +96,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "approx" version = "0.3.2" @@ -515,7 +524,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim 0.8.0", @@ -2362,6 +2371,15 @@ dependencies = [ "tendril", ] +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.8" @@ -3548,6 +3566,16 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.22" @@ -3638,6 +3666,10 @@ dependencies = [ "librsvg", "mint", "rst-core", + "svg", + "tempfile", + "tracing", + "tracing-subscriber", ] [[package]] @@ -3846,6 +3878,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "sharded-slab" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +dependencies = [ + "lazy_static", +] + [[package]] name = "shared_library" version = "0.1.9" @@ -4090,6 +4131,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +[[package]] +name = "svg" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbef9cf3cf75dd7772fb1f40dd6d90278a5263454db94ee399500ee9918aaa7" + [[package]] name = "syn" version = "0.15.44" @@ -4329,6 +4376,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fa8f0c8f4c594e4fc9debc1990deab13238077271ba84dd853d54902ee3401" +dependencies = [ + "ansi_term 0.12.1", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + [[package]] name = "ttf-parser" version = "0.6.2" diff --git a/games/rstnode/assets/raw/frame/frame_l.png b/games/rstnode/assets/raw/frame/frame_l.png new file mode 100644 index 0000000000000000000000000000000000000000..f37386dca351365adcc45b9f1d2ed2fc34da21ce GIT binary patch literal 11582 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 literal 0 HcmV?d00001 diff --git a/games/rstnode/assets/raw/frame_l.svg b/games/rstnode/assets/raw/frame/frame_l.svg similarity index 100% rename from games/rstnode/assets/raw/frame_l.svg rename to games/rstnode/assets/raw/frame/frame_l.svg diff --git a/games/rstnode/assets/raw/frame/frame_m.png b/games/rstnode/assets/raw/frame/frame_m.png new file mode 100644 index 0000000000000000000000000000000000000000..02afae96ac202f1415b8eaff16fb8e8640803d53 GIT binary patch literal 9175 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 RhTW+HXsPL|*50>`{2ypg5()qS literal 0 HcmV?d00001 diff --git a/games/rstnode/assets/raw/frame_m.svg b/games/rstnode/assets/raw/frame/frame_m.svg similarity index 100% rename from games/rstnode/assets/raw/frame_m.svg rename to games/rstnode/assets/raw/frame/frame_m.svg diff --git a/games/rstnode/assets/raw/frame/frame_s.png b/games/rstnode/assets/raw/frame/frame_s.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc0133acbcb242483a7b1181c11e591df4fd5b5 GIT binary patch literal 8749 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 E0h#)6Q2+n{ literal 0 HcmV?d00001 diff --git a/games/rstnode/assets/raw/frame_s.svg b/games/rstnode/assets/raw/frame/frame_s.svg similarity index 100% rename from games/rstnode/assets/raw/frame_s.svg rename to games/rstnode/assets/raw/frame/frame_s.svg diff --git a/games/rstnode/assets/raw/frame/frame_xl.png b/games/rstnode/assets/raw/frame/frame_xl.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec3a513d05dd352d01f25893857abaaccb25e82 GIT binary patch literal 13187 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 B@_YaQ literal 0 HcmV?d00001 diff --git a/games/rstnode/assets/raw/frame_xl.svg b/games/rstnode/assets/raw/frame/frame_xl.svg similarity index 100% rename from games/rstnode/assets/raw/frame_xl.svg rename to games/rstnode/assets/raw/frame/frame_xl.svg diff --git a/games/rstnode/assets/raw/relay/relay1.png b/games/rstnode/assets/raw/relay/relay1.png new file mode 100644 index 0000000000000000000000000000000000000000..0c66135130f4c7bfe810364d467a60851c3a394a GIT binary patch literal 4632 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 Cf5q?s literal 0 HcmV?d00001 diff --git a/games/rstnode/assets/raw/relay1.svg b/games/rstnode/assets/raw/relay/relay1.svg similarity index 100% rename from games/rstnode/assets/raw/relay1.svg rename to games/rstnode/assets/raw/relay/relay1.svg diff --git a/games/rstnode/rst-client/Cargo.toml b/games/rstnode/rst-client/Cargo.toml index 929f7550bbe..7b5155f8108 100644 --- a/games/rstnode/rst-client/Cargo.toml +++ b/games/rstnode/rst-client/Cargo.toml @@ -9,8 +9,13 @@ authors = ["Bread Machine", "Katharina Fey "] [dependencies] rst-core = { path = "../rst-core" } -clap = "2.0" +cairo-rs = { version="0.8.0", features=["v1_16", "png", "svg"] } +librsvg = { git = "https://gitlab.gnome.org/GNOME/librsvg.git", rev = "d34f570f" } ggez = "0.6.0-rc0" mint = "0.5" # Required because ggez is trash -librsvg = { git = "https://gitlab.gnome.org/GNOME/librsvg.git", rev = "d34f570f" } -cairo-rs = { version="0.8.0", features=["v1_16", "png", "svg"] } \ No newline at end of file +svg = "0.9" + +clap = "2.0" +tracing = "0.1" +tracing-subscriber = "0.2" +tempfile = "*" \ No newline at end of file diff --git a/games/rstnode/rst-client/src/assets.rs b/games/rstnode/rst-client/src/assets.rs index 7e368b8d4c1..d1ccdaf875e 100644 --- a/games/rstnode/rst-client/src/assets.rs +++ b/games/rstnode/rst-client/src/assets.rs @@ -1,5 +1,19 @@ +use crate::{error::LoadError, GameSettings}; +use cairo::{Context, Format, ImageSurface, Rectangle}; use ggez::graphics::Image; -use std::{collections::BTreeMap, path::Path}; +use librsvg::{CairoRenderer, Loader}; +use std::{ + collections::BTreeMap, + error::Error, + ffi::OsStr, + fs::{read_dir, File}, + io::BufWriter, + io::Read, + path::{Path, PathBuf}, +}; +use tempfile::tempdir; + +pub type Result = std::result::Result; /// Construct a `node` prefixed URI pub fn node(tt: &str) -> URI { @@ -24,21 +38,137 @@ impl From for URI { } /// Asset loader +#[derive(Debug)] pub struct Assets { inner: BTreeMap, } impl Assets { - pub fn load(p: &Path) -> Self { + fn new() -> Self { Self { inner: Default::default(), } } + + pub fn find>(&self, u: U) -> Option { + self.inner.get(&u.into()).map(|i| i.clone()) + } + + /// Load an asset directory path + fn load_tree(&mut self, ctx: &mut ggez::Context, tmpdir: &Path, p: &Path) -> Result<()> { + let err: LoadError = p.to_str().unwrap().into(); + + read_dir(p) + .map_err(|_| err)? + .map(|e| { + let e = e.unwrap(); + let p = e.path(); + + let ext = OsStr::new("svg"); + + if p.is_dir() { + debug!( + "Entering directory {}", + p.file_name().unwrap().to_str().unwrap() + ); + self.load_tree(ctx, tmpdir, p.as_path())?; + } else if p.extension() == Some(ext) { + let png = load_svg(tmpdir, p.as_path())?; + + let basepath = p.with_extension(""); + + let uri_cat = p.parent().unwrap().file_name().unwrap().to_str().unwrap(); + let name = basepath.file_name().unwrap().to_str().unwrap(); + let uri = format!("{}/{}", uri_cat, name); + let path_str = png.as_path().to_str().unwrap(); + + let mut content = vec![]; + let mut f = File::open(png.as_path()).map_err(|_| { + LoadError::from(format!("No such file: {}", path_str).as_str()) + })?; + + f.read_to_end(&mut content).map_err(|e| { + LoadError::from( + format!("Read error for {}: {}", path_str, e.to_string()).as_str(), + ) + })?; + + self.inner.insert( + uri.into(), + Image::from_bytes(ctx, content.as_slice()).map_err(|e| { + LoadError::from( + format!("Read error for {}: {}", path_str, e.to_string()).as_str(), + ) + })?, + ); + } + + Ok(()) + }) + .fold(Ok(()), |acc, res| match (acc, res) { + (Ok(_), Ok(_)) => Ok(()), + (Ok(_), Err(e)) => Err(e), + (Err(e), _) => Err(e), + }) + } } +/// Load all game assets into the game +/// +/// This function performs three main steps. +/// +/// 1. Check that the provided path is a directory +/// 2. Recursively load Directories and files and call +/// [`load_svg`](self::load_svg) on each `.svg` file +/// 3. Re-load newly converted assets into [`Assets`](self::Assets) +pub fn load_tree(ctx: &mut ggez::Context, settings: &GameSettings) -> Result { + let path = match settings.assets.clone() { + Some(s) => Ok(s), + None => Err(LoadError::from("No assets path set!")), + }?; + + debug!( + "Starting assets loading harness on {}", + path.to_str().unwrap() + ); + + let tmpdir = tempdir().unwrap(); + let mut assets = Assets::new(); + assets.load_tree(ctx, tmpdir.path(), path.as_path())?; + info!("Asset loading complete!"); + Ok(assets) +} /// A utility function to take an SVG and render it to a raster image /// according to a render spec -fn load_svg(p: &Path) -> () { - +pub fn load_svg(tmpdir: &Path, p: &Path) -> Result { + let err: LoadError = p.to_str().unwrap().into(); + + let handle = Loader::new().read_path(p).map_err(|_| err.clone())?; + let renderer = CairoRenderer::new(&handle); + + let surf = ImageSurface::create(Format::ARgb32, 256, 256).map_err(|_| err.clone())?; + let cr = Context::new(&surf); + + renderer + .render_document( + &cr, + &Rectangle { + x: 0.0, + y: 0.0, + width: 256.0, + height: 256.0, + }, + ) + .map_err(|_| err.clone())?; + + let png = p.with_extension("png"); + let name = png + .file_name() + .map_or_else(|| Err(err.clone()), |name| Ok(name))?; + + let out = tmpdir.join(name.clone()); + let mut file = BufWriter::new(File::create(out.clone()).map_err(|_| err.clone())?); + surf.write_to_png(&mut file).map_err(|_| err.clone())?; + Ok(out.to_path_buf()) } diff --git a/games/rstnode/rst-client/src/cli.rs b/games/rstnode/rst-client/src/cli.rs index b8a93b237a7..b13697adb29 100644 --- a/games/rstnode/rst-client/src/cli.rs +++ b/games/rstnode/rst-client/src/cli.rs @@ -1 +1,57 @@ //! Handle user CLI inputs + +use crate::{ + constants::{NAME, VERSION}, + settings::WindowMode, + GameSettings, +}; +use clap::{App, Arg}; +use std::path::PathBuf; + +/// Run CLI parser and parse options into GameSettings structure +pub fn parse(settings: &mut GameSettings) { + let app = App::new(NAME) + .version(VERSION) + .author("Bread Machine (Katharina Fey From<&'s str> for LoadError { + fn from(s: &'s str) -> Self { + Self(s.into()) + } +} + +impl Display for LoadError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Error for LoadError {} diff --git a/games/rstnode/rst-client/src/graphics/entities/mod.rs b/games/rstnode/rst-client/src/graphics/entities/mod.rs index d3536d4f5fc..17f26a8e5a7 100644 --- a/games/rstnode/rst-client/src/graphics/entities/mod.rs +++ b/games/rstnode/rst-client/src/graphics/entities/mod.rs @@ -22,20 +22,28 @@ pub struct NodeRndr { pub inner: Arc, } -impl EventHandler for NodeRndr { - fn update(&mut self, _: &mut Context) -> GameResult<()> { +impl Renderer for NodeRndr { + fn update(&mut self, _: &mut ClientState, _: &mut Context) -> GameResult<()> { Ok(()) } - fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { + fn draw(&self, s: &ClientState, ctx: &mut Context) -> GameResult<()> { + let frame = s.assets().find("frame/frame_s").unwrap(); + + frame.draw( + ctx, + DrawParam::new().dest([256.0, 256.0]).color(graphics::RED), + )?; + let circ = Mesh::new_circle( ctx, DrawMode::fill(), Point2::from(&self.loc), - 128.0, + 64.0, 0.1, graphics::WHITE, - ).unwrap(); + ) + .unwrap(); circ.draw(ctx, DrawParam::new()).unwrap(); Ok(()) diff --git a/games/rstnode/rst-client/src/graphics/mod.rs b/games/rstnode/rst-client/src/graphics/mod.rs index 8118207d70a..095e66f1ad1 100644 --- a/games/rstnode/rst-client/src/graphics/mod.rs +++ b/games/rstnode/rst-client/src/graphics/mod.rs @@ -8,9 +8,25 @@ pub mod entities; pub mod ui; +use crate::state::ClientState; +use ggez::{Context, GameResult}; + /// A utility module to include everything required to implement a /// graphics entity pub(self) mod prelude { - pub use ggez::{event::EventHandler, graphics::{self, Drawable, DrawParam, Mesh, DrawMode}, Context, GameResult}; + pub use ggez::{ + event::EventHandler, + graphics::{self, DrawMode, DrawParam, Drawable, Mesh}, + Context, GameResult, + }; pub use mint::Point2; + + pub use super::Renderer; + pub use crate::state::ClientState; +} + +/// A rendering trait which is given graphics context, and game state +pub trait Renderer { + fn update(&mut self, _state: &mut ClientState, _ctx: &mut Context) -> GameResult<()>; + fn draw(&self, _state: &ClientState, _ctx: &mut Context) -> GameResult<()>; } diff --git a/games/rstnode/rst-client/src/log.rs b/games/rstnode/rst-client/src/log.rs new file mode 100644 index 00000000000..f1c346ae343 --- /dev/null +++ b/games/rstnode/rst-client/src/log.rs @@ -0,0 +1,43 @@ +//! Logging specifics + +const BANNER: &'static str = " +██████╗ ███████╗████████╗ ███╗ ██╗ ██████╗ ██████╗ ███████╗ +██╔══██╗██╔════╝╚══██╔══╝ ████╗ ██║██╔═══██╗██╔══██╗██╔════╝ +██████╔╝███████╗ ██║ ██╔██╗ ██║██║ ██║██║ ██║█████╗ +██╔══██╗╚════██║ ██║ ██║╚██╗██║██║ ██║██║ ██║██╔══╝ +██║ ██║███████║ ██║ ██║ ╚████║╚██████╔╝██████╔╝███████╗ +╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝"; + +use tracing_subscriber::{filter::LevelFilter, fmt, EnvFilter}; + +pub(crate) fn initialise() { + let filter = EnvFilter::try_from_env("RST_LOG") + .unwrap_or_default() + .add_directive(LevelFilter::DEBUG.into()) + .add_directive("async_std=error".parse().unwrap()) + .add_directive("gfx_device_gl=error".parse().unwrap()) + .add_directive("ggez=error".parse().unwrap()) + .add_directive("selectors=error".parse().unwrap()) + .add_directive("gilrs=error".parse().unwrap()) + .add_directive("mio=error".parse().unwrap()); + + // Initialise the logger + fmt().with_env_filter(filter).init(); + info!("Initialising..."); + info!("{}", BANNER); + info!("Platform: unknown"); + info!("GPU Driver: unknown"); + info!("Version: {}", crate::constants::VERSION); +} + +#[macro_export] +macro_rules! fatal { + () => { + error!("Unknown failure!"); + std::process::exit(2) + }; + ($($arg:tt)*) => ({ + error!($($arg)*); + std::process::exit(2) + }) +} diff --git a/games/rstnode/rst-client/src/main.rs b/games/rstnode/rst-client/src/main.rs index 57a686a19d7..b86ee341075 100644 --- a/games/rstnode/rst-client/src/main.rs +++ b/games/rstnode/rst-client/src/main.rs @@ -1,10 +1,15 @@ //! RST Node game client +#[macro_use] +extern crate tracing; + mod assets; mod cli; mod constants; mod ctx; +mod error; mod graphics; +mod log; mod settings; mod state; mod window; @@ -14,8 +19,22 @@ pub(crate) use settings::{GameSettings, GraphicsSettings, WindowSettings}; pub(crate) use state::*; fn main() { - let settings = settings::default(); + // Initialise logging mechanism + log::initialise(); + + // Initialise default game settings + let mut settings = settings::default(); + + // Parse commandline arguments + cli::parse(&mut settings); + + // Initialise window context + let mut window = window::create(&settings); + + // Load assets tree + let assets = + assets::load_tree(window.ctx(), &settings).unwrap_or_else(|e| fatal!("LoadError: {}!", e)); + let state = ClientState::new(settings, assets); - let state = ClientState::new(&settings); - window::run(&settings, state) + window.run(state) } diff --git a/games/rstnode/rst-client/src/settings.rs b/games/rstnode/rst-client/src/settings.rs index a339c4106da..cb44eacca3a 100644 --- a/games/rstnode/rst-client/src/settings.rs +++ b/games/rstnode/rst-client/src/settings.rs @@ -1,16 +1,18 @@ //! Configuration structures for the game client use ggez::conf::{FullscreenType, NumSamples}; +use std::path::PathBuf; pub fn default() -> GameSettings { GameSettings { + assets: None, window: WindowSettings { width: 1280, height: 720, window_mode: WindowMode::Windowed, }, graphics: GraphicsSettings { - samples: Samples(16), + samples: Samples(8), vsync: true, }, } @@ -18,6 +20,7 @@ pub fn default() -> GameSettings { /// Complete tree of basic game client settings pub struct GameSettings { + pub assets: Option, pub window: WindowSettings, pub graphics: GraphicsSettings, } @@ -45,7 +48,7 @@ impl<'s> From<&'s Samples> for NumSamples { 2 => Self::Two, 4 => Self::Four, 8 => Self::Eight, - 16 => Self::Sixteen, + // 16 => Self::Sixteen, // currently broken _ => panic!("Invalid multisampling value: {}", s.0), } } diff --git a/games/rstnode/rst-client/src/state.rs b/games/rstnode/rst-client/src/state.rs index c55dc2faa6d..6b7312f13d7 100644 --- a/games/rstnode/rst-client/src/state.rs +++ b/games/rstnode/rst-client/src/state.rs @@ -1,7 +1,11 @@ //! Game client state handling use crate::{ - graphics::entities::{Coordinates, NodeRndr}, + assets::Assets, + graphics::{ + entities::{Coordinates, NodeRndr}, + Renderer, + }, GameSettings, }; use ggez::{event::EventHandler, graphics, Context, GameResult}; @@ -9,12 +13,18 @@ use rst_core::data::{Node, Owner, Upgrade}; use std::sync::Arc; pub struct ClientState { + assets: Assets, + settings: GameSettings, + + // Game state node: NodeRndr, } impl ClientState { - pub fn new(_settings: &GameSettings) -> Self { + pub fn new(settings: GameSettings, assets: Assets) -> Self { Self { + assets, + settings, node: NodeRndr { loc: Coordinates(250.0, 250.0), inner: Arc::new(Node { @@ -30,6 +40,10 @@ impl ClientState { }, } } + + pub fn assets(&self) -> &Assets { + &self.assets + } } impl EventHandler for ClientState { @@ -41,7 +55,7 @@ impl EventHandler for ClientState { graphics::clear(ctx, graphics::Color::from_rgb(15, 15, 15)); // Render the node - self.node.draw(ctx).unwrap(); + self.node.draw(&self, ctx).unwrap(); graphics::present(ctx) } diff --git a/games/rstnode/rst-client/src/window.rs b/games/rstnode/rst-client/src/window.rs index ad58c38e0ff..3dcf375a900 100644 --- a/games/rstnode/rst-client/src/window.rs +++ b/games/rstnode/rst-client/src/window.rs @@ -1,10 +1,28 @@ //! Basic window setup code use crate::{ctx, state::ClientState, GameSettings}; -use ggez::event; +use ggez::{ + event::{self, EventLoop}, + Context, +}; + +pub struct Window { + ctx: Context, + eloop: EventLoop<()>, +} + +impl Window { + pub fn ctx(&mut self) -> &mut Context { + &mut self.ctx + } + + pub fn run(self, state: ClientState) -> ! { + event::run(self.ctx, self.eloop, state) + } +} /// Start the main event loop with game settings and state -pub fn run(settings: &GameSettings, state: ClientState) -> ! { +pub fn create(settings: &GameSettings) -> Window { let (ctx, eloop) = ctx::build(settings).build().unwrap(); - event::run(ctx, eloop, state) + Window { ctx, eloop } }