diff --git a/.gitignore b/.gitignore index fd167442..b0e7628e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# TODO remove this +examples/test_*.rs + # Generated by Cargo # will have compiled files and executables **/target/ diff --git a/Cargo.lock b/Cargo.lock index 6da3d8e1..480a077a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,238 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anymap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + [[package]] name = "bytemuck" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "notan" version = "1.0.0-alpha.0" dependencies = [ "bytemuck", - "notan_sys", + "notan_core", + "notan_macro2", ] [[package]] -name = "notan_sys" +name = "notan_core" version = "1.0.0-alpha.0" +dependencies = [ + "anymap", + "arrayvec", + "indexmap", + "log", + "paste", + "raw-window-handle", + "serde", + "web-sys", +] + +[[package]] +name = "notan_macro2" +version = "1.0.0-alpha.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "web-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml index b2add052..8e40248a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ description = "A simple portable multimedia layer to create apps or games easily [workspace] # TODO set back to "crates/*" after cleanup members = [ - "crates/sys" + "crates/notan_core", + "crates/notan_macro2", ] [workspace.package] @@ -27,8 +28,8 @@ repository = "https://github.com/Nazariglez/notan" edition = "2021" [workspace.dependencies] -notan_sys = { path = "crates/sys" } -#notan_core = { path = "crates/notan_core" } +notan_core = { path = "crates/notan_core" } +notan_macro2 = { path = "crates/notan_macro2" } #notan_input = { path = "crates/notan_input" } #notan_app = { path = "crates/notan_app" } #notan_macro = { path = "crates/notan_macro" } @@ -53,6 +54,9 @@ log = "0.4.20" hashbrown = "0.14.3" parking_lot = "0.12.1" bytemuck = "1.14.0" +anymap = "0.12.1" +arrayvec = "0.7.4" +raw-window-handle = "0.6.0" serde = { version = "1.0", features = ["serde_derive"] } image = { version = "0.24.7", default-features = false, features = ["jpeg", "png", "ico"] } @@ -64,8 +68,8 @@ web-sys = "0.3.66" js-sys = "0.3.65" [dependencies] -notan_sys.workspace = true -#notan_core.workspace = true +notan_core.workspace = true +notan_macro2.workspace = true #notan_input.workspace = true #notan_app.workspace = true #notan_macro.workspace = true diff --git a/crates/notan_core/Cargo.toml b/crates/notan_core/Cargo.toml index d0a0044c..60eae6ad 100644 --- a/crates/notan_core/Cargo.toml +++ b/crates/notan_core/Cargo.toml @@ -10,12 +10,26 @@ readme = "README.md" description = "Basic types and structs used in Notan" [dependencies] +paste = "1.0.14" +indexmap = "2.1.0" + +anymap.workspace = true +raw-window-handle.workspace = true +log.workspace = true + +arrayvec = { workspace = true, optional = true } serde = { workspace = true, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { workspace = true, optional = true } [features] +# Avoid using a vector per event listener, instead uses an Array limited to 32 by default. The number can be changed +# passing `NOTAN_LIMIT_EVENTS_TO=N` with the desired size as compilation variable. +limited_events = ["arrayvec"] +# Allow to open links links = [] +# Allow to load dropped files drop_files = ["web-sys", "web-sys/File"] +# Allow to use native clipboard if available clipboard = [] diff --git a/crates/notan_core/src/builder.rs b/crates/notan_core/src/builder.rs new file mode 100644 index 00000000..12310ad4 --- /dev/null +++ b/crates/notan_core/src/builder.rs @@ -0,0 +1,264 @@ +use crate::config::BuildConfig; +use crate::events::{EventListener, EventMap, EventQueue}; +use crate::handlers::{ + EventHandler, EventHandlerFn, EventHandlerFnOnce, EventHandlerOnce, PluginHandler, + RunnerHandlerFn, SetupHandler, SetupHandlerFn, +}; +use crate::plugin::Plugin; +use crate::runner::default_runner; +use crate::state::AppState; +use crate::storage::{Plugins, Storage}; +use crate::sys::System; +use indexmap::IndexMap; +use std::any::TypeId; +use std::collections::HashMap; + +pub struct AppBuilder { + plugins: Plugins, + runner: Box>, + setup_handler: Box>, + event_handler: EventMap, + late_configs: Option>>>, + event_ids: u64, +} + +impl AppState for () {} + +impl AppBuilder<()> { + pub fn init() -> Self { + Self::init_with(|| Ok(())) + } +} + +impl AppBuilder { + pub fn init_with(handler: H) -> Self + where + H: SetupHandler + 'static, + { + let plugins = Plugins::new(); + let runner = Box::new(default_runner); + let setup_handler: Box> = Box::new(|plugins| handler.call(plugins)); + let event_handler = HashMap::default(); + let late_configs = Some(Default::default()); + + Self { + plugins, + runner, + setup_handler, + event_handler, + late_configs, + event_ids: 0, + } + } + + pub fn add_config(mut self, mut config: C) -> Result + where + C: BuildConfig + 'static, + { + if config.late_evaluation() { + if let Some(late_configs) = &mut self.late_configs { + let typ = TypeId::of::(); + late_configs.insert(typ, Box::new(config)); + } + + return Ok(self); + } + + config.apply(self) + } + + pub fn on(mut self, mut handler: H) -> Self + where + E: 'static, + H: EventHandler + 'static, + { + let k = TypeId::of::(); + let cb: Box> = + Box::new(move |s: &mut Storage, e: &E| handler.call(s, e)); + self.event_handler + .entry(k) + .or_default() + .push(EventListener::Mut(self.event_ids, Box::new(cb))); + self.event_ids += 1; + self + } + + pub fn once(mut self, handler: H) -> Self + where + E: 'static, + H: EventHandlerOnce + 'static, + { + let k = TypeId::of::(); + let cb: Box> = + Box::new(move |s: &mut Storage, e: &E| handler.call(s, e)); + self.event_handler + .entry(k) + .or_default() + .push(EventListener::Once(self.event_ids, Some(Box::new(cb)))); + self.event_ids += 1; + self + } + + pub fn with_runner) -> Result<(), String> + 'static>( + mut self, + runner: F, + ) -> Self { + self.runner = Box::new(runner); + self + } + + pub fn add_plugin(mut self, plugin: T) -> Self { + self.plugins.add(plugin); + self + } + + pub fn add_plugin_with(mut self, handler: H) -> Result + where + T: 'static, + P: Plugin + 'static, + H: PluginHandler + 'static, + { + let plugin = handler.call(&mut self.plugins)?; + Ok(self.add_plugin(plugin)) + } + + pub fn build(mut self) -> Result<(), String> { + if let Some(late_configs) = self.late_configs.take() { + for (_, mut config) in late_configs { + self = config.apply(self)?; + } + } + + let Self { + mut plugins, + mut runner, + setup_handler, + event_handler, + .. + } = self; + + let state = (setup_handler)(&mut plugins)?; + let storage = Storage { + plugins, + state, + events: EventQueue::new(), + }; + + let app = System { + storage, + event_handler, + initialized: false, + in_frame: false, + closed: false, + }; + + (runner)(app)?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::state::AppState; + use crate::storage::FromStorage; + + // helper to set states + macro_rules! app_state { + ($struct_name:ident) => { + impl AppState for $struct_name {} + impl FromStorage<$struct_name> for $struct_name { + fn from_storage<'state>( + storage: &'state mut Storage<$struct_name>, + ) -> &'state mut Self { + &mut storage.state + } + } + }; + } + + #[test] + fn empty_state_default_to_void() { + let res = AppBuilder::init() + .with_runner(|app| { + assert_eq!(app.storage.state, ()); + Ok(()) + }) + .build(); + + assert!(res.is_ok()); + } + + #[test] + fn custom_state() { + #[derive(Debug, Eq, PartialEq)] + struct MyState(u64); + app_state!(MyState); + + let res = AppBuilder::init_with(|| Ok(MyState(9999))) + .with_runner(|app| { + assert_eq!(app.storage.state, MyState(9999)); + Ok(()) + }) + .build(); + + assert!(res.is_ok()); + } + + #[test] + fn once_event_executed_once() { + #[derive(Debug)] + struct MyEvent; + + #[derive(Debug)] + struct MyCount(usize); + app_state!(MyCount); + + let res = AppBuilder::init_with(|| Ok(MyCount(0))) + .once(|_: &MyEvent, count: &mut MyCount| count.0 += 1) + .with_runner(|mut app| { + app.init(); + + for _ in 0..10 { + app.event(MyEvent); + } + + assert_eq!(app.storage.state.0, 1); + Ok(()) + }) + .build(); + + assert!(res.is_ok()); + } + + #[test] + fn on_event_executed_repeat() { + #[derive(Debug)] + struct MyEvent; + + #[derive(Debug)] + struct MyCount(usize); + app_state!(MyCount); + + const COUNT: usize = 10; + + let res = AppBuilder::init_with(|| Ok(MyCount(0))) + .on(|_: &MyEvent, count: &mut MyCount| count.0 += 1) + .with_runner(|mut app| { + app.init(); + + for _ in 0..COUNT { + app.event(MyEvent); + } + + assert_eq!(app.storage.state.0, COUNT); + Ok(()) + }) + .build(); + + assert!(res.is_ok()); + } + + // TODO config and plugin +} diff --git a/crates/notan_core/src/config.rs b/crates/notan_core/src/config.rs new file mode 100644 index 00000000..6e22cf81 --- /dev/null +++ b/crates/notan_core/src/config.rs @@ -0,0 +1,13 @@ +use crate::builder::AppBuilder; +use crate::state::AppState; + +/// Used to set configurations or add plugins to AppBuilder +pub trait BuildConfig { + /// Applies the configuration on the app's builder + fn apply(&mut self, builder: AppBuilder) -> Result, String>; + + /// This will delay the evaluation of `apply` just before the apps start and not when is set + fn late_evaluation(&self) -> bool { + false + } +} diff --git a/crates/notan_core/src/events.rs b/crates/notan_core/src/events.rs index b19a1449..31614219 100644 --- a/crates/notan_core/src/events.rs +++ b/crates/notan_core/src/events.rs @@ -1,142 +1,81 @@ -use std::collections::VecDeque; +use crate::option_usize_env; +use crate::state::AppState; +use crate::sys::System; +use crate::window::WindowId; -#[cfg(feature = "drop_files")] -use std::path::PathBuf; +use std::any::{Any, TypeId}; +use std::collections::{HashMap, VecDeque}; -use crate::keyboard::KeyCode; -use crate::mouse::MouseButton; +#[cfg(feature = "limited_events")] +const MAX_EVENT_LISTENERS: usize = option_usize_env!("NOTAN_LIMIT_EVENTS_TO", 32); -/// Application events usually received from the user -#[derive(Debug, PartialEq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Event { - /// When the app is about to close - Exit, +#[cfg(feature = "limited_events")] +pub(crate) type EventMap = HashMap>; - /// Represents the current window's position after it was moved - WindowMove { x: i32, y: i32 }, +#[cfg(not(feature = "limited_events"))] +pub(crate) type EventMap = HashMap>; - /// Represents the current window's size after it was resized - WindowResize { width: u32, height: u32 }, - - /// Represents a change on the screen aspect ration - ScreenAspectChange { ratio: f64 }, - - /// Represents the current's mouse position after it was moved - MouseMove { x: i32, y: i32 }, - - /// A mouse button is down on this position - MouseDown { button: MouseButton, x: i32, y: i32 }, - - /// A mouse button was released on this position - MouseUp { button: MouseButton, x: i32, y: i32 }, - - /// The mouse wheel was moved with this delta - MouseWheel { delta_x: f32, delta_y: f32 }, - - /// Mouse cursor has entered the window's app - MouseEnter { x: i32, y: i32 }, - - /// Mouse cursor has left the window's app - MouseLeft { x: i32, y: i32 }, - - /// Mouse was moved with this delta - MouseMotion { delta: (f64, f64) }, - - /// Keyboard's key is down - KeyDown { key: KeyCode }, - - /// Keyboard's key was released - KeyUp { key: KeyCode }, - - /// User's touch the screen - TouchStart { id: u64, x: f32, y: f32 }, - - /// User0s move the touch - TouchMove { id: u64, x: f32, y: f32 }, - - /// Touch event ends - TouchEnd { id: u64, x: f32, y: f32 }, - - /// The System cancelled the touch - TouchCancel { id: u64, x: f32, y: f32 }, - - /// Unicode char pressed - ReceivedCharacter(char), - - #[cfg(feature = "drop_files")] - /// The user is dragging a file over the window - DragEnter { - path: Option, - name: Option, - mime: String, - }, - - #[cfg(feature = "drop_files")] - /// The user stops dragging any file over the window - DragLeft, - - #[cfg(feature = "drop_files")] - /// A file was dropped into the window - Drop(DroppedFile), - - /// Text copied to the clipboard - Copy, +pub(crate) enum EventListener { + Once(u64, Option>), + Mut(u64, Box), +} - /// Text cut to the clipboard - Cut, +impl EventListener { + pub(crate) fn is_once(&self) -> bool { + if let Self::Once(_, _) = self { + return true; + } - /// Text pasted from the clipboard - Paste(String), + false + } } -#[cfg(feature = "drop_files")] -#[derive(Default, Debug, PartialEq, Eq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DroppedFile { - pub path: Option, - pub name: String, - pub mime: String, - - #[cfg(target_arch = "wasm32")] - #[cfg_attr(feature = "serde", serde(skip))] - pub file: Option, +/// A list of events pushed by plugins to be processed +#[derive(Default)] +pub(crate) struct EventQueue { + pub(crate) events: VecDeque)>>, } -/// Event iterator queue -#[derive(Debug, Clone, Default)] -pub struct EventIterator(VecDeque); - -impl EventIterator { - pub fn new() -> Self { - Default::default() +impl EventQueue { + pub(crate) fn new() -> Self { + Self { + events: VecDeque::new(), + } } - /// Remove and return the first element on the queue - pub fn pop_front(&mut self) -> Option { - self.0.pop_front() + /// Add a new event to the queue + pub fn queue(&mut self, event: E) { + self.events.push_back(Box::new(move |app| app.event(event))); } - /// Add an event at the end of the list - pub fn push(&mut self, evt: Event) { - self.0.push_back(evt); + /// Take the first event of the queue + pub(crate) fn take_event(&mut self) -> Option)>> { + self.events.pop_front() } +} - /// Add an event at the beginning of the list - pub fn push_front(&mut self, evt: Event) { - self.0.push_front(evt); - } +#[derive(Debug, Copy, Clone)] +pub struct InitEvent; - /// Return the events and clear the list - pub fn take_events(&mut self) -> EventIterator { - EventIterator(std::mem::take(&mut self.0)) - } -} +#[derive(Debug, Copy, Clone)] +pub struct FrameStartEvent; -impl Iterator for EventIterator { - type Item = Event; +#[derive(Debug, Copy, Clone)] +pub struct UpdateEvent; - fn next(&mut self) -> Option { - self.pop_front() - } +#[derive(Debug, Copy, Clone)] +pub struct DrawEvent { + pub window_id: WindowId, + pub width: u32, + pub height: u32, + pub scale_factor: f64, } + +#[derive(Debug, Copy, Clone)] +pub struct FrameEndEvent; + +#[derive(Debug, Copy, Clone)] +pub struct RequestCloseEvent; + +#[derive(Debug, Copy, Clone)] +pub struct CloseEvent; diff --git a/crates/notan_core/src/handlers.rs b/crates/notan_core/src/handlers.rs new file mode 100644 index 00000000..aa8e795a --- /dev/null +++ b/crates/notan_core/src/handlers.rs @@ -0,0 +1,300 @@ +#![allow(unused)] +use crate::plugin::Plugin; +use crate::state::AppState; +use crate::storage::{FromPlugins, FromStorage, Plugins, Storage}; +use crate::sys::System; + +pub(crate) type RunnerHandlerFn = dyn FnMut(System) -> Result<(), String>; +pub(crate) type SetupHandlerFn = dyn FnOnce(&mut Plugins) -> Result; +pub(crate) type PluginHandlerFn

= dyn FnOnce(&mut Plugins) -> Result; +pub(crate) type UpdateHandlerFn = dyn FnMut(&mut Storage); +pub(crate) type EventHandlerFn = dyn FnMut(&mut Storage, &E); +pub(crate) type EventHandlerFnOnce = dyn FnOnce(&mut Storage, &E); + +/// Represent an update's handler +/// It allow to use as parameter the App's State +/// or any App's plugin +pub trait Handler { + fn call(&mut self, app: &mut Storage); +} + +// Safe for notan because the map will never change +// once it's created it will not have new register or removed ones +// Doing this we got interior mutability for the components but not the map +// because is never exposes +macro_rules! fn_handler ({ $($param:ident)* } => { + impl Handler for Fun + where + S: AppState + 'static, + Fun: FnMut($(&mut $param),*), + $($param:FromStorage + 'static),* + { + fn call(&mut self, storage: &mut Storage) { + // Look for duplicated parameters and panic + #[cfg(debug_assertions)] + { + use std::collections::HashSet; + use std::any::TypeId; + let mut h_set:HashSet = Default::default(); + + $( + if !h_set.insert(TypeId::of::<$param>()) { + panic!("Application handlers cannot contains duplicated parameters."); + } + )* + } + + + // Safety. //TODO + paste::paste! { + let ($([<$param:lower _v>],)*) = unsafe { + $(let [<$param:lower _v>] = $param::from_storage(storage) as *mut _;)* + ($(&mut *[<$param:lower _v>],)*) + }; + (self)($([<$param:lower _v>],)*); + } + } + } +}); + +fn_handler! {} +fn_handler! { A } +fn_handler! { A B } +fn_handler! { A B C } +fn_handler! { A B C D } +fn_handler! { A B C D E } +fn_handler! { A B C D E F } +fn_handler! { A B C D E F G } +fn_handler! { A B C D E F G H } +fn_handler! { A B C D E F G H I } +fn_handler! { A B C D E F G H I J } + +/// Represent a setuos's handler +/// It allow to use as parameter any app's plugin +pub trait SetupHandler { + fn call(self, storage: &mut Plugins) -> Result; +} + +// Safe for notan because the map will never change +// once it's created it will not have new register or removed ones +// Doing this we got interior mutability for the components but not the map +// because is never exposes +macro_rules! fn_setup_handler ({ $($param:ident)* } => { + impl SetupHandler for Fun + where + S: AppState + 'static, + Fun: FnOnce($(&mut $param),*) -> Result, + $($param:FromPlugins + 'static),* + { + fn call(mut self, plugins: &mut Plugins) -> Result { + // Look for duplicated parameters and panic + #[cfg(debug_assertions)] + { + use std::collections::HashSet; + use std::any::TypeId; + let mut h_set:HashSet = Default::default(); + + $( + if !h_set.insert(TypeId::of::<$param>()) { + panic!("Application handlers cannot contains duplicated parameters."); + } + )* + } + + + // Safety. //TODO + paste::paste! { + let ($([<$param:lower _v>],)*) = unsafe { + $(let [<$param:lower _v>] = $param::from_plugins(plugins) as *mut _;)* + ($(&mut *[<$param:lower _v>],)*) + }; + return (self)($([<$param:lower _v>],)*); + } + } + } +}); + +fn_setup_handler! {} +fn_setup_handler! { A } +fn_setup_handler! { A B } +fn_setup_handler! { A B C } +fn_setup_handler! { A B C D } +fn_setup_handler! { A B C D E } +fn_setup_handler! { A B C D E F } +fn_setup_handler! { A B C D E F G } +fn_setup_handler! { A B C D E F G H } +fn_setup_handler! { A B C D E F G H I } +fn_setup_handler! { A B C D E F G H I J } + +/// Represent a plugin's handler +/// It allow to use as parameter any app's plugin +pub trait PluginHandler { + fn call(self, storage: &mut Plugins) -> Result; +} + +// Safe for notan because the map will never change +// once it's created it will not have new register or removed ones +// Doing this we got interior mutability for the components but not the map +// because is never exposes +macro_rules! fn_plugin_handler ({ $($param:ident)* } => { + impl PluginHandler for Fun + where + P: Plugin + 'static, + Fun: FnOnce($(&mut $param),*) -> Result, + $($param:FromPlugins + 'static),* + { + fn call(mut self, plugins: &mut Plugins) -> Result { + // Look for duplicated parameters and panic + #[cfg(debug_assertions)] + { + use std::collections::HashSet; + use std::any::TypeId; + let mut h_set:HashSet = Default::default(); + + $( + if !h_set.insert(TypeId::of::<$param>()) { + panic!("Application handlers cannot contains duplicated parameters."); + } + )* + } + + + // Safety. //TODO + paste::paste! { + let ($([<$param:lower _v>],)*) = unsafe { + $(let [<$param:lower _v>] = $param::from_plugins(plugins) as *mut _;)* + ($(&mut *[<$param:lower _v>],)*) + }; + return (self)($([<$param:lower _v>],)*); + } + } + } +}); + +fn_plugin_handler! {} +fn_plugin_handler! { A } +fn_plugin_handler! { A B } +fn_plugin_handler! { A B C } +fn_plugin_handler! { A B C D } +fn_plugin_handler! { A B C D E } +fn_plugin_handler! { A B C D E F } +fn_plugin_handler! { A B C D E F G } +fn_plugin_handler! { A B C D E F G H } +fn_plugin_handler! { A B C D E F G H I } +fn_plugin_handler! { A B C D E F G H I J } + +/// Represent a event's handler +/// It allow to use as parameter the App's State +/// or any App's plugin +pub trait EventHandler { + fn call(&mut self, app: &mut Storage, evt: &Evt); +} + +// Safe for notan because the map will never change +// once it's created it will not have new register or removed ones +// Doing this we got interior mutability for the components but not the map +// because is never exposes +macro_rules! fn_event_handler ({ $($param:ident)* } => { + impl EventHandler for Fun + where + S: AppState + 'static, + Fun: FnMut(&Evt, $(&mut $param),*), + $($param:FromStorage + 'static),* + { + fn call(&mut self, storage: &mut Storage, evt: &Evt) { + // Look for duplicated parameters and panic + #[cfg(debug_assertions)] + { + use std::collections::HashSet; + use std::any::TypeId; + let mut h_set:HashSet = Default::default(); + + $( + if !h_set.insert(TypeId::of::<$param>()) { + panic!("Application handlers cannot contains duplicated parameters."); + } + )* + } + + + // Safety. //TODO + paste::paste! { + let ($([<$param:lower _v>],)*) = unsafe { + $(let [<$param:lower _v>] = $param::from_storage(storage) as *mut _;)* + ($(&mut *[<$param:lower _v>],)*) + }; + (self)(evt, $([<$param:lower _v>],)*); + } + } + } +}); + +fn_event_handler! {} +fn_event_handler! { A } +fn_event_handler! { A B } +fn_event_handler! { A B C } +fn_event_handler! { A B C D } +fn_event_handler! { A B C D E } +fn_event_handler! { A B C D E F } +fn_event_handler! { A B C D E F G } +fn_event_handler! { A B C D E F G H } +fn_event_handler! { A B C D E F G H I } +fn_event_handler! { A B C D E F G H I J } + +/// Represent a event's handler +/// It allow to use as parameter the App's State +/// or any App's plugin +pub trait EventHandlerOnce { + fn call(self, app: &mut Storage, evt: &Evt); +} + +// Safe for notan because the map will never change +// once it's created it will not have new register or removed ones +// Doing this we got interior mutability for the components but not the map +// because is never exposes +macro_rules! fn_event_once_handler ({ $($param:ident)* } => { + impl EventHandlerOnce for Fun + where + S: AppState + 'static, + Fun: FnOnce(&Evt, $(&mut $param),*), + $($param:FromStorage + 'static),* + { + fn call(mut self, storage: &mut Storage, evt: &Evt) { + // Look for duplicated parameters and panic + #[cfg(debug_assertions)] + { + use std::collections::HashSet; + use std::any::TypeId; + let mut h_set:HashSet = Default::default(); + + $( + if !h_set.insert(TypeId::of::<$param>()) { + panic!("Application handlers cannot contains duplicated parameters."); + } + )* + } + + + // Safety. //TODO + paste::paste! { + let ($([<$param:lower _v>],)*) = unsafe { + $(let [<$param:lower _v>] = $param::from_storage(storage) as *mut _;)* + ($(&mut *[<$param:lower _v>],)*) + }; + (self)(evt, $([<$param:lower _v>],)*); + } + } + } +}); + +fn_event_once_handler! {} +fn_event_once_handler! { A } +fn_event_once_handler! { A B } +fn_event_once_handler! { A B C } +fn_event_once_handler! { A B C D } +fn_event_once_handler! { A B C D E } +fn_event_once_handler! { A B C D E F } +fn_event_once_handler! { A B C D E F G } +fn_event_once_handler! { A B C D E F G H } +fn_event_once_handler! { A B C D E F G H I } +fn_event_once_handler! { A B C D E F G H I J } diff --git a/crates/notan_core/src/keyboard.rs b/crates/notan_core/src/keyboard.rs index d5f970b3..a0918de7 100644 --- a/crates/notan_core/src/keyboard.rs +++ b/crates/notan_core/src/keyboard.rs @@ -1,3 +1,16 @@ +use crate::window::WindowId; + +#[derive(Copy, Clone, Debug)] +pub struct KeyboardEvent { + pub window_id: WindowId, + pub action: KeyboardAction, +} + +#[derive(Copy, Clone, Debug)] +pub enum KeyboardAction { + Pressed { key: KeyCode }, + Released { key: KeyCode }, +} /// KeyCode represents the symbolic name of the keyboard keys pressed /// This enum code comes from `winit` just adding the Unknown key for non-compatible keys between platforms #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] diff --git a/crates/notan_core/src/lib.rs b/crates/notan_core/src/lib.rs index 2aa9037a..ed0a0ecd 100644 --- a/crates/notan_core/src/lib.rs +++ b/crates/notan_core/src/lib.rs @@ -1,3 +1,17 @@ +mod builder; +mod config; pub mod events; -pub mod keyboard; -pub mod mouse; +pub mod handlers; +mod keyboard; +mod mouse; +mod plugin; +mod runner; +mod state; +pub mod storage; +mod sys; +mod utils; +mod window; + +pub use builder::AppBuilder; +pub use plugin::Plugin; +pub use state::AppState; diff --git a/crates/notan_core/src/mouse.rs b/crates/notan_core/src/mouse.rs index c6fc82a0..957a4146 100644 --- a/crates/notan_core/src/mouse.rs +++ b/crates/notan_core/src/mouse.rs @@ -1,3 +1,23 @@ +use crate::window::WindowId; + +#[derive(Copy, Clone, Debug)] +pub struct MouseEvent { + pub window_id: WindowId, + pub action: MouseAction, + pub x: f32, + pub y: f32, +} + +#[derive(Copy, Clone, Debug)] +pub enum MouseAction { + Wheel { delta_x: f32, delta_y: f32 }, + ButtonPressed { button: MouseButton }, + ButtonReleased { button: MouseButton }, + Move { relative_x: f32, relative_y: f32 }, + Enter, + Left, +} + /// Represents a button of a mouse #[derive(Clone, Copy, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/notan_core/src/plugin.rs b/crates/notan_core/src/plugin.rs new file mode 100644 index 00000000..7959b2c7 --- /dev/null +++ b/crates/notan_core/src/plugin.rs @@ -0,0 +1,2 @@ +/// Represents an App's plugin +pub trait Plugin {} diff --git a/crates/notan_core/src/runner.rs b/crates/notan_core/src/runner.rs new file mode 100644 index 00000000..3173d303 --- /dev/null +++ b/crates/notan_core/src/runner.rs @@ -0,0 +1,21 @@ +use crate::state::AppState; +use crate::sys::System; + +pub(crate) fn default_runner(mut app: System) -> Result<(), String> { + // Execute initialize callback + app.init(); + + // Frame starts here + app.frame_start(); + + // Execute event's callback + app.update(); + + // Frame ends here + app.frame_end(); + + // Execute close callback + app.close(); + + Ok(()) +} diff --git a/crates/notan_core/src/state.rs b/crates/notan_core/src/state.rs new file mode 100644 index 00000000..13fbc4cb --- /dev/null +++ b/crates/notan_core/src/state.rs @@ -0,0 +1,2 @@ +/// Represents an App's state +pub trait AppState {} diff --git a/crates/notan_core/src/storage.rs b/crates/notan_core/src/storage.rs new file mode 100644 index 00000000..4283d232 --- /dev/null +++ b/crates/notan_core/src/storage.rs @@ -0,0 +1,61 @@ +use crate::events::EventQueue; +use crate::plugin::Plugin; +use crate::state::AppState; +use crate::sys::System; +use anymap::AnyMap; + +pub struct Storage { + pub state: S, + pub plugins: Plugins, + pub events: EventQueue, +} + +impl Storage { + pub fn take_event(&mut self) -> Option)>> { + self.events.take_event() + } +} + +pub(crate) struct Plugins { + map: AnyMap, +} + +impl Plugins { + pub(crate) fn new() -> Self { + Self { map: AnyMap::new() } + } + + pub(crate) fn add(&mut self, plugin: T) { + self.map.insert(plugin); + } + + pub(crate) fn get_mut(&mut self) -> Option<&mut T> { + self.map.get_mut() + } +} + +pub trait FromPlugins { + fn from_plugins(storage: &mut Plugins) -> &mut Self; +} + +impl FromPlugins for T { + fn from_plugins(storage: &mut Plugins) -> &mut Self { + storage.map.get_mut::().unwrap() + } +} + +pub trait FromStorage { + fn from_storage<'state>(app: &'state mut Storage) -> &'state mut Self; +} + +impl FromStorage for T { + fn from_storage(storage: &mut Storage) -> &mut Self { + storage.plugins.map.get_mut::().unwrap() + } +} + +impl FromStorage for EventQueue { + fn from_storage(storage: &mut Storage) -> &mut Self { + &mut storage.events + } +} diff --git a/crates/notan_core/src/sys.rs b/crates/notan_core/src/sys.rs new file mode 100644 index 00000000..6af96396 --- /dev/null +++ b/crates/notan_core/src/sys.rs @@ -0,0 +1,170 @@ +use crate::events::{self, EventListener, EventMap}; +use crate::handlers::{EventHandlerFn, EventHandlerFnOnce}; +use crate::state::AppState; +use crate::storage::Storage; +use crate::window::WindowId; +use std::any::TypeId; + +/// The core of the application, all the systems and backend interacts with it somehow +pub struct System { + pub(crate) storage: Storage, + pub(crate) event_handler: EventMap, + pub(crate) initialized: bool, + pub(crate) in_frame: bool, + pub(crate) closed: bool, +} + +impl System { + /// Allows mutable access to a plugin stored + pub fn get_mut_plugin(&mut self) -> Option<&mut T> { + self.storage.plugins.get_mut() + } + + /// It's called when the backend is ready + /// it dispatched the event `Init` + pub fn init(&mut self) { + if self.initialized { + return; + } + + self.initialized = true; + self.event(events::InitEvent); + } + + pub fn frame_start(&mut self) { + if self.in_frame { + return; + } + + self.in_frame = true; + self.event(events::FrameStartEvent); + } + + pub fn frame_end(&mut self) { + if !self.in_frame { + return; + } + + self.event(events::FrameEndEvent); + self.in_frame = false; + } + + fn exec_event_callback( + &mut self, + evt: &E, + idx: usize, + ) -> Result { + let listener = self + .event_handler + .get_mut(&TypeId::of::()) + .and_then(|list| list.get_mut(idx)) + .ok_or_else(|| { + format!( + "Callback {} for event {:?} cannot be found", + idx, + TypeId::of::() + ) + })?; + + let mut needs_clean = false; + match listener { + EventListener::Once(_, cb_opt) => { + if let Some(cb) = cb_opt.take() { + let cb = cb.downcast::>>(); + if let Ok(cb) = cb { + cb(&mut self.storage, evt); + needs_clean = true; + } + } + } + EventListener::Mut(_, cb) => { + let cb = cb.downcast_mut::>>(); + if let Some(cb) = cb { + cb(&mut self.storage, evt); + } + } + } + execute_queued_events(self); + Ok(needs_clean) + } + + /// Execute any listener set for the event passed in + pub fn event(&mut self, evt: E) { + if !self.initialized { + return; + } + + let len = self + .event_handler + .get(&TypeId::of::()) + .map_or(0, |list| list.len()); + + // There is a bad thing about this event system. There is a double indirection + // because we need to fetch the list of events and then call id per callback + // this is because we cannot get the list and execute the callbacks with a forloop + // due borrow checker issues when pushing events inside event callbacks + + if len != 0 { + // clean once events once all callback has been executed + let mut needs_clean = false; + + for idx in 0..len { + let once = match self.exec_event_callback(&evt, idx) { + Ok(needs_clean) => needs_clean, + Err(err) => { + log::error!("Error with event '{:?}': {}", evt, err); + false + } + }; + + if once { + needs_clean = true; + } + } + + if needs_clean { + if let Some(list) = self.event_handler.get_mut(&TypeId::of::()) { + list.retain(|listener| !listener.is_once()); + } + } + } + } + + /// It's called each frame by the backend and it dispatches + /// the event `Update`. + pub fn update(&mut self) { + let frame_running = self.initialized && self.in_frame; + if !frame_running { + return; + } + + if self.closed { + return; + } + + self.event(events::UpdateEvent); + } + + /// It's called when the backend/app is about to close + /// it dispatched the events `RequestedClose` and `Close` + pub fn close(&mut self) { + if !self.initialized { + return; + } + + if self.closed { + return; + } + + self.event(events::RequestCloseEvent); + self.closed = true; + self.event(events::CloseEvent); + } +} + +#[inline] +fn execute_queued_events(app: &mut System) { + while let Some(cb) = app.storage.take_event() { + cb(app); + } +} diff --git a/crates/notan_core/src/utils.rs b/crates/notan_core/src/utils.rs new file mode 100644 index 00000000..31b6a62e --- /dev/null +++ b/crates/notan_core/src/utils.rs @@ -0,0 +1,30 @@ +#[macro_export] +macro_rules! option_usize_env { + ($s:expr, $d:expr) => { + $crate::utils::parse_string_as_usize(option_env!($s), $d) + }; +} + +pub const fn parse_string_as_usize(key: Option<&'static str>, default: usize) -> usize { + match key { + None => default, // Default value + Some(num) => { + // str.parse::() is not a const fn yet + // this trick will do it for now: + // https://www.reddit.com/r/rust/comments/10ol38k/comment/j6fbjwj/?utm_source=reddit&utm_medium=web2x&context=3 + let mut res: usize = 0; + let mut bytes = num.as_bytes(); + while let [byte, rest @ ..] = bytes { + bytes = rest; + if let b'0'..=b'9' = byte { + res *= 10; + res += (*byte - b'0') as usize; + } else { + // FIXME: log here that the value cannot be parsed? + return default; + } + } + res + } + } +} diff --git a/crates/notan_core/src/window.rs b/crates/notan_core/src/window.rs new file mode 100644 index 00000000..38ee71ef --- /dev/null +++ b/crates/notan_core/src/window.rs @@ -0,0 +1,223 @@ +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + +#[derive(Copy, Clone, Hash, Debug, Eq, PartialEq)] +pub struct WindowId(u64); + +impl From for WindowId { + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: WindowId) -> Self { + value.0 + } +} + +#[derive(Debug, Clone)] +pub struct WindowAttributes { + pub size: Option<(u32, u32)>, + pub min_size: Option<(u32, u32)>, + pub max_size: Option<(u32, u32)>, + pub position: Option<(i32, i32)>, + pub resizable: bool, + pub title: String, + pub fullscreen: bool, + pub maximized: bool, + pub visible: bool, + pub transparent: bool, +} + +impl WindowAttributes { + pub fn with_size(mut self, width: u32, height: u32) -> Self { + self.size = Some((width, height)); + self + } + + pub fn with_min_size(mut self, width: u32, height: u32) -> Self { + self.min_size = Some((width, height)); + self + } + + pub fn with_max_size(mut self, width: u32, height: u32) -> Self { + self.max_size = Some((width, height)); + self + } + + pub fn with_position(mut self, x: i32, y: i32) -> Self { + self.position = Some((x, y)); + self + } + + pub fn with_resizable(mut self, resizable: bool) -> Self { + self.resizable = resizable; + self + } + + pub fn with_title(mut self, title: &str) -> Self { + self.title = title.to_string(); + self + } + + pub fn with_fullscreen(mut self, fullscreen: bool) -> Self { + self.fullscreen = fullscreen; + self + } + + pub fn with_maximized(mut self, maximized: bool) -> Self { + self.maximized = maximized; + self + } + + pub fn with_visible(mut self, visible: bool) -> Self { + self.visible = visible; + self + } + + pub fn with_transparent(mut self, transparent: bool) -> Self { + self.transparent = transparent; + self + } +} + +impl Default for WindowAttributes { + fn default() -> Self { + Self { + size: Some((800, 600)), + min_size: None, + max_size: None, + position: None, + resizable: false, + title: "GameKit Window".to_string(), + fullscreen: false, + maximized: false, + visible: true, + transparent: false, + } + } +} + +pub trait NotanApp { + fn new() -> Self; + fn create(&mut self, attrs: WindowAttributes) -> Result; + fn window(&mut self, id: WindowId) -> Option<&mut W>; + fn close(&mut self, id: WindowId) -> bool; + fn exit(&mut self); +} + +pub trait NotanWindow: HasRawWindowHandle + HasRawDisplayHandle { + fn id(&self) -> WindowId; + fn physical_size(&self) -> (u32, u32); + fn size(&self) -> (u32, u32); + fn width(&self) -> u32; + fn height(&self) -> u32; + fn set_size(&mut self, width: u32, height: u32); + fn scale(&self) -> f64; + fn position(&self) -> Result<(i32, i32), String>; + fn set_position(&mut self, x: i32, y: i32); + fn title(&self) -> &str; + fn set_title(&mut self, title: &str); + fn fullscreen(&self) -> bool; + fn set_fullscreen(&mut self, fullscreen: bool); + fn request_focus(&mut self); + fn has_focus(&self) -> bool; + fn set_cursor_icon(&mut self, cursor: CursorIcon); + fn cursor(&self) -> CursorIcon; + fn set_maximized(&mut self, maximized: bool); + fn maximized(&self) -> bool; + fn set_minimized(&mut self, minimized: bool); + fn minimized(&self) -> bool; + fn set_visible(&mut self, visible: bool); + fn visible(&self) -> bool; + fn set_transparent(&mut self, transparent: bool); + fn transparent(&self) -> bool; + fn set_resizable(&mut self, resizable: bool); + fn resizable(&self) -> bool; + fn set_min_size(&mut self, width: u32, height: u32); + fn min_size(&self) -> Option<(u32, u32)>; + fn set_max_size(&mut self, width: u32, height: u32); + fn max_size(&self) -> Option<(u32, u32)>; + fn request_redraw(&mut self); +} + +/// Window's event +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct WindowEvent { + pub id: WindowId, + pub action: WindowAction, +} + +/// Window's event type +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum WindowAction { + /// A new window was created + Init, + + /// Window's position after it was moved + Moved { x: i32, y: i32 }, + + /// Window's size after it was resized + Resized { + width: u32, + height: u32, + scale_factor: f64, + }, + + /// The window was minimized + Minimized, + + /// The window was maximized + Maximized, + + /// The window did gain the focus + FocusGained, + + /// The window did lost the focus + FocusLost, + + /// The window was closed + Close, +} + +/// Represent mouse cursor icon +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] +pub enum CursorIcon { + Default, + None, + ContextMenu, + Help, + PointingHand, + Progress, + Wait, + Cell, + Crosshair, + Text, + VerticalText, + Alias, + Copy, + Move, + NoDrop, + NotAllowed, + Grab, + Grabbing, + AllScroll, + ResizeHorizontal, + ResizeNeSw, + ResizeNwSe, + ResizeVertical, + ZoomIn, + ZoomOut, + ResizeEast, + ResizeSouthEast, + ResizeSouth, + ResizeSouthWest, + ResizeWest, + ResizeNorthWest, + ResizeNorth, + ResizeNorthEast, + ResizeColumn, + ResizeRow, +} diff --git a/crates/notan_macro2/Cargo.toml b/crates/notan_macro2/Cargo.toml new file mode 100644 index 00000000..6a58df6a --- /dev/null +++ b/crates/notan_macro2/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "notan_macro2" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +readme = "README.md" +description = "Provides a set of utils as macros for Notan" + +[dependencies] +syn = { version = "2.0.17", features = ["full", "extra-traits"] } +proc-macro2 = "1.0.59" +quote = "1.0.28" + +[lib] +proc-macro = true \ No newline at end of file diff --git a/crates/notan_macro2/src/lib.rs b/crates/notan_macro2/src/lib.rs new file mode 100644 index 00000000..def44d60 --- /dev/null +++ b/crates/notan_macro2/src/lib.rs @@ -0,0 +1,9 @@ +use proc_macro::*; + +mod state; + +#[proc_macro_derive(AppState)] +pub fn state_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + state::impl_state_derive(&ast) +} diff --git a/crates/notan_macro2/src/state.rs b/crates/notan_macro2/src/state.rs new file mode 100644 index 00000000..9e02e428 --- /dev/null +++ b/crates/notan_macro2/src/state.rs @@ -0,0 +1,19 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; + +pub(crate) fn impl_state_derive(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let generics = &ast.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let gen = quote! { + impl #impl_generics ::notan::core::AppState for #name #ty_generics #where_clause {} + impl #impl_generics ::notan::core::storage::FromStorage<#name #ty_generics> for #name #ty_generics #where_clause { + fn from_storage<'gk_state>(storage: &'gk_state mut ::notan::core::storage::Storage<#name #ty_generics>) -> &'gk_state mut Self { + &mut storage.state + } + } + }; + gen.into() +} diff --git a/crates/sys/Cargo.toml b/crates/sys/Cargo.toml deleted file mode 100644 index 44a72af9..00000000 --- a/crates/sys/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "notan_sys" -version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -edition.workspace = true - -[dependencies] diff --git a/crates/sys/src/lib.rs b/crates/sys/src/lib.rs deleted file mode 100644 index 7d12d9af..00000000 --- a/crates/sys/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/examples/core_events.rs b/examples/core_events.rs new file mode 100644 index 00000000..3505013e --- /dev/null +++ b/examples/core_events.rs @@ -0,0 +1,40 @@ +use notan::core::events::*; +use notan::prelude::*; + +#[derive(AppState)] +struct State {} + +fn main() -> Result<(), String> { + notan::init_with(|| Ok(State {})) + .once(on_init) + .on(on_start_frame) + .on(on_update) + .on(on_draw) + .on(on_end_frame) + .on(on_close) + .build() +} + +fn on_init(_: &InitEvent, _state: &mut State) { + println!("On Init"); +} + +fn on_start_frame(_: &FrameStartEvent) { + println!("On Start Frame"); +} + +fn on_update(_: &UpdateEvent) { + println!("On Update"); +} + +fn on_draw(_: &FrameEndEvent) { + println!("On Draw"); +} + +fn on_end_frame(_: &FrameEndEvent) { + println!("On End Frame"); +} + +fn on_close(_: &CloseEvent) { + println!("On Close"); +} diff --git a/src/lib.rs b/src/lib.rs index 0a07af71..afda4b63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ -fn test() { - println!("Test"); -} \ No newline at end of file +mod notan; +pub mod prelude; + +pub use notan_core as core; +pub use notan_macro2 as macros; + +pub use notan::*; diff --git a/src/notan.rs b/src/notan.rs index 7ec4d604..33fb59aa 100644 --- a/src/notan.rs +++ b/src/notan.rs @@ -1,3 +1,27 @@ +use crate::core; + +pub fn init() -> core::AppBuilder<()> { + // simple_logger::SimpleLogger::new() + // .without_timestamps() + // .with_level(log::LevelFilter::Debug) + // .init() + // .unwrap(); + core::AppBuilder::init() +} + +pub fn init_with(handler: H) -> core::AppBuilder +where + S: core::AppState + 'static, + H: core::handlers::SetupHandler + 'static, +{ + // simple_logger::SimpleLogger::new() + // .without_timestamps() + // .with_level(log::LevelFilter::Debug) + // .init() + // .unwrap(); + core::AppBuilder::init_with(handler) +} + // use crate::app::{AppBuilder, BackendSystem, SetupHandler}; // // #[cfg(not(feature = "backend"))] diff --git a/src/prelude.rs b/src/prelude.rs index a2596c7f..1f9693ad 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,8 @@ +pub use crate::macros::AppState; + +// TODO prelude +// pub use crate::core::prelude::*; + // pub use crate::app::prelude::*; // pub use crate::graphics::prelude::*; // pub use crate::input::prelude::*;