From 64f3b87c8e046fcce963183fbe98d6c572520cad Mon Sep 17 00:00:00 2001 From: Jeron Aldaron Lau Date: Tue, 30 Jul 2019 01:43:15 -0500 Subject: [PATCH] v0.3.0 --- CHANGELOG.md | 12 +- Cargo.toml | 31 ++- examples/controller/Cargo.toml | 9 + examples/controller/src/main.rs | 15 ++ examples/monitoring/Cargo.toml | 9 + examples/monitoring/src/main.rs | 30 +++ examples/user/Cargo.toml | 9 + examples/user/src/main.rs | 10 + src/internal/mod.rs | 24 ++ src/internal/stick.rs | 408 ++++++++++++++++++++++++++++++++ src/internal/stronghold.rs | 54 +++++ src/internal/wavy.rs | 57 +++++ src/internal/whoami.rs | 91 +++++++ src/iolock.rs | 75 ++++++ src/lib.rs | 218 +++++++++++++++-- src/run.rs | 124 ++++++++++ 16 files changed, 1151 insertions(+), 25 deletions(-) create mode 100644 examples/controller/Cargo.toml create mode 100644 examples/controller/src/main.rs create mode 100644 examples/monitoring/Cargo.toml create mode 100644 examples/monitoring/src/main.rs create mode 100644 examples/user/Cargo.toml create mode 100644 examples/user/src/main.rs create mode 100644 src/internal/mod.rs create mode 100644 src/internal/stick.rs create mode 100644 src/internal/stronghold.rs create mode 100644 src/internal/wavy.rs create mode 100644 src/internal/whoami.rs create mode 100644 src/iolock.rs create mode 100644 src/run.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a9c7127..6deb383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://free.plopgrizzly.com/semver/). ## [Unreleased] +### TODO +- Possibly redesign the controller API to be event based using a message queue. + +## [0.3.0] - 2019-05-23 ### Added +- `controllers()` which returns a `ControllerIter`. +- `Loop` and `loop_init!()`. Together they handle the program's control flow. It also makes it easier in the future to port to Android and other platforms that don't use `main()`. Besides that, it's a nice abstraction that works similarly to Android activities. +- Multi-threaded support. You should now be able to do IO calls from multiple threads. + ### Removed +- `App` type. + ### Changed -### Fixed +- Instead of `App` type everything is in a module, and must be enabled with a feature. This makes it so you don't have to compile the parts of the project you don't need. ## [0.2.1] - 2019-05-13 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index d78127e..8e6b42b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,12 @@ # # Copyright © 2017-2019 Jeron Aldaron Lau. # Dual-licensed under either the MIT License or the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_BSL.txt or copy at https://www.boost.org/LICENSE_1_0.txt, and -# accompanying file LICENSE_MIT.txt or copy at https://opensource.org/licenses/MIT) +# (See accompanying file LICENSE_BSL.txt or copy at https://www.boost.org/LICENSE_1_0.txt +# and accompanying file LICENSE_MIT.txt or copy at https://opensource.org/licenses/MIT) [package] name = "cala" -version = "0.2.1" +version = "0.3.0" authors = ["Jeron Aldaron Lau "] edition = "2018" @@ -21,8 +21,23 @@ keywords = ["device", "platform-agnostic", "cross-platform", "io", "gui"] categories = ["game-engines", "hardware-support", "multimedia", "os"] [dependencies] -whoami = "0.5" # user -wavy = "0.1" # audio -stronghold = "0.2" # file -serde = "1.0" -stick = "0.6" # joystick / controller +whoami = {version = "0.5", optional = true} # user +wavy = {version = "0.1", optional = true} # audio +stronghold = {version = "0.2", optional = true} # file +serde = {version = "1.0", optional = true} +stick = {version = "0.7", optional = true} # joystick / controller +# barg = {path = "../barg", optional = true} + +[package.metadata.docs.rs] +features = ["all"] + +[features] +default = [] +all = ["user", "audio", "files", "controller", +#"gui" +] +user = ["whoami"] +audio = ["wavy"] +files = ["stronghold", "serde"] +controller = ["stick"] +# gui = ["barg"] diff --git a/examples/controller/Cargo.toml b/examples/controller/Cargo.toml new file mode 100644 index 0000000..f40ecdb --- /dev/null +++ b/examples/controller/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "controller" +version = "0.1.0" +authors = ["Jeron Aldaron Lau "] +edition = "2018" + +[dependencies.cala] +features = ["controller"] +path = "../../" diff --git a/examples/controller/src/main.rs b/examples/controller/src/main.rs new file mode 100644 index 0000000..b11b1d5 --- /dev/null +++ b/examples/controller/src/main.rs @@ -0,0 +1,15 @@ +// Set the home loop to `run()`. +cala::loop_init!(run, ()); + +// Function that runs while your app runs. +pub fn run(_: &mut ()) -> cala::Loop<()> { + let layout = cala::ControllerLayout::new().joy(false).lrt(false).abxy(false); + + // Iterate through all of the controllers. + 'a: for (id, state) in cala::controllers(&layout) { + println!("{}: {:?}", id, state.get()); + } + std::thread::sleep(std::time::Duration::from_millis(16)); + // Exit. + cala::Continue +} diff --git a/examples/monitoring/Cargo.toml b/examples/monitoring/Cargo.toml new file mode 100644 index 0000000..8f48ef6 --- /dev/null +++ b/examples/monitoring/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "monitoring" +version = "0.1.0" +authors = ["Jeron Aldaron Lau "] +edition = "2018" + +[dependencies.cala] +features = ["audio"] +path = "../../" diff --git a/examples/monitoring/src/main.rs b/examples/monitoring/src/main.rs new file mode 100644 index 0000000..3a40d90 --- /dev/null +++ b/examples/monitoring/src/main.rs @@ -0,0 +1,30 @@ +use std::collections::VecDeque; + +// The program data context. +struct Data { + buffer: VecDeque<(i16, i16)>, +} + +// Set the home loop to `run()`. +cala::loop_init!(run, Data { + buffer: VecDeque::new(), +}); + +fn run(data: &mut Data) -> cala::Loop { + // Record some sound. + cala::record(&mut |_whichmic, l, r| { + data.buffer.push_back((l, r)); + }); + + // Play that sound. + cala::play(&mut || { + if let Some((lsample, rsample)) = data.buffer.pop_front() { + cala::AudioSample::stereo(lsample, rsample) + } else { + // Play silence if not enough has been recorded yet. + cala::AudioSample::stereo(0, 0) + } + }); + + cala::Continue +} diff --git a/examples/user/Cargo.toml b/examples/user/Cargo.toml new file mode 100644 index 0000000..de06e9d --- /dev/null +++ b/examples/user/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "user" +version = "0.1.0" +authors = ["Jeron Aldaron Lau "] +edition = "2018" + +[dependencies.cala] +features = ["user"] +path = "../../" diff --git a/examples/user/src/main.rs b/examples/user/src/main.rs new file mode 100644 index 0000000..f722614 --- /dev/null +++ b/examples/user/src/main.rs @@ -0,0 +1,10 @@ +// Set the home loop to `run()`. +cala::loop_init!(run, ()); + +// Function that runs while your app runs. +pub fn run(_: &mut ()) -> cala::Loop<()> { + // Print out the user's information. + println!("{}", cala::user()); + // Exit. + cala::Exit +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs new file mode 100644 index 0000000..8ecbc6e --- /dev/null +++ b/src/internal/mod.rs @@ -0,0 +1,24 @@ +static START: std::sync::Once = std::sync::ONCE_INIT; + +// // // // // // + +/// Initialize Cala. +pub fn init() { + START.call_once(|| { + #[cfg(feature = "user")] + { + // Initialize user data. + crate::user::initialize_user_io(); + } + #[cfg(feature = "controller")] + { + // Initialize controller port data. + crate::controller::initialize_controller_io(); + } + #[cfg(feature = "audio")] + { + // Intialize audio interface. + crate::audio::initialize_audio_io(); + } + }); +} diff --git a/src/internal/stick.rs b/src/internal/stick.rs new file mode 100644 index 0000000..01e505e --- /dev/null +++ b/src/internal/stick.rs @@ -0,0 +1,408 @@ +// This is one of the global contexts of Cala. + +pub(crate) use stick::Btn; +pub(crate) use stick::Port as ControllerPort; +pub(crate) use stick::CONTROLLER_MAX as MAX; + +/// The maximum number of controllers allowed. +pub const CONTROLLER_MAX: u8 = MAX as u8; + +pub(crate) enum Axis { + JoyXY, + CamXY, + Lrt, +} + +pub(crate) enum Btns { + Abxy, + Dpad, + Quit, + Menu, + Wz, + Lr, + Dc, +} + +/// Iterator over controllers. Use [`controllers()`](fn.controllers.html) to get. +pub struct ControllerIter<'a> { + id: u8, + i: u8, + layout: &'a ControllerLayout, +} + +/// Get an iterator over controller IDs. **Warning**: They are not guarenteed to be +/// consecutive, even though they usually are. +pub fn controllers(layout: &'_ ControllerLayout) -> ControllerIter<'_> { + ControllerIter { + id: 0, i: 0, + layout, + } +} + +impl<'a> Iterator for ControllerIter<'a> { + type Item = (u8, ControllerState); + + fn next(&mut self) -> Option<(u8, ControllerState)> { + // Search for controller. + loop { + // We need to check if the controller has been unplugged. + if self.id == CONTROLLER_MAX || self.i == controller_count() { + return None; + } + + // Get controller at i, if it exists, otherwise keep looking. + if let Some(state) = controller_get(self.id, &self.layout) { + self.i += 1; + return Some((self.id, state)); + } + self.id += 1; + } + } +} + +/// State of a controller. +pub struct ControllerState { + axis_count: usize, + axis: [f32; 8], + btns_count: usize, + btns: [bool; 16], +} + +impl ControllerState { + /// Return the controller state as a tuple of axis and buttons. + pub fn get(&self) -> (&[f32], &[bool]) { + (&self.axis[..self.axis_count], &self.btns[..self.btns_count]) + } +} + +/// Select which buttons and axis you want on your controller. +/// +/// Button names are from this contoller layout: +/// +/// +pub struct ControllerLayout { + joy: Option, + cam: Option, + lrt: Option, + abxy: Option, + dpad: Option, + quit: Option, + menu: Option, + wz: Option, + cd: Option, + lrb: Option, + axis: Vec, + btns: Vec, +} + +impl Default for ControllerLayout { + fn default() -> Self { + Self::new() + } +} + +impl ControllerLayout { + /// Create a new `ControllerLayout`. + pub fn new() -> Self { + ControllerLayout { + joy: None, + cam: None, + lrt: None, + abxy: None, + dpad: None, + quit: None, + menu: None, + wz: None, + cd: None, + lrb: None, + axis: vec![], + btns: vec![], + } + } + + /// Request an x & y axis for main joystick. + pub fn joy(mut self, optional: bool) -> Self { + // Don't do twice! + if self.joy.is_some() { + return self; + } + + self.joy = Some(optional); + self.axis.push(Axis::JoyXY); + self + } + + /// Request an x & y axis for camera (secondary) joystick. + pub fn cam(mut self, optional: bool) -> Self { + // Don't do twice! + if self.cam.is_some() { + return self; + } + + self.cam = Some(optional); + self.axis.push(Axis::CamXY); + self + } + + /// Request an x & y axis for camera (secondary) joystick. + pub fn lrt(mut self, optional: bool) -> Self { + // Don't do twice! + if self.lrt.is_some() { + return self; + } + + self.cam = Some(optional); + self.axis.push(Axis::Lrt); + self + } + + /// Request ABXY buttons. + pub fn abxy(mut self, optional: bool) -> Self { + if self.abxy.is_some() { + return self; + } + + self.abxy = Some(optional); + self.btns.push(Btns::Abxy); + self + } + + /// Request arrow buttons. + pub fn arrow(mut self, optional: bool) -> Self { + if self.dpad.is_some() { + return self; + } + + self.dpad = Some(optional); + self.btns.push(Btns::Dpad); + self + } + + /// Request Back button. + pub fn back(mut self, optional: bool) -> Self { + if self.quit.is_some() { + return self; + } + + self.quit = Some(optional); + self.btns.push(Btns::Quit); + self + } + + /// Request Menu button. + pub fn menu(mut self, optional: bool) -> Self { + if self.menu.is_some() { + return self; + } + + self.menu = Some(optional); + self.btns.push(Btns::Menu); + self + } + + /// Request W & Z buttons. + pub fn wz(mut self, optional: bool) -> Self { + if self.wz.is_some() { + return self; + } + + self.wz = Some(optional); + self.btns.push(Btns::Wz); + self + } + + /// Request D & C buttons (Push in joystick). + pub fn dc(mut self, optional: bool) -> Self { + if self.cd.is_some() { + return self; + } + + self.cd = Some(optional); + self.btns.push(Btns::Dc); + self + } + + /// Request L & R buttons. + pub fn lrb(mut self, optional: bool) -> Self { + if self.lrb.is_some() { + return self; + } + + self.lrb = Some(optional); + self.btns.push(Btns::Lr); + self + } +} + +static mut PORT: FakePort = FakePort([0; std::mem::size_of::()]); + +// Make aligned to usize. +#[repr(align(8))] +struct FakePort([u8; std::mem::size_of::()]); + +// // // // // // + +pub(crate) fn initialize_controller_io() { + unsafe { + let port = &mut PORT as *mut _ as *mut ControllerPort; + + *port = ControllerPort::new(); + } + + // Start a new thread for getting controller input. + std::thread::spawn(controller_thread); +} + +// The controller thread. +fn controller_thread() { + let port = unsafe { &mut PORT as *mut _ as *mut ControllerPort }; + + loop { + unsafe { (*port).poll(); } + } +} + +/// Return the number of controllers. +pub fn controller_count() -> u8 { + let port = unsafe { &PORT as *const _ as *const ControllerPort }; + + // TODO: Write? + unsafe { (*port).count() } +} + +/// Get the state of a controller from the requested controller layout. +/// +/// This is useful for when you want different controllers to have different layouts. If +/// you want all of your controllers to have the same layout, then you should get a +/// [`ControllerIter`](struct.ControllerIter.html) from +/// [`controllers()`](fn.controllers.html). +/// +/// # Usage +/// ```rust +/// // Set the home loop to `run()`. +/// cala::loop_init!(run, ()); +/// +/// // Function that runs while your app runs. +/// pub fn run(_: &mut ()) -> cala::Loop<()> { +/// let layout = cala::ControllerLayout::new().joy(false).lrt(false).abxy(false); +/// let mut i = 0; +/// +/// // Iterate through all of the controllers. +/// 'a: for id in 0..cala::controller_count() { +/// // Search for controller. +/// 'b: loop { +/// // Get controller at i, if it exists, otherwise keep looking. +/// if let Some(state) = cala::controller_get(i, &layout) { +/// // Found the controller with id = `id`. +/// println!("{}→{}: {:?}", id, i, state.get()); +/// break 'b; +/// } +/// i += 1; +/// // We need to check if the controller has been unplugged. +/// if i == cala::CONTROLLER_MAX { +/// break 'a; +/// } +/// } +/// } +/// std::thread::sleep(std::time::Duration::from_millis(16)); +/// // Exit. +/// cala::Continue +/// } +/// ``` +pub fn controller_get(id: u8, layout: &ControllerLayout) -> Option { + let port = unsafe { &PORT as *const _ as *const ControllerPort }; + + let mut axis: [f32; 8] = [0.0; 8]; + let mut btns: [bool; 16] = [false; 16]; + + let state = unsafe { (*port).get(id) }?; + let mut i_axis = 0; + let mut i_btns = 0; + + for axis_number in 0..layout.axis.len() { + match layout.axis[axis_number] { + Axis::JoyXY => { + // TODO: Fallback. + let (x, y) = state.joy().unwrap_or((0.0, 0.0)); + axis[i_axis] = x; + i_axis += 1; + axis[i_axis] = y; + i_axis += 1; + } + Axis::CamXY => { + // TODO: Fallback. + let (x, y) = state.cam().unwrap_or((0.0, 0.0)); + axis[i_axis] = x; + i_axis += 1; + axis[i_axis] = y; + i_axis += 1; + } + Axis::Lrt => { + // TODO: Fallback. + let (x, y) = state.lrt().unwrap_or((0.0, 0.0)); + axis[i_axis] = x; + i_axis += 1; + axis[i_axis] = y; + i_axis += 1; + } + } + } + + for btn in 0..layout.btns.len() { + match layout.btns[btn] { + Btns::Abxy => { + btns[i_btns] = state.btn(Btn::A).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::B).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::X).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::Y).unwrap_or(false); + i_btns += 1; + } + Btns::Dpad => { + btns[i_btns] = state.btn(Btn::Up).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::Down).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::Left).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::Right).unwrap_or(false); + i_btns += 1; + } + Btns::Quit => { + btns[i_btns] = state.btn(Btn::E).unwrap_or(false); + i_btns += 1; + } + Btns::Menu => { + btns[i_btns] = state.btn(Btn::F).unwrap_or(false); + i_btns += 1; + } + Btns::Wz => { + btns[i_btns] = state.btn(Btn::W).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::Z).unwrap_or(false); + i_btns += 1; + } + Btns::Lr => { + btns[i_btns] = state.btn(Btn::L).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::R).unwrap_or(false); + i_btns += 1; + } + Btns::Dc => { + btns[i_btns] = state.btn(Btn::D).unwrap_or(false); + i_btns += 1; + btns[i_btns] = state.btn(Btn::C).unwrap_or(false); + i_btns += 1; + } + } + } + + Some(ControllerState { + axis, + btns, + axis_count: i_axis, + btns_count: i_btns, + }) +} diff --git a/src/internal/stronghold.rs b/src/internal/stronghold.rs new file mode 100644 index 0000000..cf18ef3 --- /dev/null +++ b/src/internal/stronghold.rs @@ -0,0 +1,54 @@ +/// The Application State. +pub struct App { + // Store + file: T, + changed: bool, +} + +impl App { + /// Create a new Application. + pub fn new(file: T) -> App { + App { + file, + changed: false, + } + } + + /// Get the file data. + pub fn file(&mut self) -> &mut T { + &mut self.file + } + + /// Fetch a resource. + pub fn fetch(&mut self, res: &str) -> Option + where + for<'de> U: serde::Deserialize<'de>, + { + stronghold::fetch(res) + } + + /// Load file `res` from `zip`. + pub fn open(&mut self, zip: &str, res: &str) -> Option<()> + where + for<'de> T: serde::Deserialize<'de>, + { + self.file = stronghold::load(zip, res)?; + Some(()) + } + + /// File data has changed. Next call to `sync()` will save the file. + pub fn edit(&mut self) { + self.changed = true; + } + + /// Save file `res` in `zip` only if `edit()` has been called since last change. + pub fn sync(&mut self, zip: &str, res: &str) + where + T: serde::Serialize, + { + if self.changed { + stronghold::save(zip, res, &self.file); + self.changed = false; + } + } +} diff --git a/src/internal/wavy.rs b/src/internal/wavy.rs new file mode 100644 index 0000000..81a2cd6 --- /dev/null +++ b/src/internal/wavy.rs @@ -0,0 +1,57 @@ +// This is one of the global contexts of Cala. This one is particularly fast because it +// is read only, and multiple threads can read the data at the same time. + +use wavy::MicrophoneSystem; +use wavy::SpeakerSystem; + +pub use wavy::SampleRate; +pub use wavy::AudioSample; + +struct AudioIO { + speaker: std::sync::Mutex, + mic: std::sync::Mutex, +} + +static mut AUD_IO: FakeAudioIO = FakeAudioIO([0; std::mem::size_of::()]); + +#[repr(align(8))] +struct FakeAudioIO([u8; std::mem::size_of::()]); + +// // // // // // + +pub(crate) fn initialize_audio_io() { + use wavy::*; + + unsafe { + let aud_io = &mut AUD_IO as *mut _ as *mut AudioIO; + + std::ptr::write(aud_io, AudioIO { + speaker: std::sync::Mutex::new(SpeakerSystem::new(SampleRate::Normal).unwrap()), + mic: std::sync::Mutex::new(MicrophoneSystem::new(SampleRate::Normal).unwrap()), + }); + } +} + +/// Set the `SampleRate` for playing. +pub fn set_play_hz(sr: SampleRate) { + let aud_io = unsafe { &mut AUD_IO as *mut _ as *mut AudioIO }; + unsafe { *(*aud_io).speaker.lock().unwrap() = SpeakerSystem::new(sr).unwrap(); } +} + +/// Set the `SampleRate` for recording. +pub fn set_record_hz(sr: SampleRate) { + let aud_io = unsafe { &mut AUD_IO as *mut _ as *mut AudioIO }; + unsafe { *(*aud_io).mic.lock().unwrap() = MicrophoneSystem::new(sr).unwrap(); } +} + +/// Play Audio. Callback generates audio samples sent directly to speakers. +pub fn play(callback: &mut FnMut() -> AudioSample) { + let aud_io = unsafe { &mut AUD_IO as *mut _ as *mut AudioIO }; + unsafe { (*aud_io).speaker.lock().unwrap().play(callback) }; +} + +/// Record Audio. Callback's parameters are (microphone ID, left sample, right sample). +pub fn record(callback: &mut FnMut(usize, i16, i16)) { + let aud_io = unsafe { &mut AUD_IO as *mut _ as *mut AudioIO }; + unsafe { (*aud_io).mic.lock().unwrap().record(callback) }; +} diff --git a/src/internal/whoami.rs b/src/internal/whoami.rs new file mode 100644 index 0000000..49194b6 --- /dev/null +++ b/src/internal/whoami.rs @@ -0,0 +1,91 @@ +// This is one of the global contexts of Cala. This one is particularly fast because it +// is read only, and multiple threads can read the data at the same time. + +/// User information. +pub struct User { + /// User's desktop environment. + pub env: &'static str, + /// The user's host machine's name. + pub host: &'static str, + /// The user's host machine's hostname. + pub hostname: &'static str, + /// The user's host machine's operating system. + pub os: &'static str, + /// The user's host machine's platform. + pub platform: &'static str, + /// The user's full name. + pub user: &'static str, + /// The user's username. + pub username: &'static str, +} + +struct UserInfo { + pub env: String, + pub host: String, + pub hostname: String, + pub os: String, + pub platform: String, + pub user: String, + pub username: String, +} + +static mut USER_INFO: FakeUserInfo = FakeUserInfo([0; std::mem::size_of::()]); + +#[repr(align(8))] +struct FakeUserInfo([u8; std::mem::size_of::()]); + +// // // // // // + +pub(crate) fn initialize_user_io() { + use whoami::*; + + unsafe { + let user_info = &mut USER_INFO as *mut _ as *mut UserInfo; + + *user_info = UserInfo { + env: env().to_string(), + host: host(), + hostname: hostname(), + os: os(), + platform: platform().to_string(), + user: whoami::user(), + username: username(), + }; + } +} + +/// Get information about the current user. +pub fn user() -> User { + let userinfo = unsafe { &USER_INFO as *const _ as *const UserInfo }; + + User { + /// User's desktop environment. + env: unsafe { &(*userinfo).env }, + /// The user's host machine's name. + host: unsafe { &(*userinfo).host }, + /// The user's host machine's hostname. + hostname: unsafe { &(*userinfo).hostname }, + /// The user's host machine's operating system. + os: unsafe { &(*userinfo).os }, + /// The user's host machine's platform. + platform: unsafe { &(*userinfo).platform }, + /// The user's full name. + user: unsafe { &(*userinfo).user }, + /// The user's username. + username: unsafe { &(*userinfo).username }, + } +} + +impl std::fmt::Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + writeln!(f, "User {{")?; + writeln!(f, " env: \"{}\"", self.env)?; + writeln!(f, " host: \"{}\"", self.host)?; + writeln!(f, " hostname: \"{}\"", self.hostname)?; + writeln!(f, " os: \"{}\"", self.os)?; + writeln!(f, " platform: \"{}\"", self.platform)?; + writeln!(f, " user: \"{}\"", self.user)?; + writeln!(f, " username: \"{}\"", self.username)?; + write!(f, "}}") + } +} diff --git a/src/iolock.rs b/src/iolock.rs new file mode 100644 index 0000000..73c2fae --- /dev/null +++ b/src/iolock.rs @@ -0,0 +1,75 @@ +use std::sync::atomic::Ordering; + +const STATE_UNLOCKED: usize = 0; +const STATE_LOCKED_W: usize = std::usize::MAX; + +pub(crate) struct ReadIOLock<'a> { + iostate: &'a mut IOLock, +} + +impl<'a> Drop for ReadIOLock<'a> { + fn drop(&mut self) { + // Decrement this thread (Unlock). + self.iostate.state.fetch_sub(1, Ordering::SeqCst); + } +} + +pub(crate) struct WriteIOLock<'a> { + iostate: &'a mut IOLock, +} + +impl<'a> Drop for WriteIOLock<'a> { + fn drop(&mut self) { + // Unlock writer. + self.iostate.state.store(STATE_UNLOCKED, Ordering::SeqCst); + } +} + +/// A global IOLock, that allows uninitialized data. +#[repr(C)] +pub(crate) struct IOLock { + state: std::sync::atomic::AtomicUsize, +} + +impl IOLock { + /// + pub const fn new() -> IOLock { + IOLock { + state: std::sync::atomic::AtomicUsize::new(0), + } + } + + pub fn lock_read<'a>(&'a mut self) -> () { + // Aquire the lock. + loop { + let value = self.state.load(Ordering::SeqCst); + // Writer has locked data. + if value == STATE_LOCKED_W { continue } + // Attempt to lock before another writer lock, now that it's not locked. + if self.state.compare_and_swap(value, value + 1, Ordering::SeqCst) == value { + break; + } + } + } + +// pub fn unlock_read<'a> + + pub fn lock_write<'a>(&'a mut self) -> () { + // Aquire the lock. + loop { + let value = self.state.load(Ordering::SeqCst); + // Writer has locked data. + if value != STATE_UNLOCKED { continue } + // Attempt to lock before another writer lock, now that it's not locked. + if self.state + .compare_and_swap(value, STATE_LOCKED_W, Ordering::SeqCst) == value + { + break; + } + } + +/* WriteIOLock { + iostate: self, + }*/ + } +} diff --git a/src/lib.rs b/src/lib.rs index 607b0d2..6abfb0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,12 @@ //! * Camera - Webcam (NOT IMPLEMENTED YET) //! * Network - Bluetooth & Wifi Direct (NOT IMPLEMENTED YET) //! -//! ## Getting Started +//! # Getting Started //! * TODO +//! +//! ## Features +//! Each hardware interface can be enabled with a feature. By default, all features are +//! enabled. There is a module for each feature (feature and module names match). #![warn(missing_docs)] #![doc( @@ -31,19 +35,201 @@ html_favicon_url = "https://jeronaldaron.github.io/cala/icon.svg" )] -mod audio; -mod controller; -mod dive; -mod user; - -pub use audio::AudioSample; -pub use controller::ControllerLayout; -pub use dive::App; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +mod run; + +#[cfg(feature = "user")] +pub mod user { + //! API for getting user information. Enable with the `user` feature. + //! + //! # Usage + //! ```rust + //! // Set the home loop to `run()`. + //! cala::loop_init!(run, ()); + //! + //! // Function that runs while your app runs. + //! pub fn run(_: &mut ()) -> cala::Loop<()> { + //! // Print out the user's information. + //! println!("{}", cala::user()); + //! // Exit. + //! cala::Exit + //! } + //! ``` + + include!("internal/whoami.rs"); +} + +#[cfg(feature = "controller")] +pub mod controller { + //! API for getting joystick / controller / gamepad input. Enable with the + //! `controller` feature. + //! + //! # Usage + //! ```rust + //! // Set the home loop to `run()`. + //! cala::loop_init!(run, ()); + //! + //! // Function that runs while your app runs. + //! pub fn run(_: &mut ()) -> cala::Loop<()> { + //! let layout = cala::ControllerLayout::new().joy(false).lrt(false).abxy(false); + //! + //! // Iterate through all of the controllers. + //! 'a: for (id, state) in cala::controllers(&layout) { + //! println!("{}: {:?}", id, state.get()); + //! } + //! std::thread::sleep(std::time::Duration::from_millis(16)); + //! // Exit. + //! cala::Continue + //! } + //! ``` + + include!("internal/stick.rs"); +} + +#[cfg(feature = "audio")] +pub mod audio { + //! API for recording / playing audio. Enable with the `audio` feature. + //! + //! # Usage + //! The following example shows how to play audio as it's being recorded. Headphones + //! recommended. + //! + //! ```rust + //! use std::collections::VecDeque; + //! + //! // The program data context. + //! struct Data { + //! buffer: VecDeque<(i16, i16)>, + //! } + //! + //! // Set the home loop to `run()`. + //! cala::loop_init!(run, Data { + //! buffer: VecDeque::new(), + //! }); + //! + //! fn run(data: &mut Data) -> cala::Loop { + //! // Record some sound. + //! cala::record(&mut |_whichmic, l, r| { + //! data.buffer.push_back((l, r)); + //! }); + //! + //! // Play that sound. + //! cala::play(&mut || { + //! if let Some((lsample, rsample)) = data.buffer.pop_front() { + //! cala::AudioSample::stereo(lsample, rsample) + //! } else { + //! // Play silence if not enough has been recorded yet. + //! cala::AudioSample::stereo(0, 0) + //! } + //! }); + //! + //! cala::Continue + //! } + //! ``` + + include!("internal/wavy.rs"); +} + +#[cfg(feature = "files")] +pub mod files { + //! API for loading & saving files. Enable with the `files` feature. + //! + //! # Usage + //! ```rust + //! // TODO + //! ``` + + include!("internal/stronghold.rs"); +} + +// Export all types to root. +pub use run::Loop; + +#[cfg(feature = "user")] +#[doc(hidden)] +pub use user::*; + +#[cfg(feature = "controller")] +#[doc(hidden)] +pub use controller::*; + +#[cfg(feature = "audio")] +#[doc(hidden)] +pub use audio::*; + +#[doc(hidden)] +pub use internal::init; +#[doc(hidden)] +pub use run::Loop::*; + +//mod audio; +// mod dive; +mod internal; +// mod iolock; + +// Others.... +//pub use audio::AudioSample; + +/// Define the entry point for your program. +/// +/// Note that not only is the function an entry point, but also a loop. Usually you'll +/// want to do your initialization in a block of code for the second parameter. You can +/// also do additional intialization inside of the initial loop, and then at the end of +/// the function switch to your main loop. +/// +/// See [`Loop`](enum.Loop.html) for more details. +/// +/// # Usage +/// ```rust +/// // Set the home loop to `run()`. +/// cala::loop_init!(run, ()); +/// +/// // Function that runs while your app runs. +/// pub fn run(_: &mut ()) -> cala::Loop<()> { +/// // Print out the user's information. +/// println!("{}", cala::user()); +/// // Exit. +/// cala::Exit +/// } +/// ``` +#[macro_export] +macro_rules! loop_init { + ($home_loop: expr, $init_data: expr) => { + fn main() { + cala::init(); + + let mut current_loops: Vec cala::Loop<_>> = vec![$home_loop]; + + let mut data = { $init_data }; + + 'a: loop { + match current_loops[current_loops.len() - 1](&mut data) { + cala::Exit => { + break 'a; + } + cala::Continue => { /* do nothing */ } + cala::Back => { + if current_loops.pop().is_none() { + break 'a; + } + } + cala::Replace(loop_a) => { + if current_loops.pop().is_none() { + break 'a; + } + current_loops.push(loop_a); + } + cala::Append(loop_a) => { + current_loops.push(loop_a); + } + cala::ReplaceWithBack(loop_a, loop_b) => { + if current_loops.pop().is_none() { + break 'a; + } + current_loops.push(loop_b); + current_loops.push(loop_a); + } + } + } + } + }; } diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 0000000..0f78705 --- /dev/null +++ b/src/run.rs @@ -0,0 +1,124 @@ +/// Cala program control flow loop. +pub enum Loop { + /// Exit the program. + /// + /// # Usage + /// ``` + /// // Set the home loop to `do_nothing()`. + /// cala::loop_init!(do_nothing, ()); + /// + /// // This program just quits right away. + /// pub fn do_nothing(_: &mut ()) -> cala::Loop<()> { + /// // Exit. + /// cala::Exit + /// } + /// ``` + Exit, + /// Keep the program running. + /// + /// # Usage + /// ``` + /// // Set the home loop to `infinite_loop()`. + /// cala::loop_init!(infinite_loop, ()); + /// + /// // This program runs until the user interupts it. + /// pub fn infinite_loop(_: &mut ()) -> cala::Loop<()> { + /// // Run this function again. + /// cala::Continue + /// } + /// ``` + Continue, + /// Go back one activity. + /// + /// # Usage + /// ``` + /// // Set the home loop to `home()`. + /// cala::loop_init!(home, ()); + /// + /// // This program creates 2 new activities, which both go back right away. + /// pub fn home(_: &mut ()) -> cala::Loop<()> { + /// // `activity_a` is first, because it will be on the top of the activity stack. + /// cala::ReplaceWithBack(activity_a, activity_b) + /// } + /// + /// pub fn activity_a(_: &mut ()) -> cala::Loop<()> { + /// println!("First"); + /// // Go to activity_b + /// cala::Back + /// } + /// + /// pub fn activity_b(_: &mut ()) -> cala::Loop<()> { + /// println!("Last"); + /// // Quit + /// cala::Back + /// } + /// ``` + Back, + /// Add new activities to the activity stack (new activity loop). + /// + /// # Usage + /// ``` + /// // Set the home loop to `home()`. + /// cala::loop_init!(home, ()); + /// + /// // Infinite loop. + /// pub fn home(_: &mut ()) -> cala::Loop<()> { + /// // `activity_a` is first, because it will be on the top of the activity stack. + /// cala::Append(activity_a) + /// } + /// + /// pub fn activity_a(_: &mut ()) -> cala::Loop<()> { + /// // Go to activity `home()` + /// cala::Back + /// } + /// ``` + Append(fn(&mut T) -> Loop), + /// Add a new activity to the activity stack (new activity loop), throwing away the + /// one on top (the current activity). + /// + /// # Usage + /// ``` + /// // Set the home loop to `home()`. + /// cala::loop_init!(home, ()); + /// + /// // This program creates 1 new activity, which quits right away. + /// pub fn home(_: &mut ()) -> cala::Loop<()> { + /// // Replace our `home()` activity with `activity_a()`. + /// cala::Replace(activity_a) + /// } + /// + /// pub fn activity_a(_: &mut ()) -> cala::Loop<()> { + /// // Quit + /// cala::Exit + /// } + /// ``` + Replace(fn(&mut T) -> Loop), + /// Add 2 new activities to the activity stack (new activity loop), throwing away the + /// one on top (the current activity). First parameter is the activity that the user + /// will enter, and the second is the one they will enter when they go back. + /// + /// # Usage + /// ``` + /// // Set the home loop to `home()`. + /// cala::loop_init!(home, ()); + /// + /// // This program creates 2 new activities, which both go back right away. + /// pub fn home(_: &mut ()) -> cala::Loop<()> { + /// // `activity_a` is first, because it will be on the top of the activity stack. + /// cala::ReplaceWithBack(activity_a, activity_b) + /// } + /// + /// pub fn activity_a(_: &mut ()) -> cala::Loop<()> { + /// println!("First"); + /// // Go to activity_b + /// cala::Back + /// } + /// + /// pub fn activity_b(_: &mut ()) -> cala::Loop<()> { + /// println!("Last"); + /// // Quit + /// cala::Back + /// } + /// ``` + ReplaceWithBack(fn(&mut T) -> Loop, fn(&mut T) -> Loop), +}