From a13194be71ec0130cd159d2c81ef5ac18daf3831 Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Fri, 9 Dec 2022 13:00:57 -0500 Subject: [PATCH] basic window management --- Cargo.toml | 10 +++++ build.rs | 45 ++++++++++++++++++++-- examples/basic.rs | 17 +++++---- src/raylib/logging.rs | 87 +++++++++++++++++++++++++++++++++++++++++++ src/raylib/mod.rs | 5 ++- src/raylib/window.rs | 58 +++++++++++++++++++++++++++++ 6 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 src/raylib/logging.rs create mode 100644 src/raylib/window.rs diff --git a/Cargo.toml b/Cargo.toml index 24c0b3e..e71980a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,17 @@ exclude = [ [dependencies] rgb = "^0.8.34" cgmath = "^0.18.0" +cstr = "^0.2.11" +num-traits = "^0.2.14" +printf = "^0.1.0" +log = "^0.4.14" +num-derive = "^0.3.3" +vsprintf = "^2.0.0" +lazy_static = "^1.4.0" [build-dependencies] bindgen = "^0.63.0" cmake = "^0.1.49" + +[dev-dependencies] +env_logger = "^0.10.0" \ No newline at end of file diff --git a/build.rs b/build.rs index d2b810d..a975a1a 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,17 @@ -use std::path::PathBuf; +use std::{path::PathBuf, collections::HashSet}; + +#[derive(Debug)] +struct IgnoreMacros(HashSet); + +impl bindgen::callbacks::ParseCallbacks for IgnoreMacros { + fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior { + if self.0.contains(name) { + bindgen::callbacks::MacroParsingBehavior::Ignore + } else { + bindgen::callbacks::MacroParsingBehavior::Default + } + } +} /// Compiles raylib fn compile_raylib(raylib_path: &str) { @@ -23,9 +36,18 @@ fn compile_raylib(raylib_path: &str) { let destination = cmake_config.build(); // Tell cargo where the libraries might be - println!("cargo:rustc-link-search=native={}", destination.join("lib").display()); - println!("cargo:rustc-link-search=native={}", destination.join("lib32").display()); - println!("cargo:rustc-link-search=native={}", destination.join("lib64").display()); + println!( + "cargo:rustc-link-search=native={}", + destination.join("lib").display() + ); + println!( + "cargo:rustc-link-search=native={}", + destination.join("lib32").display() + ); + println!( + "cargo:rustc-link-search=native={}", + destination.join("lib64").display() + ); } /// Links libraries @@ -62,10 +84,25 @@ fn link_libs() { /// Generates `bindings.rs` file fn generate_bindings(header_file: &str) { + // Tell bindgen to ignore these macros + let ignored_macros = IgnoreMacros( + vec![ + "FP_INFINITE".into(), + "FP_NAN".into(), + "FP_NORMAL".into(), + "FP_SUBNORMAL".into(), + "FP_ZERO".into(), + "IPPORT_RESERVED".into(), + ] + .into_iter() + .collect(), + ); + // Generate the data let bindings = bindgen::Builder::default() .header(header_file) .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .parse_callbacks(Box::new(ignored_macros)) .generate() .expect("Unable to generate bindings"); diff --git a/examples/basic.rs b/examples/basic.rs index 928a983..afd41e2 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,10 +1,14 @@ +use renderkit::raylib::window::Window; + pub fn main() { unsafe { + // Set up logging + env_logger::init(); + // Create a window - renderkit::raylib::ffi::InitWindow( - 800, - 450, - "raylib-ffi example - basic window\0".as_ptr() as *const i8, + let window = Window::new( + cgmath::Vector2::new(800, 450), + "raylib-ffi example - basic window", ); // Render the window @@ -20,7 +24,7 @@ pub fn main() { // Render text and a background renderkit::raylib::ffi::ClearBackground(renderkit::raylib::palette::RAYWHITE.into()); renderkit::raylib::ffi::DrawText( - "Congrats! You created your first window!\0".as_ptr() as *const i8, + cstr::cstr!("Congrats! You created your first window!").as_ptr(), 190, 200, 20, @@ -30,8 +34,5 @@ pub fn main() { // End the draw call renderkit::raylib::ffi::EndDrawing(); } - - // Clean up - renderkit::raylib::ffi::CloseWindow(); } } diff --git a/src/raylib/logging.rs b/src/raylib/logging.rs new file mode 100644 index 0000000..d39c994 --- /dev/null +++ b/src/raylib/logging.rs @@ -0,0 +1,87 @@ +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use vsprintf::vsprintf; + +/// Direct mapping of raylib's log levels +/// See: https://github.com/raysan5/raylib/blob/d875891a3c2621ab40733ca3569eca9e054a6506/parser/raylib_api.json#L985-L1026 +#[derive(FromPrimitive)] +enum RaylibLogLevel { + All = 0, + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Fatal = 6, + None = 7, +} + +#[no_mangle] +unsafe extern "C" fn rl_log_callback( + level: i32, + message: *const i8, + args: *mut super::ffi::__va_list_tag, +) { + // Format the message + let formatted_message = vsprintf(message, args); + + // If all is well, we can continue logging + if let Ok(formatted_message) = formatted_message { + // Convert the string prefix to a target if possible + let mut target = "raylib"; + let final_message; + if formatted_message.starts_with("RLGL: ") { + target = "raylib::rlgl"; + final_message = + std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("RLGL: ")); + } + // else if formatted_message.starts_with("DISPLAY: ") { + // target = "raylib::display"; + // final_message = std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("DISPLAY: ")); + // } + // else if formatted_message.starts_with("GL: ") { + // target = "raylib::gl"; + // final_message = std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("GL: ")); + // } + else if formatted_message.starts_with("GLAD: ") { + target = "raylib::glad"; + final_message = + std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("GLAD: ")); + } else if formatted_message.starts_with("TEXTURE: ") { + target = "raylib::texture"; + final_message = + std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("TEXTURE: ")); + } else if formatted_message.starts_with("SHADER: ") { + target = "raylib::shader"; + final_message = + std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("SHADER: ")); + } else if formatted_message.starts_with("FONT: ") { + target = "raylib::font"; + final_message = + std::borrow::Cow::Borrowed(formatted_message.trim_start_matches("FONT: ")); + } else { + final_message = std::borrow::Cow::Borrowed(&formatted_message); + } + + // Handle the log level and fall back on info! + match RaylibLogLevel::from_u32(level as u32) { + Some(level) => match level { + RaylibLogLevel::Trace => log::trace!(target: target, "{}", final_message), + RaylibLogLevel::Debug => log::debug!(target: target, "{}", final_message), + RaylibLogLevel::Warning => log::warn!(target: target, "{}", final_message), + RaylibLogLevel::Error => log::error!(target: target, "{}", final_message), + RaylibLogLevel::Fatal => log::error!(target: target, "{}", final_message), + _ => log::info!(target: target, "{}", final_message), + }, + None => { + log::info!(target:"raylib", "{}", final_message) + } + } + } +} + +/// Configures raylib to use our custom logging code +pub unsafe fn rl_use_rust_logging() { + log::debug!("Configuring raylib to use the `log` crate"); + super::ffi::SetTraceLogCallback(Some(rl_log_callback)); +} diff --git a/src/raylib/mod.rs b/src/raylib/mod.rs index 47a8cd5..8c7c005 100644 --- a/src/raylib/mod.rs +++ b/src/raylib/mod.rs @@ -2,4 +2,7 @@ pub mod xlat; pub mod ffi; -pub mod palette; \ No newline at end of file +pub mod palette; +pub mod window; + +pub(crate) mod logging; \ No newline at end of file diff --git a/src/raylib/window.rs b/src/raylib/window.rs new file mode 100644 index 0000000..08391e4 --- /dev/null +++ b/src/raylib/window.rs @@ -0,0 +1,58 @@ +use cgmath::Vector2; + +use super::ffi as raylib; +use super::logging::rl_use_rust_logging; + +lazy_static::lazy_static! { + static ref WINDOW_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); +} + +/// A window handle +#[derive(Debug)] +pub struct Window {} + +impl Window { + /// Construct a new `Window` + pub fn new(size: Vector2, title: &str) -> Self { + // Make sure we only have one window + if WINDOW_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) > 0 { + panic!("Only one window is allowed at a time"); + } + + // Perform FFI work + unsafe { + // Hook our rusty logging system into raylib + rl_use_rust_logging(); + + // Set some reasonable defaults + raylib::SetWindowState( + raylib::ConfigFlags_FLAG_VSYNC_HINT + | raylib::ConfigFlags_FLAG_WINDOW_RESIZABLE + | raylib::ConfigFlags_FLAG_MSAA_4X_HINT, + ); + + // Create a window + raylib::InitWindow( + size.x as i32, + size.y as i32, + format!("{}\0", title).as_ptr() as *const i8, + ); + + } + + Self {} + } +} + +impl Drop for Window { + fn drop(&mut self) { + // Perform FFI work + unsafe { + // Clean up + crate::raylib::ffi::CloseWindow(); + } + + // Decrement the window count + WINDOW_COUNT.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + } +}