diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c48021a..9a365e93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ [core] REVIEWED: HighDPI support on macOS retina (#1510) [core] REDESIGNED: GetFileExtension(), includes the .dot [core] REDESIGNED: IsFileExtension(), includes the .dot - [core] REDESIGNED: Compresion API to use sdefl/sinfl libs + [core] REDESIGNED: Compression API to use sdefl/sinfl libs - [rlgl] ADDED: SUPPORT_GL_DETAILS_INFO config flag [rlgl] REMOVED: GenTexture\*() functions (#721) [rlgl] REVIEWED: rlLoadShaderDefault() @@ -118,7 +118,7 @@ Added: Vector2Reflect Added: Vector2LengthSqr Added: Vector2MoveTowards Added: UnloadFontData -Added: LoadFontFromMemmory(ttf) +Added: LoadFontFromMemory(ttf) Added: ColorAlphaBlend Added: GetPixelColor Added: SetPixelColor diff --git a/DECISIONS.md b/DECISIONS.md index 239cb6bc..1785a8fb 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1,7 +1,7 @@ Document where I put all the little design decisions that go into this library. 1. Allocations - A few functiosn in raylib return a buffer that should be deallocated by the user. Previously, we copied this data into a Vector and then freed with libc::free. + A few functions in raylib return a buffer that should be deallocated by the user. Previously, we copied this data into a Vector and then freed with libc::free. If the user had a custom alocator or some other strange linking strategy, this would free an invalid pointer. diff --git a/README.md b/README.md index b406481f..23298ee7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Though this binding tries to stay close to the simple C API, it makes some chang Most development happens over at: https://github.com/raylib-rs/raylib-rs -Versions normally match Raylib's own, with the minor number incremented for any patches (i.e. 5.5.1 for Raylib v5.5). On occassion, if enough breaking changes are made in between Raylib releases, we'll release a 5.6, which is 5.5 but with breaking changes. +Versions normally match Raylib's own, with the minor number incremented for any patches (i.e. 5.5.1 for Raylib v5.5). On occasion, if enough breaking changes are made in between Raylib releases, we'll release a 5.6, which is 5.5 but with breaking changes. # Installation @@ -124,7 +124,7 @@ The `raygui.h` file has to have this ifdef modified to point to where `raylib.h` # Safe Binding characteristics - Resources are automatically cleaned up when they go out of scope (or when `std::mem::drop` is called). This is essentially RAII. This means that "Unload" functions are not exposed (and not necessary unless you obtain a `Weak` resource using make_weak()). - Most of the Raylib API is exposed through `RaylibHandle`, which is for enforcing that Raylib is only initialized once, and for making sure the window is closed properly. RaylibHandle has no size and goes away at compile time. Because of mutability rules, Raylib-rs is thread safe! -- A `RaylibHandle` and `RaylibThread` are obtained through `raylib::init_window(...)` or through the newer `init()` function which will allow you to `build` up some window options before initialization (replaces `set_config_flags`). RaylibThread should not be sent to any other threads, or used in a any syncronization primitives (Mutex, Arc) etc. +- A `RaylibHandle` and `RaylibThread` are obtained through `raylib::init_window(...)` or through the newer `init()` function which will allow you to `build` up some window options before initialization (replaces `set_config_flags`). RaylibThread should not be sent to any other threads, or used in a any synchronization primitives (Mutex, Arc) etc. - Manually closing the window is unnecessary, because `CloseWindow` is automatically called when `RaylibHandle` goes out of scope. - `Model::set_material`, `Material::set_shader`, and `MaterialMap::set_texture` methods were added since one cannot set the fields directly. Also enforces correct ownership semantics. - `Font::from_data`, `Font::set_chars`, and `Font::set_texture` methods were added to create a `Font` from loaded `CharInfo` data. @@ -139,6 +139,7 @@ The `raygui.h` file has to have this ifdef modified to point to where `raylib.h` - In C, `LoadDroppedFiles` returns a pointer to an array of strings owned by raylib. Again, for safety and also ease of use, this binding copies said array into a `Vec` which is returned to the caller. - I've tried to make linking automatic, though I've only tested on Windows 10, Ubuntu, and MacOS 15. Other platforms may have other considerations. - OpenGL 3.3, 2.1, and ES 2.0 may be forced via adding `["opengl_33"]`, `["opengl_21"]` or `["opengl_es_20]` to the `features` array in your Cargo.toml dependency definition. +- DRM build to render graphics in a tty can be enabled by adding `["drm", "opengl_es_20"]` to `features`. Note that `drm` should usually be used together with `opengl_es_20`. # Testing diff --git a/checklist.md b/checklist.md index 58ffbf36..a18d7725 100644 --- a/checklist.md +++ b/checklist.md @@ -47,7 +47,7 @@ RLAPI void DisableEventWaiting(void); // Disable wai // Custom frame control functions // NOTE: Those functions are intended for advance users that want full control over the frame processing -// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timming + PollInputEvents() +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() // To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) RLAPI void PollInputEvents(void); // Register all input events diff --git a/raylib-sys/Cargo.toml b/raylib-sys/Cargo.toml index 80ea9002..5466b90f 100644 --- a/raylib-sys/Cargo.toml +++ b/raylib-sys/Cargo.toml @@ -91,6 +91,8 @@ opengl_es_20 = [] opengl_es_30 = [] sdl = [] wayland = [] +legacy_rpi = [] +drm = [] # extra build profiles: release_with_debug_info = [] diff --git a/raylib-sys/binding/nobuild.h b/raylib-sys/binding/nobuild.h new file mode 100644 index 00000000..a05ceaca --- /dev/null +++ b/raylib-sys/binding/nobuild.h @@ -0,0 +1,3 @@ +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" diff --git a/raylib-sys/build.rs b/raylib-sys/build.rs index 72cf6004..32665b68 100644 --- a/raylib-sys/build.rs +++ b/raylib-sys/build.rs @@ -58,7 +58,7 @@ impl ParseCallbacks for TypeOverrideCallback { DeriveTrait::Debug, DeriveTrait::PartialEqOrPartialOrd, ]; - let overriden_types = [ + let overridden_types = [ "Vector2", "Vector3", "Vector4", @@ -68,7 +68,7 @@ impl ParseCallbacks for TypeOverrideCallback { "Color", ]; - (OK_TRAITS.contains(&derive_trait) && overriden_types.contains(&name)) + (OK_TRAITS.contains(&derive_trait) && overridden_types.contains(&name)) .then_some(ImplementsTrait::Yes) } } @@ -81,6 +81,9 @@ fn build_with_cmake(src_path: &str) { // CMake uses different lib directories on different systems. // I do not know how CMake determines what directory to use, // so we will check a few possibilities and use whichever is present. + if is_directory_empty(src_path) { + panic!("raylib source does not exist in: `raylib-sys/raylib`. Please copy it in"); + } fn join_cmake_lib_directory(path: PathBuf) -> PathBuf { let possible_cmake_lib_directories = ["lib", "lib64", "lib32"]; for lib_directory in &possible_cmake_lib_directories { @@ -169,6 +172,7 @@ fn build_with_cmake(src_path: &str) { } } Platform::Web => conf.define("PLATFORM", "Web"), + Platform::DRM => conf.define("PLATFORM", "DRM"), Platform::RPI => conf.define("PLATFORM", "Raspberry Pi"), Platform::Android => { // get required env variables @@ -256,6 +260,7 @@ fn gen_bindings() { let plat = match platform { Platform::Desktop => "-DPLATFORM_DESKTOP", + Platform::DRM => "-DPLATFORM_DRM", Platform::RPI => "-DPLATFORM_RPI", Platform::Android => "-DPLATFORM_ANDROID", Platform::Web => "-DPLATFORM_WEB", @@ -274,8 +279,17 @@ fn gen_bindings() { .collect(), ); + let header; + #[cfg(feature = "nobuild")] + { + header = "binding/nobuild.h" + } + #[cfg(not(feature = "nobuild"))] + { + header = "binding/binding.h" + } let mut builder = bindgen::Builder::default() - .header("binding/binding.h") + .header(header) .rustified_enum(".+") .derive_partialeq(true) .derive_default(true) @@ -336,7 +350,9 @@ fn gen_utils() { } #[cfg(feature = "nobuild")] -fn link(_platform: Platform, _platform_os: PlatformOS) {} +fn link(_platform: Platform, _platform_os: PlatformOS) { + println!("cargo:rustc-link-lib=dylib=raylib"); +} #[cfg(not(feature = "nobuild"))] fn link(platform: Platform, platform_os: PlatformOS) { @@ -349,7 +365,7 @@ fn link(platform: Platform, platform_os: PlatformOS) { } PlatformOS::Linux => { // X11 linking - #[cfg(all(not(feature = "wayland"), target_os = "android"))] + #[cfg(not(any(feature = "wayland", target_os = "android", feature = "drm")))] { println!("cargo:rustc-link-search=/usr/local/lib"); println!("cargo:rustc-link-lib=X11"); @@ -375,6 +391,10 @@ fn link(platform: Platform, platform_os: PlatformOS) { } if platform == Platform::Web { println!("cargo:rustc-link-lib=glfw"); + } else if platform == Platform::DRM { + println!("cargo:rustc-link-lib=EGL"); + println!("cargo:rustc-link-lib=drm"); + println!("cargo:rustc-link-lib=gbm"); } else if platform == Platform::RPI { println!("cargo:rustc-link-search=/opt/vc/lib"); println!("cargo:rustc-link-lib=bcm_host"); @@ -387,8 +407,17 @@ fn link(platform: Platform, platform_os: PlatformOS) { } fn main() { + let header; + #[cfg(feature = "nobuild")] + { + header = "/usr/include/raylib.h" + } + #[cfg(not(feature = "nobuild"))] + { + header = "binding/binding.h" + } println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=./binding/binding.h"); + println!("cargo:rerun-if-changed={header}"); //for cross compiling on switch arm //https://users.rust-lang.org/t/cross-compiling-arm/96456/10 if std::env::var("CROSS_SYSROOT").is_ok() { @@ -424,19 +453,19 @@ fn main() { let (platform, platform_os) = platform_from_target(&target); let raylib_src = "./raylib"; - if is_directory_empty(raylib_src) { - panic!("raylib source does not exist in: `raylib-sys/raylib`. Please copy it in"); - } build_with_cmake(raylib_src); gen_bindings(); link(platform, platform_os); - #[cfg(feature = "raygui")] - gen_rgui(); + #[cfg(feature = "raygui")] { + gen_rgui(); + } - gen_utils(); + #[cfg(not(feature = "nobuild"))] { + gen_utils(); + } } #[must_use] @@ -449,10 +478,12 @@ fn is_directory_empty(path: &str) -> bool { } fn platform_from_target(target: &str) -> (Platform, PlatformOS) { - let platform = if target.contains("wasm") { - Platform::Web - } else if target.contains("armv7-unknown-linux") { + let platform = if cfg!(feature = "drm") { + Platform::DRM + } else if cfg!(feature = "legacy_rpi") { Platform::RPI + } else if target.contains("wasm") { + Platform::Web } else if target.contains("android") { Platform::Android } else { @@ -483,7 +514,7 @@ fn platform_from_target(target: &str) -> (Platform, PlatformOS) { _ => panic!("Unknown platform {}", uname()), } } - } else if matches!(platform, Platform::RPI | Platform::Android) { + } else if matches!(platform, Platform::DRM | Platform::RPI | Platform::Android) { let un: &str = &uname(); if un == "Linux" { PlatformOS::Linux @@ -514,7 +545,8 @@ enum Platform { Web, Desktop, Android, - RPI, // raspberry pi + DRM, + RPI, // legacy raspberry pi } #[derive(Clone, Copy, Debug, PartialEq)] @@ -527,7 +559,7 @@ enum PlatformOS { } /// Copied from https://github.com/raysan5/raylib/wiki/CMake-Build-Options and https://github.com/raysan5/raylib/blob/master/src/config.h -/// You should be copy pasting into both raylib/raylib-sys `Cargo.toml` and here while keeping it as close as possible to raylibs `config.h`` for easy maintance +/// You should be copy pasting into both raylib/raylib-sys `Cargo.toml` and here while keeping it as close as possible to raylibs `config.h`` for easy maintenance #[rustfmt::skip] fn features_from_env(cmake: &mut Config) { let is_android = cfg!(target_os = "android"); // skip linking to x11 & wayland diff --git a/raylib-sys/raylib b/raylib-sys/raylib index 83587e94..ba046a5d 160000 --- a/raylib-sys/raylib +++ b/raylib-sys/raylib @@ -1 +1 @@ -Subproject commit 83587e94c844c61b455a48d2f8d9ce872ffc92b6 +Subproject commit ba046a5d60fe40ebb815822d4972e1dd3944e3cf diff --git a/raylib-sys/src/color.rs b/raylib-sys/src/color.rs index b41f1b8e..ddc963c9 100644 --- a/raylib-sys/src/color.rs +++ b/raylib-sys/src/color.rs @@ -152,7 +152,7 @@ impl Color { } /// NOTE(IOI_XD): We manually implement PartialEq as of 5.5 to use Raylib's function. It's very unlikely it will ever -/// change or do anything different, but in the ultra rare case that it does, we want to mimick Raylib's behavior. +/// change or do anything different, but in the ultra rare case that it does, we want to mimic Raylib's behavior. impl PartialEq for Color { fn eq(&self, other: &Self) -> bool { return self.is_equal(other); diff --git a/raylib-test/Cargo.toml b/raylib-test/Cargo.toml index ae1d8273..f9191a47 100644 --- a/raylib-test/Cargo.toml +++ b/raylib-test/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/raylib-rs/raylib-rs" [dependencies] raylib = { version = "5.7.0", path = "../raylib" } -raylib_sys = { version = "5.7.0", path = "../raylib-sys" } +raylib-sys = { version = "5.7.0", path = "../raylib-sys" } lazy_static = "1.2.0" colored = "1.9.1" diff --git a/raylib-test/REAMDE.md b/raylib-test/README.md similarity index 100% rename from raylib-test/REAMDE.md rename to raylib-test/README.md diff --git a/raylib-test/src/automation.rs b/raylib-test/src/automation.rs index 792bd39e..171efc1b 100644 --- a/raylib-test/src/automation.rs +++ b/raylib-test/src/automation.rs @@ -33,7 +33,7 @@ pub(crate) mod automation_test { let mut events = aelist.events(); while !rl.window_should_close() { - rl.update_camera(&mut camera, CameraMode::CAMERA_FIRST_PERSON); + camera.update_camera(CameraMode::CAMERA_FIRST_PERSON); if rl.is_key_released(KeyboardKey::KEY_SPACE) { if !is_recording { diff --git a/raylib-test/src/callbacks.rs b/raylib-test/src/callbacks.rs index e796214d..a83e36de 100644 --- a/raylib-test/src/callbacks.rs +++ b/raylib-test/src/callbacks.rs @@ -101,7 +101,7 @@ pub mod callback_tests { let mut handle = TEST_HANDLE.write().unwrap(); let rl = handle.as_mut().unwrap(); { - rl.set_trace_log_callback(custom_callback).unwrap(); + set_trace_log_callback(custom_callback).unwrap(); for _ in 0..5 { let noise = Image::gen_image_white_noise(10, 10, 1.0); let _ = rl.load_texture_from_image(&thread, &noise).unwrap(); @@ -115,10 +115,8 @@ pub mod callback_tests { "\n{}\n", "Setting file data saver callback".bold().underline(), ); - let mut handle = TEST_HANDLE.write().unwrap(); - let rl = handle.as_mut().unwrap(); { - rl.set_save_file_data_callback(custom_save_file_data_callback) + set_save_file_data_callback(custom_save_file_data_callback) .unwrap(); } } @@ -128,10 +126,8 @@ pub mod callback_tests { "\n{}\n", "Setting file text saver callback".bold().underline(), ); - let mut handle = TEST_HANDLE.write().unwrap(); - let rl = handle.as_mut().unwrap(); { - rl.set_save_file_text_callback(custom_save_file_text_callback) + set_save_file_text_callback(custom_save_file_text_callback) .unwrap(); } } @@ -141,10 +137,8 @@ pub mod callback_tests { "\n{}\n", "Setting file data loader callback".bold().underline(), ); - let mut handle = TEST_HANDLE.write().unwrap(); - let rl = handle.as_mut().unwrap(); { - rl.set_load_file_data_callback(custom_read_file_data_callback) + set_load_file_data_callback(custom_read_file_data_callback) .unwrap(); } } diff --git a/raylib-test/src/data.rs b/raylib-test/src/data.rs index a588c836..220ecfa1 100644 --- a/raylib-test/src/data.rs +++ b/raylib-test/src/data.rs @@ -1,14 +1,10 @@ #[cfg(test)] mod data_test { use crate::tests::*; - use colored::Colorize; use raylib::prelude::*; ray_test!(data_test); fn data_test(_: &RaylibThread) { - //let mut handle = TEST_HANDLE.write().unwrap(); - //let rl = handle.as_mut().unwrap(); - export_data_as_code( "The quick brown fox jumped over the lazy dog.".as_bytes(), "./test_out/export_data.txt", @@ -18,10 +14,37 @@ mod data_test { ray_test!(base64); fn base64(_: &RaylibThread) { let encoded = encode_data_base64("This is a test".as_bytes()); - let enc: Vec = encoded.to_vec().iter().map(|f| *f as u8).collect(); - let decoded = decode_data_base64(&enc); - + let enc: Vec = encoded.expect("encode ok").to_vec().iter().map(|f| *f as u8).collect(); + let decoded = decode_data_base64(&enc).expect("decode ok"); let fin = std::str::from_utf8(&decoded).unwrap(); - assert!(fin == "This is a test") + assert_eq!(fin, "This is a test") + } + + ray_test!(base64_encode_and_decode); + fn base64_encode_and_decode(_: &RaylibThread) { + let encoded = encode_data_base64(b"This is a test").expect("encode ok"); + let mut enc = encoded.as_ref(); + if enc.ends_with(&[0]) { + enc = &enc[..enc.len() - 1]; + } + let decoded = decode_data_base64(enc).expect("decode ok"); + assert_eq!(decoded.as_ref(), b"This is a test"); + } + + ray_test!(base64_decode_plain_str); + fn base64_decode_plain_str(_: &RaylibThread) { + //non-trailing null + let str = b"VGhpcyBpcyBhIHRlc3Q="; + let out = decode_data_base64(str).expect("decode plain ok"); + assert_eq!(out.as_ref(), b"This is a test"); + } + + ray_test!(base64_decode_with_trailing_null); + fn base64_decode_with_trailing_null(_: &RaylibThread) { + // trailing null + let mut c_str = b"SGVsbG8sIHdvcmxkIQ==".to_vec(); + c_str.push(0); + let out = decode_data_base64(&c_str).expect("decode c-string ok"); + assert_eq!(out.as_ref(), b"Hello, world!"); } -} +} \ No newline at end of file diff --git a/raylib-test/src/image.rs b/raylib-test/src/image.rs index ab426984..8aa61df0 100644 --- a/raylib-test/src/image.rs +++ b/raylib-test/src/image.rs @@ -32,7 +32,7 @@ mod image_test { d.draw_texture(&tex, 0, 0, Color::WHITE); } - rl.take_screenshot(&thread, &format!("test_image_{}.png", name)); + rl.take_screenshot(&thread, &format!("test_out/test_image_{}.png", name)); { let mut d = rl.begin_drawing(&thread); diff --git a/raylib-test/src/logging.rs b/raylib-test/src/logging.rs index 04a0e6c8..93fdec29 100644 --- a/raylib-test/src/logging.rs +++ b/raylib-test/src/logging.rs @@ -5,10 +5,8 @@ mod test_logging { ray_test!(test_logs); fn test_logs(_: &RaylibThread) { - let mut handle = TEST_HANDLE.write().unwrap(); - let rl = handle.as_mut().unwrap(); - rl.set_trace_log(TraceLogLevel::LOG_ALL); - rl.trace_log(TraceLogLevel::LOG_DEBUG, "This Is From `test_logs`"); - rl.set_trace_log(TraceLogLevel::LOG_INFO); + set_trace_log(TraceLogLevel::LOG_ALL); + trace_log(TraceLogLevel::LOG_DEBUG, "This Is From `test_logs`"); + set_trace_log(TraceLogLevel::LOG_INFO); } } diff --git a/raylib-test/src/misc.rs b/raylib-test/src/misc.rs index 86bf6b09..a4279621 100644 --- a/raylib-test/src/misc.rs +++ b/raylib-test/src/misc.rs @@ -6,8 +6,8 @@ mod core_test { fn test_screenshot(t: &RaylibThread) { let mut handle = TEST_HANDLE.write().unwrap(); let rl = handle.as_mut().unwrap(); - rl.take_screenshot(t, "./screenshot.png"); - assert!(std::path::Path::new("./screenshot.png").exists()); + rl.take_screenshot(t, "test_out/screenshot.png"); + assert!(std::path::Path::new("test_out/screenshot.png").exists()); } ray_test!(test_screendata); diff --git a/raylib-test/src/models.rs b/raylib-test/src/models.rs index b5669dc9..614c920e 100644 --- a/raylib-test/src/models.rs +++ b/raylib-test/src/models.rs @@ -37,7 +37,7 @@ mod model_test { let mesh = unsafe { Mesh::gen_mesh_cube(&thread, 1.0, 1.0, 1.0).make_weak() }; let model = rl.load_model_from_mesh(&thread, mesh).unwrap(); - let zero = Vector3::zero(); + let zero = Vector3::ZERO; let camera = Camera3D::perspective(zero, zero, zero, 10.0); diff --git a/raylib-test/src/tests.rs b/raylib-test/src/tests.rs index e8b8e347..dbb8d0bc 100644 --- a/raylib-test/src/tests.rs +++ b/raylib-test/src/tests.rs @@ -156,7 +156,7 @@ pub fn test_runner(tests: &[&dyn Testable]) { } // take_screenshot takes the last frames screenshot - rl.take_screenshot(&thread, &format!("{}.png", t.name)); + rl.take_screenshot(&thread, &format!("test_out/{}.png", t.name)); { let mut d = rl.begin_drawing(&thread); d.clear_background(Color::WHITE); @@ -180,7 +180,7 @@ pub fn test_runner(tests: &[&dyn Testable]) { (t.test)(&mut d, &thread, &assets); } // take_screenshot takes the last frames screenshot - rl.take_screenshot(&thread, &format!("{}.png", t.name)); + rl.take_screenshot(&thread, &format!("test_out/{}.png", t.name)); { let mut d_ = rl.begin_drawing(&thread); let mut d = d_.begin_mode3D(&camera); diff --git a/raylib-test/src/text.rs b/raylib-test/src/text.rs index a76447d5..13bf655e 100644 --- a/raylib-test/src/text.rs +++ b/raylib-test/src/text.rs @@ -27,7 +27,7 @@ mod text_test { let f = rl .load_font(thread, "resources/alagard.png") .expect("couldn't load font"); - f.export_as_code("test_out/font.h"); + let _ = f.export_font_as_code("test_out/font.h"); } ray_draw_test!(test_default_font); diff --git a/raylib-test/src/window.rs b/raylib-test/src/window.rs index 106626bf..4dc38a73 100644 --- a/raylib-test/src/window.rs +++ b/raylib-test/src/window.rs @@ -20,14 +20,14 @@ mod core_test { let handle = TEST_HANDLE.read().unwrap(); let rl = handle.as_ref().unwrap(); let c = Camera::orthographic( - Vector3::zero(), + Vector3::ZERO, Vector3::new(0.0, 0.0, 1.0), - Vector3::up(), + Vector3::Y, 90.0, ); - let _ = rl.get_mouse_ray(Vector2::zero(), &c); + let _ = rl.get_screen_to_world_ray(Vector2::ZERO, &c); // Should be the middle of the screen - let _ = rl.get_world_to_screen(Vector3::zero(), &c); + let _ = rl.get_world_to_screen(Vector3::ZERO, &c); } #[test] diff --git a/raylib/Cargo.toml b/raylib/Cargo.toml index e90c5c09..1df47138 100644 --- a/raylib/Cargo.toml +++ b/raylib/Cargo.toml @@ -51,6 +51,8 @@ opengl_es_30 = ["raylib-sys/opengl_es_30"] opengl_es_20 = ["raylib-sys/opengl_es_20"] sdl = ["raylib-sys/sdl"] wayland = ["raylib-sys/wayland"] +legacy_rpi = ["raylib-sys/legacy_rpi"] +drm = ["raylib-sys/drm"] # extra build profiles: release_with_debug_info = ["raylib-sys/release_with_debug_info"] diff --git a/raylib/src/consts.rs b/raylib/src/consts.rs index b3256875..d2695241 100644 --- a/raylib/src/consts.rs +++ b/raylib/src/consts.rs @@ -23,25 +23,29 @@ pub use ffi::DEG2RAD; // TODO Fix when rlgl bindings are in pub const MAX_MATERIAL_MAPS: u32 = 12; pub const MAX_SHADER_LOCATIONS: u32 = 32; -pub use ffi::GuiCheckBoxProperty; -pub use ffi::GuiColorPickerProperty; -pub use ffi::GuiComboBoxProperty; -pub use ffi::GuiControl; -pub use ffi::GuiControlProperty; -pub use ffi::GuiDefaultProperty; -pub use ffi::GuiDropdownBoxProperty; -pub use ffi::GuiIconName; -pub use ffi::GuiListViewProperty; -pub use ffi::GuiProgressBarProperty; -pub use ffi::GuiScrollBarProperty; -pub use ffi::GuiSliderProperty; -pub use ffi::GuiState; -pub use ffi::GuiTextAlignment; -pub use ffi::GuiTextAlignmentVertical; -pub use ffi::GuiTextBoxProperty; -pub use ffi::GuiTextWrapMode; -pub use ffi::GuiToggleProperty; -pub use ffi::GuiValueBoxProperty; + +#[cfg(not(feature = "nobuild"))] +pub use ffi::{ + GuiCheckBoxProperty, + GuiColorPickerProperty, + GuiComboBoxProperty, + GuiControl, + GuiControlProperty, + GuiDefaultProperty, + GuiDropdownBoxProperty, + GuiIconName, + GuiListViewProperty, + GuiProgressBarProperty, + GuiScrollBarProperty, + GuiSliderProperty, + GuiState, + GuiTextAlignment, + GuiTextAlignmentVertical, + GuiTextBoxProperty, + GuiTextWrapMode, + GuiToggleProperty, + GuiValueBoxProperty, +}; pub use ffi::MouseCursor; pub use ffi::PI; pub use ffi::RAD2DEG; diff --git a/raylib/src/core/audio.rs b/raylib/src/core/audio.rs index 6bd54017..97151706 100644 --- a/raylib/src/core/audio.rs +++ b/raylib/src/core/audio.rs @@ -1,7 +1,7 @@ //! Contains code related to audio. [`RaylibAudio`] plays sounds and music. use crate::{ - error::{AudioInitError, LoadSoundError}, + error::{AudioInitError, LoadSoundError, UpdateAudioStreamError}, ffi, }; use std::ffi::{CStr, CString}; @@ -301,7 +301,7 @@ impl<'aud> Wave<'aud> { /// Copies a wave to a new wave. #[inline] #[must_use] - pub(crate) fn copy(&self) -> Wave { + pub(crate) fn copy(&'_ self) -> Wave<'_> { unsafe { Wave(ffi::WaveCopy(self.0), self.1) } } @@ -413,18 +413,44 @@ impl<'aud> Sound<'aud> { unsafe { ffi::SetSoundPan(self.0, pan) } } - // Uncomment this when Raylib fulfills the todo comment within the original function to make the function safe. - // /// Updates sound buffer with new data. - // #[inline] - // pub fn update(&mut self, data: &[T]) { - // unsafe { - // ffi::UpdateSound( - // self.0, - // data.as_ptr() as *const std::os::raw::c_void, - // (data.len() * std::mem::size_of::()) as i32, - // ); - // } - // }} + /// Updates sound buffer with new data. + /// **Notes** (iann): + /// 1. raylib’s `UpdateSound` is a raw `memcpy` without size checks, we add safety checks to here to prevent invalid memory writes. + /// - potential upstream raylib discussion: "too many frames" doesn't exist for the `Sound`'s `AudioStream` + /// - potential upstream raylib discussion: adding sampleSize checks for the `memcpy` + /// 2. raylib's `Sound`'s `AudioStream` always gets 32-bit sample size (so we always catch non-32-bit `Sound`'s with a `SampleSizeMismatch`) + /// - 32-bit fixed in config here: https://github.com/raysan5/raylib/blob/master/src/config.h#L282 + /// - device format set here: https://github.com/raysan5/raylib/blob/master/src/raudio.c#L288 + /// - potential upstream raylib discussion: allowing for other samplesSizes for `Sound` + #[inline] + pub fn update(&mut self, data: &[T]) -> Result<(), UpdateAudioStreamError> { + let expected_sample_size_bits = + usize::try_from(self.stream.sampleSize).expect("sampleSize should be 8, 16, or 32"); + let provided_sample_size_bits = size_of::() * u8::BITS as usize; + if provided_sample_size_bits != expected_sample_size_bits { + return Err(UpdateAudioStreamError::SampleSizeMismatch { + expected: expected_sample_size_bits, + provided: provided_sample_size_bits, + }); + } + let max_frame_count = usize::try_from(self.frameCount) + .expect("frameCount should be a valid memory allocation size"); + let provided_frame_count = data.len(); + if provided_frame_count > max_frame_count { + return Err(UpdateAudioStreamError::TooManyFrames { + max: max_frame_count, + provided: provided_frame_count, + }); + } + unsafe { + ffi::UpdateSound( + self.0, + data.as_ptr() as *const std::os::raw::c_void, + provided_frame_count.try_into().unwrap(), + ); + } + Ok(()) + } } impl<'aud, 'bind> SoundAlias<'aud, 'bind> { @@ -623,14 +649,26 @@ impl<'aud> AudioStream<'aud> { /// Updates audio stream buffers with data. #[inline] - pub fn update(&mut self, data: &[T]) { + pub fn update(&mut self, data: &[T]) -> Result<(), UpdateAudioStreamError> { + let expected_sample_size = + usize::try_from(self.sampleSize).expect("sampleSize should be 8, 16, or 32"); + let provided_sample_size_bits = size_of::() * u8::BITS as usize; + if provided_sample_size_bits != expected_sample_size { + return Err(UpdateAudioStreamError::SampleSizeMismatch { + expected: expected_sample_size, + provided: provided_sample_size_bits, + }); + } + let provided_frame_count = data.len(); + unsafe { ffi::UpdateAudioStream( self.0, data.as_ptr() as *const std::os::raw::c_void, - (data.len() * std::mem::size_of::()) as i32, + provided_frame_count.try_into().unwrap(), ); } + Ok(()) } /// Plays audio stream. diff --git a/raylib/src/core/callbacks.rs b/raylib/src/core/callbacks.rs index 390e0786..9235de9b 100644 --- a/raylib/src/core/callbacks.rs +++ b/raylib/src/core/callbacks.rs @@ -112,11 +112,11 @@ extern "C" fn custom_load_file_data_callback(path: *const c_char, size: *mut c_i } } -extern "C" fn custom_save_file_text_callback(a: *const c_char, b: *mut c_char) -> bool { +extern "C" fn custom_save_file_text_callback(a: *const c_char, b: *const c_char) -> bool { let save_file_text = save_file_text_callback().unwrap(); let a = unsafe { CStr::from_ptr(a) }; let b = unsafe { CStr::from_ptr(b) }; - return save_file_text(a.to_str().unwrap(), b.to_str().unwrap()); + save_file_text(a.to_str().unwrap(), b.to_str().unwrap()) } extern "C" fn custom_load_file_text_callback(a: *const c_char) -> *mut c_char { let load_file_text = load_file_text_callback().unwrap(); @@ -157,6 +157,7 @@ macro_rules! safe_callback_set_func { /// Set custom trace log pub fn set_trace_log_callback<'a>(cb: fn(TraceLogLevel, &str)) -> Result<(), SetLogError<'a>> { TRACE_LOG_CALLBACK.store(cb as usize, Ordering::Relaxed); + #[cfg(not(feature = "nobuild"))] unsafe { ffi::setLogCallbackWrapper() }; Ok(()) } @@ -313,17 +314,17 @@ impl RaylibHandle { /// Set custom trace log #[deprecated = "Decoupled from RaylibHandle. Use [set_trace_log_callback](core::callbacks::set_trace_log_callback) instead."] pub fn set_trace_log_callback( - &mut self, + &'_ mut self, cb: fn(TraceLogLevel, &str), - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { set_trace_log_callback(cb) } /// Set custom file binary data saver #[deprecated = "Decoupled from RaylibHandle. Use [set_save_file_data_callback](core::callbacks::set_save_file_data_callback) instead."] pub fn set_save_file_data_callback( - &mut self, + &'_ mut self, cb: fn(&str, &[u8]) -> bool, - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { set_save_file_data_callback(cb) } /// Set custom file binary data loader @@ -331,17 +332,17 @@ impl RaylibHandle { /// Whatever you return from your callback will be intentionally leaked as Raylib is relied on to free it. #[deprecated = "Decoupled from RaylibHandle. Use [set_load_file_data_callback](core::callbacks::set_load_file_data_callback) instead."] pub fn set_load_file_data_callback<'b>( - &mut self, + &'_ mut self, cb: fn(&str) -> Vec, - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { set_load_file_data_callback(cb) } /// Set custom file text data saver #[deprecated = "Decoupled from RaylibHandle. Use [set_save_file_text_callback](core::callbacks::set_save_file_text_callback) instead."] pub fn set_save_file_text_callback( - &mut self, + &'_ mut self, cb: fn(&str, &str) -> bool, - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { set_save_file_text_callback(cb) } /// Set custom file text data loader @@ -349,19 +350,19 @@ impl RaylibHandle { /// Whatever you return from your callback will be intentionally leaked as Raylib is relied on to free it. #[deprecated = "Decoupled from RaylibHandle. Use [set_load_file_text_callback](core::callbacks::set_load_file_text_callback) instead."] pub fn set_load_file_text_callback( - &mut self, + &'_ mut self, cb: fn(&str) -> String, - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { set_load_file_text_callback(cb) } /// Audio thread callback to request new data #[deprecated = "Decoupled from RaylibHandle. Use [set_audio_stream_callback](core::callbacks::set_audio_stream_callback) instead."] pub fn set_audio_stream_callback( - &mut self, + &'_ mut self, stream: AudioStream, cb: fn(&[u8]), - ) -> Result<(), SetLogError> { + ) -> Result<(), SetLogError<'_>> { if AUDIO_STREAM_CALLBACK.load(Ordering::Acquire) == 0 { AUDIO_STREAM_CALLBACK.store(cb as _, Ordering::Release); unsafe { ffi::SetAudioStreamCallback(stream.0, Some(custom_audio_stream_callback)) } diff --git a/raylib/src/core/collision.rs b/raylib/src/core/collision.rs index 602b6749..43b3e491 100644 --- a/raylib/src/core/collision.rs +++ b/raylib/src/core/collision.rs @@ -5,7 +5,7 @@ use crate::ffi; use crate::math::{Matrix, RayCollision}; use crate::models::Mesh; -/// Check if circle collides with a line created betweeen two points [p1] and [p2] +/// Check if circle collides with a line created between two points [p1] and [p2] #[inline] #[must_use] pub fn check_collision_circle_line( diff --git a/raylib/src/core/data.rs b/raylib/src/core/data.rs index c4efdd86..96bb2239 100644 --- a/raylib/src/core/data.rs +++ b/raylib/src/core/data.rs @@ -1,187 +1,38 @@ -//! Data manipulation functions. Compress and Decompress with DEFLATE +use crate::{databuf::DataBuf, error::CompressionError, ffi}; use std::{ - alloc::Layout, ffi::{c_char, CString}, ops::{Deref, DerefMut}, path::Path, ptr::NonNull + ffi::{CString, c_char}, + mem::MaybeUninit, + path::Path, }; +use crate::error::Base64Error; -use crate::{ffi, error::{AllocationError, CompressionError}}; - -/// A wrapper acting as an owned buffer for Raylib-allocated memory. -/// Automatically releases the memory with [`ffi::MemFree()`] when dropped. -/// -/// Dereference or call `.as_ref()`/`.as_mut()` to access the memory as a `&[T]` or `&mut [T]` respectively. -/// -/// # Example -/// ``` -/// use raylib::prelude::*; -/// let buf: DataBuf = compress_data(b"11111").unwrap(); -/// // Use this how you used to use the return of `compress_data()`. -/// // It will live until `buf` goes out of scope or gets dropped. -/// let data: &[u8] = buf.as_ref(); -/// let expected: &[u8] = &[1, 5, 0, 250, 255, 49, 49, 49, 49, 49]; -/// assert_eq!(data, expected); -/// ``` -#[derive(Debug)] -pub struct DataBuf { - buf: NonNull, - len: usize, -} -impl Drop for DataBuf { - fn drop(&mut self) { - unsafe { - ffi::MemFree(self.buf.as_ptr().cast()); - } - } -} -impl Deref for DataBuf { - type Target = [T]; - fn deref(&self) -> &Self::Target { - // This is safe because DataBuf contents are checked everywhere `buf` can be set. - unsafe { &*std::ptr::slice_from_raw_parts(self.buf.as_ptr(), self.len) } - } -} -impl DerefMut for DataBuf { - fn deref_mut(&mut self) -> &mut Self::Target { - // This is safe because DataBuf contents are checked everywhere `buf` can be set. - unsafe { &mut *std::ptr::slice_from_raw_parts_mut(self.buf.as_ptr(), self.len) } - } -} -impl AsRef<[T]> for DataBuf { - #[inline] - fn as_ref(&self) -> &[T] { - self.deref() - } -} -impl AsMut<[T]> for DataBuf { - #[inline] - fn as_mut(&mut self) -> &mut [T] { - self.deref_mut() - } -} -impl DataBuf { - /// Wrap an already allocated pointer in a `DataBuf`. - /// - /// **Note:** This method is only intended for use with pointers given by Raylib - /// with the expectation that they will be manually deallocated with [`ffi::MemFree`]. - /// DO NOT use this function to wrap arbitrary pointers or pointers that Raylib will - /// deallocate itself. - /// - /// If the pointer is expected to be conditionally deallocated by Raylib, - /// (i.e. conditionally passing the buffer to a Raylib function that will certainly deallocatate it) - /// use [`DataBuf::leak`] to unwrap the memory so that `drop` does not automatically free it. - /// - /// # Returns - /// - /// This method returns [`None`] if `buf` is null. - /// - /// # Panics - /// - /// This method may panic if any of the following are true while `buf` is non-null: - /// - `count` is less than 1 - /// - `buf` is unaligned - /// - total bytes exceed [`isize::MAX`] - pub(crate) fn new(buf: *mut T, count: i32) -> Option { - NonNull::new(buf).map(|buf| { - // Ensure DataBuf can always be dereferenced as a slice. - assert!(count >= 1, "non-null data should be at least 1 byte"); - assert!(buf.is_aligned(), "DataBuf should be aligned"); - assert!(std::mem::size_of::() - .checked_mul(count as usize) - .is_some_and(|total_size| total_size <= (isize::MAX as usize)), - "total size of DataBuf should not exceed `isize::MAX`"); - - Self { buf, len: count as usize } - }) - } - - /// Extract the pointer without freeing it, for the purpose of passing it to a function that will deallocate it manually. - pub(crate) fn leak(self) -> (NonNull, usize) { - let buf = self.buf; - let len = self.len; - std::mem::forget(self); - (buf, len) - } - - /// Allocate new memory managed by Raylib - /// - /// # Errors - /// - /// - "cannot allocate less than 1 element": `count` is less than 1. - /// - "memory request exceeds unsigned integer maximum": The size of `[T; count]` is greater than [`u32::MAX`]. - /// - "memory request exceeds capacity": [`ffi::MemAlloc`] returned null. - /// - /// # Panics - /// - /// This method may panic if the pointer returned by [`ffi::MemAlloc`] is unaligned. - pub fn alloc(count: i32) -> Result { - if count >= 1 { - let count = count as usize; - match Layout::array::(count) { - Err(_e) => Err(AllocationError::InvalidLayout), // I would like to display `e` if possible - Ok(layout) => { - let size = layout.size(); - if size <= u32::MAX as usize { - if let Some(buf) = NonNull::new(unsafe { ffi::MemAlloc(size as u32) }.cast()) { - assert!(buf.is_aligned(), "allocated buffer should always be aligned"); - Ok(Self { buf, len: count }) - } else { Err(AllocationError::ExceedsCapacity) } - } else { Err(AllocationError::ExceedsUIntMax) } - } - } - } else { Err(AllocationError::SubMinSize) } - } - - /// Reallocate memory already managed by Raylib - /// - /// # Errors - /// - /// - "cannot allocate less than 1 element": `count` is less than 1. - /// - "memory request exceeds unsigned integer maximum": The size of `[T; count]` is greater than [`u32::MAX`]. - /// - "memory request exceeds capacity": [`ffi::MemRealloc`] returned null. \ - /// **Warning:** This represents a risk of double-free if `RL_REALLOC` deallocates regardless of reallocation success, - /// because `self` will retain the old pointer and `DataBuf`'s drop implementation will still free it. - /// - /// # Panics - /// - /// This method may panic if the pointer returned by [`ffi::MemRealloc`] is unaligned. - pub fn realloc(&mut self, new_count: i32) -> Result<(), AllocationError> { - if new_count >= 1 { - let new_count = new_count as usize; - match Layout::array::(new_count) { - Err(_e) => Err(AllocationError::InvalidLayout), // I would like to display `e` if possible - Ok(layout) => { - let size = layout.size(); - if size <= u32::MAX as usize { - if let Some(buf) = NonNull::new(unsafe { ffi::MemRealloc(self.buf.as_ptr().cast(), size as u32) }.cast()) { - assert!(buf.is_aligned(), "allocated buffer should always be aligned"); - self.buf = buf; - self.len = new_count; - Ok(()) - } else { Err(AllocationError::ExceedsCapacity) } - } else { Err(AllocationError::ExceedsUIntMax) } - } - } - } else { Err(AllocationError::SubMinSize) } - } -} - -/// Compress data (DEFLATE algorythm) +/// Compress data (DEFLATE algorithm) /// ```rust /// use raylib::prelude::*; /// let data = compress_data(b"11111").unwrap(); /// let expected: &[u8] = &[1, 5, 0, 250, 255, 49, 49, 49, 49, 49]; /// assert_eq!(data.as_ref(), expected); /// ``` -pub fn compress_data(data: &[u8]) -> Result, CompressionError> { - let mut out_length: i32 = 0; +pub fn compress_data(data: &[u8]) -> Result, CompressionError> { + let mut out_length = MaybeUninit::uninit(); // CompressData doesn't actually modify the data, but the header is wrong let buffer = { - unsafe { ffi::CompressData(data.as_ptr() as *mut _, data.len() as i32, &mut out_length) } + unsafe { + ffi::CompressData( + data.as_ptr() as *mut _, + data.len() as i32, + out_length.as_mut_ptr(), + ) + } }; - DataBuf::new(buffer, out_length) - .ok_or_else(|| CompressionError::CompressionFailed) + // SAFETY: `CompressData` returns a unique, owned pointer that is safe to dereference for + // `out_length` valid, initialized elements if `buffer` is not null. It also guarantees + // `out_length` is initialized if `buffer` is non-null. + unsafe { DataBuf::slice_from_raw(buffer, out_length) } + .ok_or(CompressionError::CompressionFailed) } -/// Decompress data (DEFLATE algorythm) +/// Decompress data (DEFLATE algorithm) /// ```rust /// use raylib::prelude::*; /// let input: &[u8] = &[1, 5, 0, 250, 255, 49, 49, 49, 49, 49]; @@ -189,17 +40,26 @@ pub fn compress_data(data: &[u8]) -> Result, CompressionError> { /// let data = decompress_data(input).unwrap(); /// assert_eq!(data.as_ref(), expected); /// ``` -pub fn decompress_data(data: &[u8]) -> Result, CompressionError> { +pub fn decompress_data(data: &[u8]) -> Result, CompressionError> { #[cfg(debug_assertions)] println!("{:?}", data.len()); - let mut out_length: i32 = 0; + let mut out_length = MaybeUninit::uninit(); // CompressData doesn't actually modify the data, but the header is wrong let buffer = { - unsafe { ffi::DecompressData(data.as_ptr() as *mut _, data.len() as i32, &mut out_length) } + unsafe { + ffi::DecompressData( + data.as_ptr() as *mut _, + data.len() as i32, + out_length.as_mut_ptr(), + ) + } }; - DataBuf::new(buffer, out_length) - .ok_or_else(|| CompressionError::CompressionFailed) + // SAFETY: `DecompressData` returns a unique, owned pointer that is safe to dereference for + // `out_length` valid, initialized elements if `buffer` is not null. It also guarantees + // `out_length` is initialized if `buffer` is non-null. + unsafe { DataBuf::slice_from_raw(buffer, out_length) } + .ok_or(CompressionError::CompressionFailed) } #[cfg(unix)] @@ -221,53 +81,24 @@ pub fn export_data_as_code(data: &[u8], file_name: impl AsRef) -> bool { } /// Encode data to Base64 string -pub fn encode_data_base64(data: &[u8]) -> Vec { - let mut output_size = 0; - let bytes = - unsafe { ffi::EncodeDataBase64(data.as_ptr(), data.len() as i32, &mut output_size) }; - - let s = unsafe { std::slice::from_raw_parts(bytes, output_size as usize) }; - if s.contains(&0) { - // Work around a bug in Rust's from_raw_parts function - let mut keep = true; - let b: Vec = s - .iter() - .filter(|f| { - if **f == 0 { - keep = false; - } - keep - }) - .map(|f| *f) - .collect(); - b - } else { - s.to_vec() - } +pub fn encode_data_base64(data: &[u8]) -> Result, Base64Error> { + let mut output_size = MaybeUninit::::uninit(); + let bytes = unsafe { ffi::EncodeDataBase64(data.as_ptr(), data.len() as i32, output_size.as_mut_ptr()) }; + unsafe { DataBuf::slice_from_raw(bytes as *mut u8, output_size) } + .ok_or(Base64Error::EncodeFailed) } /// Decode Base64 data -pub fn decode_data_base64(data: &[u8]) -> Vec { - let mut output_size = 0; - - let bytes = unsafe { ffi::DecodeDataBase64(data.as_ptr(), &mut output_size) }; - - let s = unsafe { std::slice::from_raw_parts(bytes, output_size as usize) }; - if s.contains(&0) { - // Work around a bug in Rust's from_raw_parts function - let mut keep = true; - let b: Vec = s - .iter() - .filter(|f| { - if **f == 0 { - keep = false; - } - keep - }) - .map(|f| *f) - .collect(); - b - } else { - s.to_vec() - } -} +pub fn decode_data_base64(data: &[u8]) -> Result, Base64Error> { + let mut output_size = MaybeUninit::::uninit(); + let null_trimmed_data = match data.iter().position(|&element| element == 0) { + Some(pos) => &data[..pos], + None => data, + }; + let mut c_str = Vec::with_capacity(null_trimmed_data.len() + 1); + c_str.extend_from_slice(null_trimmed_data); + c_str.push(0); + let bytes = unsafe { ffi::DecodeDataBase64(c_str.as_ptr() as *const c_char, output_size.as_mut_ptr()) }; + unsafe { DataBuf::slice_from_raw(bytes, output_size) } + .ok_or(Base64Error::DecodeFailed) +} \ No newline at end of file diff --git a/raylib/src/core/databuf.rs b/raylib/src/core/databuf.rs new file mode 100644 index 00000000..a3e22f6a --- /dev/null +++ b/raylib/src/core/databuf.rs @@ -0,0 +1,661 @@ +//! Raylib-managed memory. Similar in function to [`Box`], but specifically +//! designed for `RL_MALLOC`/`RL_REALLOC`/`RL_FREE`. +//! +//! See [`DataBuf`] + +#![warn(clippy::style, clippy::pedantic, clippy::perf)] +#![allow( + clippy::missing_errors_doc, + reason = "errors are documented at their defintion, not by the functions that use them" +)] +#![warn( + clippy::unnecessary_safety_comment, + clippy::unnecessary_safety_doc, + reason = "minimize confusion over what is and isn't safe" +)] +#![deny( + clippy::missing_safety_doc, + clippy::undocumented_unsafe_blocks, + clippy::multiple_unsafe_ops_per_block, + reason = "DataBuf is delicate and assumptions must be clearly declared and scoped" +)] + +//! Data manipulation functions. Compress and Decompress with DEFLATE +use std::{ + alloc::Layout, + marker::PhantomData, + mem::MaybeUninit, + num::{NonZeroU32, NonZeroUsize}, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +use crate::{error::AllocationError, ffi}; + +/// Calculate the number of bytes needed to allocate `layout`. +#[inline] +fn allocation_layout_size(layout: Layout) -> Result { + let bytes = layout + .size() + .try_into() + .ok() + .ok_or(AllocationError::IntoUIntFailed)?; + + NonZeroU32::new(bytes).ok_or(AllocationError::ZeroBytes) +} + +/// Calculate the number of bytes needed to allocate `[T; count]`. +#[inline] +fn allocation_array_size(count: usize) -> Result { + allocation_layout_size(Layout::array::(count).map_err(|_| AllocationError::IntoUIntFailed)?) +} + +/// Calculate the number of bytes needed to allocate a copy of `val` +#[inline] +fn allocation_val_size(val: &T) -> Result { + allocation_layout_size(Layout::for_value(val)) +} + +mod rl_managed { + use super::ffi; + use std::{mem::MaybeUninit, num::NonZeroU32, ptr::NonNull}; + + /// Raylib-managed [`NonNull`]. + /// + /// # Safety + /// + /// [`RlManaged`] must be unique, not dangling, and allocated with `RL_ALLOC`/[`ffi::MemAlloc`] + /// or `RL_REALLOC`/[`ffi::MemRealloc`]. + /// + /// **However**, it is *not* guaranteed to be safe to dereference. It is the user's responsibility + /// to ensure the pointer stored in [`RlManaged`] is safe to dereference as the type it claims to + /// be, for the count it claims to be, before dereferencing. + /// + /// [`RlManaged`] does not guarantee the size, alignment, nor validity of its allocation -- **it + /// only guarantees that its memory block is safe for Raylib to re/deallocate**. + #[repr(transparent)] + #[derive(Debug)] + #[must_use] + pub struct RlManaged(/* unsafe */ NonNull); + + /// Allocate `size` bytes of memory aligned to `T` using [`ffi::MemAlloc`]. + /// + /// Returns [`None`] if [`ffi::MemAlloc`] returned null. + #[inline] + pub fn mem_alloc(size: NonZeroU32) -> Option>> { + // SAFETY: `size` is not zero. + let ptr = unsafe { ffi::MemAlloc(size.get()) }.cast(); + NonNull::new(ptr).map(RlManaged) + } + + impl RlManaged { + /// Mark memory as Raylib-managed. + /// + /// # Safety + /// + /// `data` must be unique, not dangling, and allocated with `RL_ALLOC`/[`ffi::MemAlloc`] or `RL_REALLOC`/[`ffi::MemRealloc`]. + #[inline] + pub(crate) const unsafe fn new(data: NonNull) -> Self { + Self(data) + } + + /// Returns a shared reference to the value. + /// + /// # Safety + /// + /// `self` must be safe to dereference as `T`. + /// See [`RlManaged`] safety for more information. + #[inline] + #[must_use] + pub const unsafe fn as_ref(&self) -> &T { + // SAFETY: RlManaged must be unique and cannot dangle. + // Taking `self` by reference guarantees aliasing rules are followed. + // Caller must ensure `self` is safe to dereference. + unsafe { self.0.as_ref() } + } + + /// Returns a unique reference to the value. + /// + /// # Safety + /// + /// `self` must be safe to dereference as `T`. + /// See [`RlManaged`] safety for more information. + #[inline] + #[must_use] + pub const unsafe fn as_mut(&mut self) -> &mut T { + // SAFETY: RlManaged must be unique and cannot dangle. + // Taking `self` by mutable reference guarantees aliasing rules are followed. + // Caller must ensure `self` is safe to dereference. + unsafe { self.0.as_mut() } + } + + /// Access the pointer of this allocation. + #[inline] + #[must_use] + pub const fn into_inner(self) -> NonNull { + self.0 + } + + /// Reallocate `self` to have `size` bytes of memory aligned to `U` using [`ffi::MemRealloc`]. + /// + /// Any elements that were initialized prior to calling will still be initialized after reallocating. + /// Any elements that were not previously in the allocation are uninitialized. + /// + /// Returns the original memory block if [`ffi::MemRealloc`] returned null. + /// + /// **WARNING:** This method does not drop the contents of `self`. + #[inline] + pub fn mem_realloc(self, size: NonZeroU32) -> Result>, Self> { + // SAFETY: `self` is non-null and is Raylib-allocated, and `size` is not zero. + let new_ptr = unsafe { ffi::MemRealloc(self.0.as_ptr().cast(), size.get()) }.cast(); + NonNull::new(new_ptr).map(RlManaged).ok_or(self) + } + + /// Free `self` using [`ffi::MemFree`]. + /// + /// **WARNING:** This method does not drop the contents of `self`. + #[inline] + pub fn mem_free(self) { + // SAFETY: `self` is non-null, not dangling, and Raylib-allocated. + unsafe { + ffi::MemFree(self.0.as_ptr().cast()); + } + } + } + + impl RlManaged<[T]> { + /// Create a Raylib-managed slice from a thin pointer and a length. + /// + /// The `len` argument is the number of **elements**, not the number of bytes. + /// + /// This function is safe, but dereferencing the return value is unsafe. + /// See the documentation of [`std::slice::from_raw_parts`] for slice safety requirements. + #[inline] + #[allow( + clippy::needless_pass_by_value, + reason = "passing by reference would allow `data` to be duplicated because `data.0` is Copy" + )] + pub(crate) const fn slice_from_raw_parts(data: RlManaged, len: usize) -> Self { + Self(NonNull::slice_from_raw_parts(data.0, len)) + } + } +} +pub use rl_managed::*; + +/// A wrapper acting as an owned buffer for Raylib-allocated memory. +/// Automatically releases the memory with [`ffi::MemFree()`] when dropped. +/// +/// # Example +/// ``` +/// use raylib::prelude::*; +/// let buf: DataBuf<[u8]> = compress_data(b"11111").unwrap(); +/// // Use this how you used to use the return of `compress_data()`. +/// // It will live until `buf` goes out of scope or gets dropped. +/// let data: &[u8] = buf.as_ref(); +/// let expected: &[u8] = &[1, 5, 0, 250, 255, 49, 49, 49, 49, 49]; +/// assert_eq!(data, expected); +/// ``` +/// +/// # Safety +/// +/// - `buf` must not be dangling. +/// - `buf` must be safe to dereference. +/// - `buf` must be a **unique, owned** pointer (not to static or local memory, and the memory must not be +/// accessible through any pointers/references not derived from the returned [`DataBuf`]). +/// - `buf` must point to [valid](https://doc.rust-lang.org/std/ptr/index.html#safety), intialized data. +/// - `buf` must be [convertible to a reference](std::ptr#pointer-to-reference-conversion). +/// - `buf` must have been created with `RL_MALLOC`/[`ffi::MemAlloc`] or `RL_REALLOC`/[`ffi::MemRealloc`]. +/// +/// This structure is only intended for use with pointers given by Raylib with the expectation that you +/// would manually deallocate them with [`ffi::MemFree`]. DO NOT use this structure to hold arbitrary +/// or un-owned pointers. +/// +/// If the pointer is expected to be conditionally deallocated by Raylib, +/// (i.e. conditionally passing the buffer to a Raylib function that will certainly deallocatate it) +/// use [`DataBuf::leak`] to prevent [`DataBuf::drop`] from causing a double-free. +#[derive(Debug)] +#[repr(transparent)] +#[must_use] +pub struct DataBuf { + buf: RlManaged, + /// Tell the compiler that this instance logically owns a `T`. + _marker: PhantomData, +} + +impl Drop for DataBuf { + #[inline] + fn drop(&mut self) { + let mut ptr = MaybeUninit::uninit(); + // SAFETY: Both `self.buf` and `ptr` are non-null and valid for 1 element. + // Taking `self` by mutable reference ensures aliasing rules are upheld + // outside of the method; and `self` is not used again after this line. + // Because `drop` is the end of `self`'s lifetime, `buf` is guaranteed not + // to be accessed again after the function returns. + unsafe { + std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), ptr.as_mut_ptr(), 1); + } + // SAFETY: Just written to with a valid value. + let data = unsafe { ptr.assume_init() }.into_inner(); + // SAFETY: DataBuf `buf` is guaranteed to be unique, owned, non-null, valid, and not dangling. + // DataBuf and RlManaged do not implement Clone, and `drop` is called *at most once*, so `data` + // is guaranteed not to have been dropped for `T` yet so long as DataBuf's safety contract has + // been upheld. + unsafe { + data.drop_in_place(); + } + // SAFETY: `data` taken from `RlManaged` is still managed by Raylib after contents is dropped. + // Any copies of `data` go out of scope, preventing double-free. + unsafe { RlManaged::new(data) }.mem_free(); + } +} + +impl Deref for DataBuf { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl DerefMut for DataBuf { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +impl AsRef for DataBuf { + #[inline] + fn as_ref(&self) -> &T { + self + } +} + +impl AsMut for DataBuf { + #[inline] + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl DataBuf> { + /// Initialize the buffer with valid memory. + #[inline] + pub const fn write(mut self, val: T) -> DataBuf { + // SAFETY: DataBuf guarantees `buf` is safe to dereference, and + // ownership of `self` ensures aliasing rules are followed. + let buf = unsafe { self.buf.as_mut() }; + MaybeUninit::write(buf, val); + // SAFETY: We just initialized this value. + unsafe { self.assume_init() } + } + + /// Mark that the data pointed to by `self` is initialized. + /// + /// # Safety + /// + /// The data pointed to by `self` must actually be initialized. + #[inline] + pub const unsafe fn assume_init(self) -> DataBuf { + // SAFETY: `DataBuf>` and `DataBuf` have the same layout + unsafe { std::mem::transmute::>, DataBuf>(self) } + } +} + +impl DataBuf<[MaybeUninit]> { + /// Mark that the data pointed to by `self` is initialized. + /// + /// # Safety + /// + /// The data pointed to by `self` must actually be initialized. + #[inline] + pub const unsafe fn assume_init(self) -> DataBuf<[T]> { + // SAFETY: `DataBuf<[MaybeUninit]>` and `DataBuf<[T]>` have the same layout + unsafe { std::mem::transmute::]>, DataBuf<[T]>>(self) } + } +} + +impl DataBuf { + /// Returns a shared reference to the value. + #[inline] + #[must_use] + pub const fn as_ref(&self) -> &T { + // SAFETY: DataBuf guarantees `buf` is safe to dereference, and taking + // `self` by reference ensures aliasing rules are followed. + unsafe { self.buf.as_ref() } + } + + /// Returns a unique reference to the value. + #[inline] + #[must_use] + pub const fn as_mut(&mut self) -> &mut T { + // SAFETY: DataBuf guarantees `buf` is safe to dereference, and taking + // `self` by mutable reference ensures aliasing rules are followed. + unsafe { self.buf.as_mut() } + } + + /// Wrap an already allocated, non-null, Raylib-managed pointer in a [`DataBuf`]. + #[inline] + pub(crate) const fn from_rlmanaged(buf: RlManaged) -> Self { + Self { + buf, + _marker: PhantomData, + } + } + + /// Wrap an already allocated, non-null pointer in a [`DataBuf`]. + /// + /// # Safety + /// + /// See the [`DataBuf`] safety requirements. + #[inline] + pub(crate) const unsafe fn from_nonnull(data: NonNull) -> Self { + // SAFETY: Caller must ensure `data` is Raylib-managed. + let buf = unsafe { RlManaged::new(data) }; + Self::from_rlmanaged(buf) + } + + /// Wrap an already allocated pointer in a [`DataBuf`]. + /// Returns [`None`] if `buf` is null. + /// + /// # Safety + /// + /// See the [`DataBuf`] safety requirements. + #[inline] + pub(crate) const unsafe fn from_raw(ptr: *mut T) -> Option { + if let Some(buf) = NonNull::new(ptr) { + // SAFETY: Caller must uphold safety contract. + Some(unsafe { Self::from_nonnull(buf) }) + } else { + None + } + } + + /// Extract the pointer without freeing it, for the purpose of transferring ownership. + /// + /// **WARNING:** The returned pointer must be unloaded manually to avoid a memory leak. + #[inline] + pub const fn into_inner(self) -> RlManaged { + let mut buf = MaybeUninit::uninit(); + // SAFETY: Both `self.buf` and `ptr` are non-null and valid for 1 element. + unsafe { + std::ptr::copy_nonoverlapping(std::ptr::from_ref(&self.buf), buf.as_mut_ptr(), 1); + } + // SAFETY: Just written to with a valid value + let buf = unsafe { buf.assume_init() }; + std::mem::forget(self); // Prevent `self` from causing double-free + buf + } +} + +impl DataBuf { + /// Allocate new memory managed by Raylib. + #[inline] + pub fn alloc() -> Result>, AllocationError> { + let bytes = allocation_array_size::(1)?; + let buf = mem_alloc::(bytes).ok_or(AllocationError::NullAlloc)?; + Ok(DataBuf::from_rlmanaged(buf)) + } + + /// Allocate new memory managed by Raylib and move `val` into it. + #[inline] + pub fn alloc_from(val: T) -> Result { + match Self::alloc() { + Ok(buf) => Ok(buf.write(val)), + Err(e) => Err((e, val)), + } + } + + /// Allocate new memory managed by Raylib and clone `val` into it. + #[inline] + pub fn alloc_from_clone(src: &T) -> Result + where + T: Clone, + { + Ok(Self::alloc()?.write(src.clone())) + } + + /// Allocate new memory managed by Raylib and copy `val` into it. + #[inline] + pub fn alloc_from_copy(src: &T) -> Result + where + T: Copy, + { + Ok(Self::alloc()?.write(*src)) + } +} + +impl DataBuf<[T]> { + /// Wrap an already allocated pointer in a [`DataBuf`]. + /// + /// # Safety + /// + /// **In addition** to the [`DataBuf`] and [`from_raw_parts_mut`](std::slice::from_raw_parts_mut) + /// safety requirements, this function also requires: + /// - `buf` must point to an array of as many valid, initialized elements as defined by `count`. + #[inline] + pub(crate) const unsafe fn slice_from_nonnull(buf: NonNull, len: NonZeroUsize) -> Self { + // SAFETY: Caller must uphold `from_raw_parts_mut` safety contract + let slice = unsafe { std::slice::from_raw_parts_mut(buf.as_ptr(), len.get()) }; + // SAFETY: A mutable reference cannot be null. + let buf = unsafe { NonNull::new_unchecked(slice) }; + // SAFETY: Calller must uphold `DataBuf` safety contract + unsafe { Self::from_nonnull(buf) } + } + + /// Wrap an already allocated pointer in a [`DataBuf`]. + /// Returns [`None`] if `buf` is null. + /// + /// Takes `count` as a [`MaybeUninit`] for convenience, as most Raylib functions returning an array + /// buffer provide the length of the buffer as an [`i32`] out param. + /// + /// # Safety + /// + /// **In addition** to the [`DataBuf`] and [`from_raw_parts_mut`](std::slice::from_raw_parts_mut) safety requirements, + /// this function also requires: + /// - `count` must be initialized if `buf` is non-null. + /// - `buf` must point to an array of as many valid, initialized elements as defined by `count`. + /// + /// # Panics + /// + /// This method may panic if `count` is less than 1 or greater than [`usize::MAX`] while `buf` is non-null. + #[inline] + pub(crate) const unsafe fn slice_from_raw( + ptr: *mut T, + count: MaybeUninit, + ) -> Option { + if let Some(buf) = NonNull::new(ptr) { + // SAFETY: Caller must ensure `count` is initialized if `buf` is non-null. + let count = unsafe { count.assume_init() }; + assert!(count >= 1, "`count` should be positive"); + // confirm `as usize` will not overflow + #[cfg(target_pointer_width = "16")] + { + assert!( + count <= usize::MAX as i32, + "`count` should fit within usize" + ); + } + #[allow(clippy::cast_sign_loss, reason = "intentional")] + // SAFETY: Just checked that count is non-zero and positive. + let len = unsafe { NonZeroUsize::new_unchecked(count as usize) }; + // SAFETY: Caller must uphold `DataBuf` and `slice` safety contracts + Some(unsafe { Self::slice_from_nonnull(buf, len) }) + } else { + None + } + } + + /// Allocate new memory managed by Raylib. + /// + /// # Example + /// ``` + /// # use raylib::prelude::DataBuf; + /// let mut data_buf = DataBuf::<[i32]>::alloc(5).unwrap(); + /// data_buf[0].write(4); + /// data_buf[1].write(8); + /// data_buf[2].write(-23); + /// data_buf[3].write(9); + /// data_buf[4].write(0); + /// // SAFETY: Just initialized all elements + /// let data_buf = unsafe { data_buf.assume_init() }; + /// assert_eq!(data_buf.as_ref(), &[4, 8, -23, 9, 0]); + /// ``` + /// (See also: [`DataBuf::alloc_from_copy`]) + #[inline] + pub fn alloc(count: usize) -> Result]>, AllocationError> { + let bytes = allocation_array_size::(count)?; + let buf = mem_alloc::(bytes).ok_or(AllocationError::NullAlloc)?; + Ok(DataBuf { + buf: RlManaged::slice_from_raw_parts(buf, count), + _marker: PhantomData, + }) + } + + /// Allocate memory managed by Raylib and initialize by copying. + /// + /// # Panics + /// + /// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned. + /// + /// # Example + /// ``` + /// # use raylib::prelude::DataBuf; + /// let src = [4, 8, -23, 9, 0]; + /// let mut data_buf = DataBuf::<[i32]>::alloc_from_copy(&src).unwrap(); + /// assert_eq!(data_buf.as_ref(), &src); + /// ``` + pub fn alloc_from_clone(src: &[T]) -> Result + where + T: Copy, + { + let mut buf = Self::alloc(src.len())?; + // SAFETY: `&[T]` and `&[MaybeUninit]` have the same layout. Reference to is non-null. + let uninit_src = unsafe { &*(std::ptr::from_ref::<[T]>(src) as *const [MaybeUninit]) }; + buf.copy_from_slice(uninit_src); + // SAFETY: Valid elements have just been copied into `self` so it is initialized. + Ok(unsafe { buf.assume_init() }) + } + + /// Allocate memory managed by Raylib and initialize by copying. + /// + /// # Panics + /// + /// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned. + /// + /// # Example + /// ``` + /// # use raylib::prelude::DataBuf; + /// let src = [4, 8, -23, 9, 0]; + /// let mut data_buf = DataBuf::<[i32]>::alloc_from_copy(&src).unwrap(); + /// assert_eq!(data_buf.as_ref(), &src); + /// ``` + pub fn alloc_from_copy(src: &[T]) -> Result + where + T: Copy, + { + let mut buf = Self::alloc(src.len())?; + // SAFETY: `&[T]` and `&[MaybeUninit]` have the same layout. Reference to is non-null. + let uninit_src = unsafe { &*(std::ptr::from_ref::<[T]>(src) as *const [MaybeUninit]) }; + buf.copy_from_slice(uninit_src); + // SAFETY: Valid elements have just been copied into `self` so it is initialized. + Ok(unsafe { buf.assume_init() }) + } + + /// Reallocate memory already managed by Raylib. + /// + /// # Panics + /// + /// This method may panic in debug if the pointer returned by [`ffi::MemAlloc`] is unaligned. + pub fn realloc( + self, + new_count: usize, + ) -> Result]>, (AllocationError, Self)> { + match allocation_array_size::(new_count) { + Err(e) => Err((e, self)), + Ok(bytes) => { + let new_buf = self.into_inner().mem_realloc(bytes).map_err(|old_buf| { + (AllocationError::NullAlloc, Self::from_rlmanaged(old_buf)) + })?; + let new_buf = RlManaged::slice_from_raw_parts(new_buf, new_count); + Ok(DataBuf::from_rlmanaged(new_buf)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_drop_value() { + struct DropTest(F); + impl Drop for DropTest { + fn drop(&mut self) { + (self.0)(); + } + } + impl std::fmt::Debug for DropTest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("DropTest").finish() + } + } + + let mut times_dropped = 0; + let buf = DataBuf::alloc_from(DropTest(|| times_dropped += 1)).unwrap(); + drop(buf); + assert_eq!( + times_dropped, 1, + "DataBuf should drop contents exactly once" + ); + } + + #[test] + fn test_from_raw() { + type ExpectTy = [i32; 5]; + const EXPECT: [i32; 5] = [64, 264, -57, 653, -153]; + let bytes @ 1.. = (std::mem::size_of::() * EXPECT.len()) + .try_into() + .unwrap() + else { + unreachable!() + }; + // SAFETY: `bytes` is non-zero + let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::(); + assert!(!ptr.is_null(), "should be able to allocate"); + // SAFETY: `ptr` is not null and `MemAlloc` returns owned memory. + unsafe { + ptr.write(EXPECT); + }; + // SAFETY: `ptr` is unique, non-dangling, valid, and allocated by Raylib + let buf = unsafe { DataBuf::from_raw(ptr) }.expect("ptr should be convertible to DataBuf"); + assert_eq!(&*buf, &EXPECT); + } + + #[test] + fn test_slice_from_raw() { + type ExpectTy = [i32; 5]; + const EXPECT: ExpectTy = [6, -453, 364, 45632, -1233]; + let bytes @ 1.. = (std::mem::size_of::() * EXPECT.len()) + .try_into() + .unwrap() + else { + unreachable!() + }; + // SAFETY: `bytes` is non-zero + let ptr = unsafe { ffi::MemAlloc(bytes) }.cast::(); + assert!(!ptr.is_null(), "should be able to allocate"); + // SAFETY: `ptr` is not null and `MemAlloc` returns owned memory. + unsafe { + ptr.cast::().write(EXPECT); + }; + // SAFETY: `ptr` is unique, non-dangling, valid, and allocated by Raylib + let buf = unsafe { + DataBuf::slice_from_raw(ptr, MaybeUninit::new(EXPECT.len().try_into().unwrap())) + } + .expect("ptr should be convertible to DataBuf"); + assert_eq!(&*buf, &EXPECT); + } +} diff --git a/raylib/src/core/drawing.rs b/raylib/src/core/drawing.rs index 83d80662..65296bf5 100644 --- a/raylib/src/core/drawing.rs +++ b/raylib/src/core/drawing.rs @@ -50,7 +50,7 @@ pub struct RaylibDrawHandle<'a>(&'a mut RaylibHandle); impl<'a> RaylibDrawHandle<'a> { #[deprecated = "Calling begin_drawing within RaylibDrawHandle will result in a runtime error."] #[doc(hidden)] - pub fn begin_drawing(&mut self, _: &RaylibThread) -> RaylibDrawHandle { + pub fn begin_drawing(&'_ mut self, _: &RaylibThread) -> RaylibDrawHandle<'_> { panic!("Nested begin_drawing call") } #[deprecated = "Calling draw within RaylibDrawHandle will result in a runtime error."] diff --git a/raylib/src/core/error.rs b/raylib/src/core/error.rs index 053d1b82..da96fb7d 100644 --- a/raylib/src/core/error.rs +++ b/raylib/src/core/error.rs @@ -1,4 +1,4 @@ -//! Definitions for error types used throught the crate +//! Definitions for error types used throughout the crate use thiserror::Error; @@ -34,16 +34,30 @@ pub enum LoadSoundError { MusicNull, } +/// Errors that can occur when pushing new audio data into a `Sound` or `AudioStream`. +/// **Notes** (iann): if raylib upstream discussion introduces any of these checks, we might simplify these to avoid any redundancy i think +/// 1. `SampleSizeMismatch` is raylib-rs only, raylib does not do sampleSize matching checks. +/// 2. `TooManyFrames` comes from the WARNING behavior in raylib `UpdateAudioStreamInLockedState`: https://github.com/raysan5/raylib/blob/master/src/raudio.c#L2662 +#[derive(Error, Debug)] +pub enum UpdateAudioStreamError { + #[error("update data format must match sound: expected {expected} bits, got {provided} bits")] + SampleSizeMismatch { expected: usize, provided: usize }, + #[error("Attempting to write too many frames to buffer: provided {provided}, max {max}")] + TooManyFrames { max: usize, provided: usize }, +} + #[derive(Error, Debug)] pub enum AllocationError { - #[error("memory request does not produce a valid layout")] - InvalidLayout, + /// [`MemAlloc`](crate::ffi::MemAlloc) returned null. #[error("memory request exceeds capacity")] - ExceedsCapacity, - #[error("memory request exceeds unsigned integer maximum")] - ExceedsUIntMax, - #[error("cannot allocate less than 1 element")] - SubMinSize, + NullAlloc, + /// The size of `[T; count]` in bytes exceeds [`u32::MAX`] + /// (the largest value [`MemAlloc`](crate::ffi::MemAlloc) can be passed). + #[error("memory request in bytes exceeds unsigned integer maximum")] + IntoUIntFailed, + /// Attempted to pass 0 to [`MemAlloc`](crate::ffi::MemAlloc). + #[error("requested zero bytes of memory")] + ZeroBytes, } #[derive(Error, Debug)] @@ -52,6 +66,14 @@ pub enum CompressionError { CompressionFailed, } +#[derive(Error, Debug)] +pub enum Base64Error { + #[error("could not decode base64 data")] + DecodeFailed, + #[error("could not encode base64 data")] + EncodeFailed, +} + #[derive(Error, Debug)] pub enum LoadModelError { #[error("could not load model\npath: {path:?}")] diff --git a/raylib/src/core/math.rs b/raylib/src/core/math.rs index b304656d..53104b62 100644 --- a/raylib/src/core/math.rs +++ b/raylib/src/core/math.rs @@ -389,7 +389,7 @@ impl Quaternion { } } - /// Returns a new `Quaternion` with componenets clamp to a certain interval. + /// Returns a new `Quaternion` with components clamp to a certain interval. #[inline] #[must_use] pub const fn clamp(&self, num: Range) -> Quaternion { diff --git a/raylib/src/core/mod.rs b/raylib/src/core/mod.rs index 145ff3eb..535892b3 100644 --- a/raylib/src/core/mod.rs +++ b/raylib/src/core/mod.rs @@ -4,13 +4,16 @@ mod macros; pub mod audio; pub mod automation; pub mod callbacks; +#[cfg(not(feature = "nobuild"))] pub mod camera; + pub mod collision; pub mod color { #[allow(unused_imports)] pub use crate::ffi::Color; } pub mod data; +pub mod databuf; pub mod drawing; pub mod error; pub mod file; diff --git a/raylib/src/core/models.rs b/raylib/src/core/models.rs index a2030c9b..4bde7e6f 100644 --- a/raylib/src/core/models.rs +++ b/raylib/src/core/models.rs @@ -454,7 +454,7 @@ pub trait RaylibMesh: AsRef + AsMut { /// Vertex indices (in case vertex data comes indexed) #[inline] #[must_use] - fn indicies(&self) -> &[u16] { + fn indices(&self) -> &[u16] { unsafe { std::slice::from_raw_parts( self.as_ref().indices as *const u16, @@ -465,7 +465,7 @@ pub trait RaylibMesh: AsRef + AsMut { /// Vertex indices (in case vertex data comes indexed) #[inline] #[must_use] - fn indicies_mut(&mut self) -> &mut [u16] { + fn indices_mut(&mut self) -> &mut [u16] { unsafe { std::slice::from_raw_parts_mut( self.as_mut().indices as *mut u16, @@ -984,21 +984,21 @@ impl RaylibHandle { WeakMaterial(unsafe { ffi::LoadMaterialDefault() }) } - /// Weak materials will leak memeory if they are not unlaoded + /// Weak materials will leak memory if they are not unlaoded /// Unload material from GPU memory (VRAM) #[inline] pub unsafe fn unload_material(&mut self, _: &RaylibThread, material: WeakMaterial) { unsafe { ffi::UnloadMaterial(*material.as_ref()) } } - /// Weak models will leak memeory if they are not unlaoded + /// Weak models will leak memory if they are not unlaoded /// Unload model from GPU memory (VRAM) #[inline] pub unsafe fn unload_model(&mut self, _: &RaylibThread, model: WeakModel) { unsafe { ffi::UnloadModel(*model.as_ref()) } } - /// Weak model_animations will leak memeory if they are not unlaoded + /// Weak model_animations will leak memory if they are not unlaoded /// Unload model_animation from GPU memory (VRAM) #[inline] pub unsafe fn unload_model_animation( @@ -1009,7 +1009,7 @@ impl RaylibHandle { unsafe { ffi::UnloadModelAnimation(*model_animation.as_ref()) } } - /// Weak meshs will leak memeory if they are not unlaoded + /// Weak meshs will leak memory if they are not unlaoded /// Unload mesh from GPU memory (VRAM) #[inline] pub unsafe fn unload_mesh(&mut self, _: &RaylibThread, mesh: WeakMesh) { diff --git a/raylib/src/core/shaders.rs b/raylib/src/core/shaders.rs index 140303cf..72cfef46 100644 --- a/raylib/src/core/shaders.rs +++ b/raylib/src/core/shaders.rs @@ -91,7 +91,7 @@ impl RaylibHandle { } #[inline] #[must_use] - /// Get default shader. Modifying it modifies everthing that uses that shader + /// Get default shader. Modifying it modifies everything that uses that shader pub fn get_shader_default() -> WeakShader { unsafe { WeakShader(ffi::Shader { diff --git a/raylib/src/core/text.rs b/raylib/src/core/text.rs index 91785d12..b13231c1 100644 --- a/raylib/src/core/text.rs +++ b/raylib/src/core/text.rs @@ -200,7 +200,7 @@ impl RaylibHandle { } /// Load font data from a given memory buffer. /// `file_type` refers to the extension, e.g. ".ttf". - /// You can pass Some(...) to chars to get the desired charaters, or None to get the whole set. + /// You can pass Some(...) to chars to get the desired characters, or None to get the whole set. #[inline] #[must_use] pub fn load_font_from_memory( @@ -250,6 +250,7 @@ impl RaylibHandle { chars: Option<&str>, sdf: i32, ) -> Option { + let mut glyph_count_out: i32 = 0; unsafe { let ci_arr_ptr = match chars { Some(c) => { @@ -261,6 +262,7 @@ impl RaylibHandle { co.0.as_mut_ptr(), co.0.len().try_into().expect(TOO_MANY_CODEPOINTS), sdf, + &mut glyph_count_out, ) } None => ffi::LoadFontData( @@ -270,6 +272,7 @@ impl RaylibHandle { std::ptr::null_mut(), 0, sdf, + &mut glyph_count_out, ), }; let ci_size = if let Some(c) = chars { c.len() } else { 95 }; // raylib assumes 95 if none given diff --git a/raylib/src/core/texture.rs b/raylib/src/core/texture.rs index 5ba1692f..281917fa 100644 --- a/raylib/src/core/texture.rs +++ b/raylib/src/core/texture.rs @@ -823,7 +823,7 @@ impl Image { } } - /// Searches `image` for all occurences of `color` and replaces them with `replace` color. + /// Searches `image` for all occurrences of `color` and replaces them with `replace` color. #[inline] pub fn color_replace(&mut self, color: impl Into, replace: impl Into) { unsafe { @@ -1009,7 +1009,7 @@ impl Image { /// Get clipboard image. /// - /// NOTE: Only avaliable on Windows. Do not use if you plan to compile to other platforms. + /// NOTE: Only available on Windows. Do not use if you plan to compile to other platforms. #[cfg(target_os = "windows")] #[must_use] pub fn get_clipboard_image(&mut self) -> Result { @@ -1359,13 +1359,13 @@ impl RaylibHandle { } impl RaylibHandle { - /// Weak Textures will leak memeory if they are not unloaded + /// Weak Textures will leak memory if they are not unloaded /// Unload textures from GPU memory (VRAM) #[inline] pub unsafe fn unload_texture(&mut self, _: &RaylibThread, texture: WeakTexture2D) { unsafe { ffi::UnloadTexture(*texture.as_ref()) } } - /// Weak RenderTextures will leak memeory if they are not unloaded + /// Weak RenderTextures will leak memory if they are not unloaded /// Unload RenderTextures from GPU memory (VRAM) #[inline] pub unsafe fn unload_render_texture(&mut self, _: &RaylibThread, texture: WeakRenderTexture2D) { diff --git a/raylib/src/core/vr.rs b/raylib/src/core/vr.rs index aa0fb330..8b034c1b 100644 --- a/raylib/src/core/vr.rs +++ b/raylib/src/core/vr.rs @@ -16,7 +16,7 @@ pub struct VrDeviceInfo { /// Horizontal resolution in pixels pub h_resolution: i32, /// Vertical resolution in pixels - pub v_esolution: i32, + pub v_resolution: i32, /// Horizontal size in meters pub h_screen_size: f32, /// Vertical size in meters @@ -49,7 +49,7 @@ impl Into for &VrDeviceInfo { fn into(self) -> ffi::VrDeviceInfo { ffi::VrDeviceInfo { hResolution: self.h_resolution, // Horizontal resolution in pixels - vResolution: self.v_esolution, // Vertical resolution in pixels + vResolution: self.v_resolution, // Vertical resolution in pixels hScreenSize: self.h_screen_size, // Horizontal size in meters vScreenSize: self.v_screen_size, // Vertical size in meters eyeToScreenDistance: self.eye_to_screen_distance, // Distance between eye and display in meters diff --git a/raylib/src/lib.rs b/raylib/src/lib.rs index 6f40c879..8dc41ff8 100644 --- a/raylib/src/lib.rs +++ b/raylib/src/lib.rs @@ -18,7 +18,7 @@ Permission is granted to anyone to use this software for any purpose, including //! //! `raylib` is a safe Rust binding to [Raylib](https://www.raylib.com/), a C library for enjoying games programming. //! -//! To get started, take a look at the [`init_window`] function. This initializes Raylib and shows a window, and returns a [`RaylibHandle`]. This handle is very important, because it is the way in which one accesses the vast majority of Raylib's functionality. This means that it must not go out of scope until the game is ready to exit. You will also recieve a !Send and !Sync [`RaylibThread`] required for thread local functions. +//! To get started, take a look at the [`init_window`] function. This initializes Raylib and shows a window, and returns a [`RaylibHandle`]. This handle is very important, because it is the way in which one accesses the vast majority of Raylib's functionality. This means that it must not go out of scope until the game is ready to exit. You will also receive a !Send and !Sync [`RaylibThread`] required for thread local functions. //! //! For more control over the game window, the [`init`] function will return a [`RaylibBuilder`] which allows for tweaking various settings such as VSync, anti-aliasing, fullscreen, and so on. Calling [`RaylibBuilder::build`] will then provide a [`RaylibHandle`]. //! @@ -45,10 +45,10 @@ Permission is granted to anyone to use this software for any purpose, including //! .size(640, 480) //! .title("Hello, World") //! .build(); -//! +//! //! while !rl.window_should_close() { //! let mut d = rl.begin_drawing(&thread); -//! +//! //! d.clear_background(Color::WHITE); //! d.draw_text("Hello, world!", 12, 12, 20, Color::BLACK); //! } @@ -61,6 +61,7 @@ pub mod consts; pub mod core; pub mod ease; pub mod prelude; +#[cfg(not(feature = "nobuild"))] pub mod rgui; /// The raw, unsafe FFI binding, in case you need that escape hatch or the safe layer doesn't provide something you need. diff --git a/raylib/src/prelude.rs b/raylib/src/prelude.rs index a3b58ac9..1c047a11 100644 --- a/raylib/src/prelude.rs +++ b/raylib/src/prelude.rs @@ -28,10 +28,14 @@ pub use crate::callbacks::*; pub use crate::consts::*; pub use crate::core::audio::*; pub use crate::core::automation::*; + +#[cfg(not(feature = "nobuild"))] pub use crate::core::camera::*; + pub use crate::core::collision::*; pub use crate::core::color::*; pub use crate::core::data::*; +pub use crate::core::databuf::*; pub use crate::core::drawing::*; pub use crate::core::file::*; pub use crate::core::input::*; @@ -45,5 +49,8 @@ pub use crate::core::texture::*; pub use crate::core::vr::*; pub use crate::core::window::*; pub use crate::core::*; + +#[cfg(not(feature = "nobuild"))] pub use crate::rgui::*; + pub use crate::*; diff --git a/raylib/src/rgui/mod.rs b/raylib/src/rgui/mod.rs index 65371c85..4e922029 100644 --- a/raylib/src/rgui/mod.rs +++ b/raylib/src/rgui/mod.rs @@ -1,2 +1,3 @@ mod safe; pub use safe::*; + diff --git a/raylib/src/rgui/safe.rs b/raylib/src/rgui/safe.rs index db8d6fd1..b6c195fa 100644 --- a/raylib/src/rgui/safe.rs +++ b/raylib/src/rgui/safe.rs @@ -384,7 +384,7 @@ pub trait RaylibDrawGui { buffer.push('\0'); let (ptr, capacity) = (buffer.as_mut_ptr(), buffer.capacity()); let res = unsafe { - ffi::GuiTextBox(bounds.into(), ptr as *mut i8, capacity as i32, edit_mode) > 0 + ffi::GuiTextBox(bounds.into(), ptr as *mut c_char, capacity as i32, edit_mode) > 0 }; let cap = buffer.capacity(); @@ -599,7 +599,7 @@ pub trait RaylibDrawGui { c_title.as_ptr(), c_message.as_ptr(), c_buttons.as_ptr(), - ptr as *mut i8, + ptr as *mut c_char, capacity as i32, secret_view_active, ) diff --git a/samples/Cargo.toml b/samples/Cargo.toml index 319a40b0..fde29491 100644 --- a/samples/Cargo.toml +++ b/samples/Cargo.toml @@ -112,3 +112,7 @@ required-features = ["ringbuf"] [[bin]] name = "snake" path = "snake.rs" + +[[bin]] +name = "audio_raw_stream" +path = "./audio_raw_stream.rs" diff --git a/samples/audio_raw_stream.rs b/samples/audio_raw_stream.rs new file mode 100644 index 00000000..6319fb09 --- /dev/null +++ b/samples/audio_raw_stream.rs @@ -0,0 +1,139 @@ +use raylib::prelude::*; +use raylib::prelude::glam::vec2; +use raylib::prelude::{MouseButton::MOUSE_BUTTON_LEFT, KeyboardKey::KEY_SPACE}; +use raylib::error::UpdateAudioStreamError; + +const MAX_SAMPLES: usize = 512; +const MAX_SAMPLES_PER_UPDATE: usize = 4096; + +const SAMPLE_RATE: u32 = 44100; +const SAMPLE_RATE_HALVED: f32 = 22050.0; +const SAMPLE_SIZE: u32 = 16; + +fn main() { + let screen_width = 800; + let screen_height = 450; + let (mut raylib_handle, raylib_thread) = init() + .size(screen_width, screen_height) + .title("raylib [audio] example - raw audio streaming") + .build(); + raylib_handle.set_target_fps(30); + let raylib_audio = RaylibAudio::init_audio_device().unwrap(); + raylib_audio.set_audio_stream_buffer_size_default(MAX_SAMPLES_PER_UPDATE as i32); + let mut stream = raylib_audio.new_audio_stream(SAMPLE_RATE, SAMPLE_SIZE, 1); + let mut data: [i16; MAX_SAMPLES] = [0; MAX_SAMPLES]; + let mut write_buf: [i16; MAX_SAMPLES_PER_UPDATE] = [0; MAX_SAMPLES_PER_UPDATE]; + stream.play(); + let mut frequency = 440.0; + let mut old_frequency = 1.0; + let mut read_cursor = 0; + let mut wave_length = 1; + let mut position = vec2(0.0, 0.0); + + let sound = sound_update_test(&raylib_audio); + sound.play(); + while !raylib_handle.window_should_close() { + //original raylib c sample never reads from the initialized -100.0, -100.0 mouse position... + let mouse_position = raylib_handle.get_mouse_position(); + if raylib_handle.is_mouse_button_down(MOUSE_BUTTON_LEFT) { + frequency = 40.0 + mouse_position.y; + // This inverts the mouse position to match with X left,right position -> pan speaker left,right effect + let invert_mouse_x_position = -1.0; + let pan = invert_mouse_x_position * mouse_position.x / screen_width as f32; + stream.set_pan(pan); + } + if raylib_handle.is_key_pressed(KEY_SPACE) { + sound.play(); + } + if frequency != old_frequency { + let old_wave_length = wave_length; + wave_length = (SAMPLE_RATE_HALVED / frequency) as usize; + if wave_length > MAX_SAMPLES / 2 { + wave_length = MAX_SAMPLES / 2; + } + if wave_length < 1 { + wave_length = 1; + } + for i in 0..wave_length * 2 { + data[i] = ((2.0 * std::f32::consts::PI * i as f32 / wave_length as f32).sin() + * 32000f32) as i16; + } + // Clear the ghost sine wave drawings. (Concern: not in the original raylib c) + for j in wave_length * 2..MAX_SAMPLES { + data[j] = 0; + } + read_cursor = read_cursor * wave_length / old_wave_length; + old_frequency = frequency; + } + if stream.is_processed() { + let mut write_cursor = 0; + while write_cursor < MAX_SAMPLES_PER_UPDATE { + let mut write_length = MAX_SAMPLES_PER_UPDATE - write_cursor; + let read_length = wave_length - read_cursor; + if write_length > read_length { + write_length = read_length; + } + write_buf[write_cursor..write_cursor + write_length] + .copy_from_slice(&data[read_cursor..read_cursor + write_length]); + read_cursor = (read_cursor + write_length) % wave_length; + write_cursor += write_length; + } + if let Err(e) = stream.update(&write_buf) { + eprintln!("Failed to update sound: {e}"); + } + } + let mut draw_handle = raylib_handle.begin_drawing(&raylib_thread); + draw_handle.clear_background(Color::RAYWHITE); + draw_handle.draw_text( + &format!("sine frequency: {}", frequency as i32), + screen_width - 220, + 10, + 20, + Color::RED, + ); + draw_handle.draw_text( + "click mouse button to change frequency or pan", + 10, + 10, + 20, + Color::DARKGRAY, + ); + for i in 0..screen_width { + position.x = i as f32; + position.y = 250.0 + + 50.0 * data[i as usize * MAX_SAMPLES / screen_width as usize] as f32 / 32000f32; + draw_handle.draw_pixel_v(position, Color::RED); + } + } +} + +fn sound_update_test(raylib_audio: &RaylibAudio) -> Sound { + // This .wav acts as a placeholder for us to inject test data using Sound::update. currently `UpdateSound` in raylib's raudio.c has no examples + let mut wave = raylib_audio.new_wave("static/coin_16bit.wav").unwrap(); + wave.format(SAMPLE_RATE as i32, 16, 1); // wave file should already be 16 bit but just for emphasis here + println!( + "wave: sampleSize = {}, sampleRate = {}, channels = {}", + wave.sample_size(), + wave.sample_rate(), + wave.channels() + ); + let freq = 440.0; + let mut sound = raylib_audio.new_sound_from_wave(&wave).unwrap(); + // Notes (iann): see comment: https://github.com/meisei4/raylib-rs/blob/unstable/raylib/src/core/audio.rs#L421 + // 1. We load a 16-bit wave to show our `UpdateAudioStreamError::SampleSizeMismatch` error + // 2. We load the 32-bit up scale data -> no error + let mut sound_data_16bit: [i16; MAX_SAMPLES] = [0; MAX_SAMPLES]; + for i in 0..MAX_SAMPLES { + sound_data_16bit[i] = ((2.0 * std::f32::consts::PI * i as f32 * freq / SAMPLE_RATE as f32).sin() * 32000.0) as i16; + } + let update_result_16bit = sound.update(&sound_data_16bit); + println!("update(&sound_data_16bit) returned: {update_result_16bit:?}"); + assert!(matches!(update_result_16bit, Err(UpdateAudioStreamError::SampleSizeMismatch { .. }))); + + let mut sound_data_32bit: [f32; MAX_SAMPLES] = [0f32; MAX_SAMPLES]; + for i in 0..MAX_SAMPLES { + sound_data_32bit[i] = (2.0 * std::f32::consts::PI * i as f32 * freq / SAMPLE_RATE as f32).sin() * 32000.0; + } + let _ = sound.update(&sound_data_32bit); + sound +} diff --git a/samples/rgui.rs b/samples/rgui.rs index 7e33a437..dbcb53e0 100644 --- a/samples/rgui.rs +++ b/samples/rgui.rs @@ -292,7 +292,7 @@ pub fn main() { // Second GUI column d.gui_list_view( Rectangle::new(165.0, 25.0, 140.0, 124.0), - "Charmander;Bulbasaur;#18#Squirtel;Pikachu;Eevee;Pidgey", + "Charmander;Bulbasaur;#18#Squirtle;Pikachu;Eevee;Pidgey", &mut listViewScrollIndex, &mut listViewActive, ); diff --git a/samples/roguelike.rs b/samples/roguelike.rs index 7d19852b..148f6a00 100644 --- a/samples/roguelike.rs +++ b/samples/roguelike.rs @@ -85,7 +85,7 @@ const LEVEL_UP_FACTOR: i32 = 150; const LEVEL_SCREEN_WIDTH: i32 = 40; const CHARACTER_SCREEN_WIDTH: i32 = 30; -// We can add custom methods to raylib types with extention traits +// We can add custom methods to raylib types with extension traits pub trait RectExt: std::ops::Deref { fn center(&self) -> (i32, i32) { let r: &Rectangle = self.deref(); @@ -219,7 +219,7 @@ impl From for Col { } /// Objects in the game. Items, monsters and the player. Items go in inventory. -/// insead of the objects vector +/// instead of the objects vector #[derive(Clone, Debug, Serialize, Deserialize)] struct Object { x: i32, @@ -932,7 +932,7 @@ fn play_game( objects: &mut Vec, ) { // force FOV "recompute" through the game loop - let previous_player_positon = (-1, -1); + let previous_player_position = (-1, -1); while !rl.window_should_close() { // logic @@ -960,7 +960,7 @@ fn play_game( let mut d = rl.begin_drawing(thread); d.clear_background(Color::GRAY); let player = &objects[PLAYER]; - let fov_recompute = previous_player_positon != (player.x, player.y); + let fov_recompute = previous_player_position != (player.x, player.y); render_all(tcod, &mut d, game, objects, fov_recompute); } } @@ -1280,7 +1280,7 @@ fn cast_confuse( ); UseResult::UsedUp } else { - // no enemy fonud within maximum range + // no enemy found within maximum range game.messages .add("No enemy is close enough to strike.", Color::RED); UseResult::Cancelled @@ -1441,7 +1441,7 @@ fn move_towards(id: usize, target_x: i32, target_y: i32, map: &Map, objects: &mu let dy = target_y - objects[id].y; let distance = ((dx.pow(2) + dy.pow(2)) as f32).sqrt(); - // normalize it to length 1 (preseving direction), then round it + // normalize it to length 1 (preserving direction), then round it // convert to integer so the movement is restricted to the map grid let dx = (dx as f32 / distance).round() as i32; let dy = (dy as f32 / distance).round() as i32; diff --git a/samples/static/coin_16bit.wav b/samples/static/coin_16bit.wav new file mode 100644 index 00000000..fbf13c86 Binary files /dev/null and b/samples/static/coin_16bit.wav differ diff --git a/samples/yaw_pitch_roll.rs b/samples/yaw_pitch_roll.rs index c5cba2a6..9d03c061 100644 --- a/samples/yaw_pitch_roll.rs +++ b/samples/yaw_pitch_roll.rs @@ -30,7 +30,7 @@ fn main() { .unwrap(); t.gen_texture_mipmaps(); t.unwrap() - // Because we are unwraping we are required to manually unload the texture and can't rely on Drop. + // Because we are unwrapping we are required to manually unload the texture and can't rely on Drop. // We don't do that here since we don't need to unload until the end of main anyway. }; mats[raylib::consts::MaterialMapIndex::MATERIAL_MAP_ALBEDO as usize].texture = texture; @@ -183,7 +183,7 @@ fn main() { }) }); } - // Draw 3D model (recomended to draw 3D always before 2D) + // Draw 3D model (recommended to draw 3D always before 2D) d.draw_mode3D(camera, |mut d| { d.draw_model(&model, Vector3::new(0.0, 6.0, 0.0), 1.0, Color::WHITE); // Draw 3d model with texture d.draw_grid(10, 10.0); diff --git a/typos.toml b/typos.toml new file mode 100644 index 00000000..2747ee43 --- /dev/null +++ b/typos.toml @@ -0,0 +1,25 @@ +[files] +extend-exclude = [ + "target/*", + "raylib-sys/raylib/*", + "raylib-sys/binding/*", + "docs/*", + "showcase/*", + "samples/static/*", + "*/*.png", + "*/*.fs", +] + +# Match Inside a Word - Case Insensitive +[default.extend-words] +ease = "ease" +easer = "easer" + +[type.fs] +extend-glob = ["*.fs"] +check-file = false + + +[type.png] +extend-glob = ["*.png"] +check-file = false