diff --git a/Cargo.toml b/Cargo.toml index 62e081c9..ddb3ac40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ include = [ ] edition = "2021" resolver = "2" -rust-version = "1.70" +rust-version = "1.71" [package.metadata.docs.rs] all-features = true @@ -59,7 +59,6 @@ sha2 = { version = "0.10.8", default-features = false } slab = { version = "0.4.9", default-features = false } smallstr = { version = "0.3.0", default-features = false } snafu = { version = "0.8.0", default-features = false } -unicase = "2.6.0" # std image = { version = "0.25.0", features = [ diff --git a/crates/livesplit-auto-splitting/Cargo.toml b/crates/livesplit-auto-splitting/Cargo.toml index f4e2a1c8..5fbd61d0 100644 --- a/crates/livesplit-auto-splitting/Cargo.toml +++ b/crates/livesplit-auto-splitting/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" description = "livesplit-auto-splitting is a library that provides a runtime for running auto splitters that can control a speedrun timer. These auto splitters are provided as WebAssembly modules." keywords = ["speedrun", "timer", "livesplit", "auto-splitting"] edition = "2021" +rust-version = "1.74" [dependencies] anyhow = { version = "1.0.45", default-features = false } diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 6eba8184..621fe334 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -551,6 +551,7 @@ missing_docs, rust_2018_idioms )] +#![forbid(clippy::incompatible_msrv)] mod process; mod runtime; diff --git a/crates/livesplit-hotkey/Cargo.toml b/crates/livesplit-hotkey/Cargo.toml index 6aca3f38..b4654987 100644 --- a/crates/livesplit-hotkey/Cargo.toml +++ b/crates/livesplit-hotkey/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" description = "livesplit-hotkey provides cross-platform global hotkey hooks." keywords = ["speedrun", "timer", "livesplit", "hotkey", "keyboard"] edition = "2021" +rust-version = "1.73" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = [ diff --git a/crates/livesplit-hotkey/src/lib.rs b/crates/livesplit-hotkey/src/lib.rs index dcb8ad29..e6ba8b79 100644 --- a/crates/livesplit-hotkey/src/lib.rs +++ b/crates/livesplit-hotkey/src/lib.rs @@ -6,6 +6,7 @@ missing_docs, rust_2018_idioms )] +#![forbid(clippy::incompatible_msrv)] #![cfg_attr(not(feature = "std"), no_std)] //! `livesplit-hotkey` is a crate that allows listening to hotkeys even when the diff --git a/crates/livesplit-title-abbreviations/Cargo.toml b/crates/livesplit-title-abbreviations/Cargo.toml index 525466f0..5841a7a8 100644 --- a/crates/livesplit-title-abbreviations/Cargo.toml +++ b/crates/livesplit-title-abbreviations/Cargo.toml @@ -8,6 +8,4 @@ license = "MIT OR Apache-2.0" description = "livesplit-title-abbreviations encapsulates the algorithm that LiveSplit uses to abbreviate game titles." keywords = ["speedrun", "timer", "livesplit", "title", "abbreviation"] edition = "2018" - -[dependencies] -unicase = "2.6.0" +rust-version = "1.70" diff --git a/crates/livesplit-title-abbreviations/src/lib.rs b/crates/livesplit-title-abbreviations/src/lib.rs index cbe1b95a..3ad04901 100644 --- a/crates/livesplit-title-abbreviations/src/lib.rs +++ b/crates/livesplit-title-abbreviations/src/lib.rs @@ -9,6 +9,7 @@ // missing_docs, rust_2018_idioms )] +#![forbid(clippy::incompatible_msrv)] #![no_std] extern crate alloc; diff --git a/src/lib.rs b/src/lib.rs index c089f784..692a9fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ missing_docs, rust_2018_idioms )] +#![forbid(clippy::incompatible_msrv)] // Clippy false positives #![allow( clippy::blocks_in_conditions, diff --git a/src/platform/no_std/time.rs b/src/platform/no_std/time.rs index 8816c9f9..d12ad259 100644 --- a/src/platform/no_std/time.rs +++ b/src/platform/no_std/time.rs @@ -38,6 +38,7 @@ pub fn register_clock(clock: impl Clock) { } #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] +#[repr(transparent)] pub struct Instant(Duration); impl Instant { diff --git a/src/platform/normal/mod.rs b/src/platform/normal/mod.rs index 5eaa9ddf..d8c6d5c6 100644 --- a/src/platform/normal/mod.rs +++ b/src/platform/normal/mod.rs @@ -106,6 +106,7 @@ cfg_if::cfg_if! { use core::{mem::MaybeUninit, ops::Sub}; #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] + #[repr(transparent)] pub struct Instant(Duration); impl Instant { @@ -156,6 +157,7 @@ cfg_if::cfg_if! { } #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] + #[repr(transparent)] pub struct Instant(u64); impl Instant { @@ -187,7 +189,37 @@ cfg_if::cfg_if! { } } } else { - pub use time::Instant; + use core::ops::Sub; + + #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] + #[repr(transparent)] + pub struct Instant(std::time::Instant); + + impl Instant { + /// Accesses the current point in time. + #[inline] + pub fn now() -> Self { + Self(std::time::Instant::now()) + } + } + + impl Sub for Instant { + type Output = Instant; + + #[inline] + fn sub(self, rhs: Duration) -> Instant { + Self(time::ext::InstantExt::sub_signed(self.0, rhs)) + } + } + + impl Sub for Instant { + type Output = Duration; + + #[inline] + fn sub(self, rhs: Instant) -> Duration { + time::ext::InstantExt::signed_duration_since(&self.0, rhs.0) + } + } } } diff --git a/src/platform/wasm/unknown/time.rs b/src/platform/wasm/unknown/time.rs index ecf9f051..983304c1 100644 --- a/src/platform/wasm/unknown/time.rs +++ b/src/platform/wasm/unknown/time.rs @@ -28,6 +28,7 @@ extern "C" { } #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] +#[repr(transparent)] pub struct Instant(Duration); impl Instant { diff --git a/src/platform/wasm/web/time.rs b/src/platform/wasm/web/time.rs index e8c4d8bc..e6844743 100644 --- a/src/platform/wasm/web/time.rs +++ b/src/platform/wasm/web/time.rs @@ -7,6 +7,7 @@ use web_sys::Performance; pub use time::{Duration, OffsetDateTime as DateTime}; #[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)] +#[repr(transparent)] pub struct Instant(Duration); thread_local! { diff --git a/src/rendering/web/mod.rs b/src/rendering/web/mod.rs index 2c5f7f0f..1e840edb 100644 --- a/src/rendering/web/mod.rs +++ b/src/rendering/web/mod.rs @@ -2,11 +2,11 @@ //! then be attached anywhere in the DOM with any desired positioning and size. use bytemuck::cast; +use hashbrown::HashMap; use js_sys::{Array, JsString, Uint8Array}; use std::{ array, cell::{Cell, RefCell}, - collections::HashMap, f64::consts::TAU, ops::Deref, rc::Rc, @@ -597,7 +597,19 @@ impl Renderer { match background { Background::Shader(shader) => { set_fill_style(shader, ctx, &mut self.cache, str_buf, &*scene.rectangle()); - ctx.fill_rect(0.0, 0.0, width, height); + // Instead of scaling the rectangle we need to use a + // transform so that the gradient's endpoints are + // correct. + set_transform( + ctx, + &Transform { + x: 0.0, + y: 0.0, + scale_x: width as _, + scale_y: height as _, + }, + ); + ctx.fill_rect(0.0, 0.0, 1.0, 1.0); } Background::Image(background_image, transform) => { let image = background_image.image.0.borrow(); diff --git a/src/run/editor/mod.rs b/src/run/editor/mod.rs index 91e94a13..be60c301 100644 --- a/src/run/editor/mod.rs +++ b/src/run/editor/mod.rs @@ -6,8 +6,12 @@ use super::{ComparisonError, ComparisonResult, LinkedLayout}; use crate::{ - comparison, platform::prelude::*, settings::Image, timing::ParseError as ParseTimeSpanError, - util::PopulateString, Run, Segment, Time, TimeSpan, TimingMethod, + comparison, + platform::prelude::*, + settings::Image, + timing::ParseError as ParseTimeSpanError, + util::{caseless, PopulateString}, + Run, Segment, Time, TimeSpan, TimingMethod, }; use core::{mem::swap, num::ParseIntError}; use snafu::{OptionExt, ResultExt}; @@ -758,7 +762,7 @@ impl Editor { if let Some((segment_index, my_segment)) = remaining_segments .iter_mut() .enumerate() - .find(|(_, s)| unicase::eq(segment.name(), s.name())) + .find(|(_, s)| caseless::eq(segment.name(), s.name())) { *my_segment.comparison_mut(comparison) = segment.personal_best_split_time(); remaining_segments = &mut remaining_segments[segment_index + 1..]; diff --git a/src/run/mod.rs b/src/run/mod.rs index d1185f7f..ca44dcbc 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -39,7 +39,7 @@ use crate::{ comparison::{default_generators, personal_best, ComparisonGenerator, RACE_COMPARISON_PREFIX}, platform::prelude::*, settings::Image, - util::PopulateString, + util::{caseless::matches_ascii_key, PopulateString}, AtomicDateTime, Time, TimeSpan, TimingMethod, }; use alloc::borrow::Cow; @@ -907,9 +907,9 @@ impl fmt::Display for ExtendedCategoryName<'_> { for (name, value) in self.run.metadata.speedrun_com_variables() { let name = name.trim_end_matches('?'); - if unicase::eq(value.as_str(), "yes") { + if matches_ascii_key("yes", value) { push(&[name])?; - } else if unicase::eq(value.as_str(), "no") { + } else if matches_ascii_key("no", value) { push(&["No ", value])?; } else { push(&[value])?; diff --git a/src/timing/time_span.rs b/src/timing/time_span.rs index 973d00e7..93622253 100644 --- a/src/timing/time_span.rs +++ b/src/timing/time_span.rs @@ -11,6 +11,7 @@ use snafu::{ensure, OptionExt, ResultExt}; /// A `TimeSpan` represents a certain span of time. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] pub struct TimeSpan(Duration); impl TimeSpan { diff --git a/src/timing/time_stamp.rs b/src/timing/time_stamp.rs index c41b0ce4..8774e94d 100644 --- a/src/timing/time_stamp.rs +++ b/src/timing/time_stamp.rs @@ -7,10 +7,12 @@ use core::ops::Sub; /// A `TimeStamp` stores a point in time that can be used to calculate a /// [`TimeSpan`]. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] pub struct TimeStamp(Instant); impl TimeStamp { /// Creates a new `TimeStamp`, representing the current point in time. + #[inline] pub fn now() -> Self { TimeStamp(Instant::now()) } @@ -19,6 +21,7 @@ impl TimeStamp { impl Sub for TimeStamp { type Output = TimeSpan; + #[inline] fn sub(self, rhs: TimeStamp) -> TimeSpan { TimeSpan::from(self.0 - rhs.0) } @@ -27,6 +30,7 @@ impl Sub for TimeStamp { impl Sub for TimeStamp { type Output = TimeStamp; + #[inline] fn sub(self, rhs: TimeSpan) -> TimeStamp { TimeStamp(self.0 - Duration::from(rhs)) } diff --git a/src/util/byte_parsing.rs b/src/util/byte_parsing.rs index 8b0a039f..ca35f2d3 100644 --- a/src/util/byte_parsing.rs +++ b/src/util/byte_parsing.rs @@ -22,7 +22,7 @@ pub mod big_endian { u16::from_be_bytes(self.0) } - #[cfg(any(windows, feature = "default-text-engine"))] + #[cfg(any(all(windows, feature = "std"), feature = "default-text-engine"))] pub const fn usize(self) -> usize { self.get() as usize } @@ -85,12 +85,12 @@ pub fn strip_slice<'a, T: AnyBitPattern>(cursor: &mut &'a [u8], n: usize) -> Opt Some(bytemuck::cast_slice(before)) } -#[cfg(any(windows, feature = "default-text-engine"))] +#[cfg(any(all(windows, feature = "std"), feature = "default-text-engine"))] pub fn pod(bytes: &[u8]) -> Option<&P> { Some(bytemuck::from_bytes(bytes.get(..mem::size_of::

())?)) } -#[cfg(any(windows, feature = "default-text-engine"))] +#[cfg(any(all(windows, feature = "std"), feature = "default-text-engine"))] pub fn slice(bytes: &[u8], n: usize) -> Option<&[P]> { Some(bytemuck::cast_slice(bytes.get(..n * mem::size_of::

())?)) } diff --git a/src/util/caseless.rs b/src/util/caseless.rs new file mode 100644 index 00000000..07f83935 --- /dev/null +++ b/src/util/caseless.rs @@ -0,0 +1,14 @@ +pub fn eq(a: &str, b: &str) -> bool { + Iterator::eq( + a.chars().flat_map(|c| c.to_lowercase()), + b.chars().flat_map(|c| c.to_lowercase()), + ) +} + +/// The key needs to already be lowercase. +pub fn matches_ascii_key(key: &str, input: &str) -> bool { + key.len() == input.len() + && key + .bytes() + .eq(input.bytes().map(|b| b.to_ascii_lowercase())) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 75ba6c4c..93d67fb7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod ascii_char; pub(crate) mod ascii_set; pub(crate) mod byte_parsing; +pub(crate) mod caseless; mod clear_vec; #[cfg(any(feature = "image-shrinking", feature = "svg-rendering"))] pub(crate) mod image; diff --git a/src/util/populate_string.rs b/src/util/populate_string.rs index a2512d9e..9c8ca65d 100644 --- a/src/util/populate_string.rs +++ b/src/util/populate_string.rs @@ -29,7 +29,8 @@ impl PopulateString for String { impl PopulateString for &str { // If the string doesn't fit into the capacity of the buffer, we just // allocate a new buffer instead of forcing it to reallocate, which would - // mean copying all the bytes of the previous buffer, which we don't care about. + // mean copying all the bytes of the previous buffer, which we don't care + // about. #[allow(clippy::assigning_clones)] fn populate(self, buf: &mut String) { if self.len() <= buf.capacity() {