diff --git a/Cargo.lock b/Cargo.lock index 08f52b1c7..50ea5ee19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,7 @@ dependencies = [ "atomic_refcell", "cint", "emath", + "nohash-hasher", "parking_lot", "serde", ] @@ -1492,6 +1493,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "6.2.1" diff --git a/README.md b/README.md index 7a0b13215..ade66ba86 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ ui.label(format!("Hello '{}', age {}", name, age)); * Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs) * Modular: You should be able to use small parts of egui and combine them in new ways * Safe: there is no `unsafe` code in egui -* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell) +* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`nohash-hasher`](https://crates.io/crates/nohash-hasher) egui is *not* a framework. egui is a library you call into, not an environment you program for. diff --git a/egui_demo_lib/src/apps/demo/code_editor.rs b/egui_demo_lib/src/apps/demo/code_editor.rs index 003955967..56b7f421c 100644 --- a/egui_demo_lib/src/apps/demo/code_editor.rs +++ b/egui_demo_lib/src/apps/demo/code_editor.rs @@ -223,6 +223,7 @@ struct Highligher {} #[cfg(not(feature = "syntect"))] impl Highligher { + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] fn highlight(&self, is_dark_mode: bool, mut text: &str, _language: &str) -> Option { // Extremely simple syntax highlighter for when we compile without syntect @@ -269,7 +270,7 @@ impl Highligher { while !text.is_empty() { if text.starts_with("//") { - let end = text.find('\n').unwrap_or(text.len()); + let end = text.find('\n').unwrap_or_else(|| text.len()); job.append(&text[..end], 0.0, comment_format); text = &text[end..]; } else if text.starts_with('"') { @@ -277,14 +278,14 @@ impl Highligher { .find('"') .map(|i| i + 2) .or_else(|| text.find('\n')) - .unwrap_or(text.len()); + .unwrap_or_else(|| text.len()); job.append(&text[..end], 0.0, quoted_string_format); text = &text[end..]; } else if text.starts_with(|c: char| c.is_ascii_alphanumeric()) { let end = text[1..] .find(|c: char| !c.is_ascii_alphanumeric()) .map(|i| i + 1) - .unwrap_or(text.len()); + .unwrap_or_else(|| text.len()); let word = &text[..end]; if is_keyword(word) { job.append(word, 0.0, keyword_format); @@ -296,7 +297,7 @@ impl Highligher { let end = text[1..] .find(|c: char| !c.is_ascii_whitespace()) .map(|i| i + 1) - .unwrap_or(text.len()); + .unwrap_or_else(|| text.len()); job.append(&text[..end], 0.0, whitespace_format); text = &text[end..]; } else { diff --git a/epaint/Cargo.toml b/epaint/Cargo.toml index 4b1962192..3e17af92c 100644 --- a/epaint/Cargo.toml +++ b/epaint/Cargo.toml @@ -31,6 +31,7 @@ ab_glyph = "0.2.11" ahash = { version = "0.7", features = ["std"], default-features = false } atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use. cint = { version = "^0.2.2", optional = true } +nohash-hasher = "0.2" parking_lot = { version = "0.11", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. serde = { version = "1", features = ["derive"], optional = true } diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 290c1c3fd..761d25906 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -189,12 +189,3 @@ pub(crate) fn f32_hash(state: &mut H, f: f32) { f.to_bits().hash(state) } } - -#[inline(always)] -pub(crate) fn f32_eq(a: f32, b: f32) -> bool { - if a.is_nan() && b.is_nan() { - true - } else { - a == b - } -} diff --git a/epaint/src/stroke.rs b/epaint/src/stroke.rs index c60bc411a..371ccc10b 100644 --- a/epaint/src/stroke.rs +++ b/epaint/src/stroke.rs @@ -1,9 +1,11 @@ +#![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine + use super::*; /// Describes the width and color of a line. /// /// The default stroke is the same as [`Stroke::none`]. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct Stroke { pub width: f32, @@ -44,12 +46,3 @@ impl std::hash::Hash for Stroke { color.hash(state); } } - -impl PartialEq for Stroke { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.color == other.color && crate::f32_eq(self.width, other.width) - } -} - -impl std::cmp::Eq for Stroke {} diff --git a/epaint/src/text/fonts.rs b/epaint/src/text/fonts.rs index ae737cc50..6ae235f39 100644 --- a/epaint/src/text/fonts.rs +++ b/epaint/src/text/fonts.rs @@ -4,8 +4,6 @@ use std::{ sync::Arc, }; -use ahash::AHashMap; - use crate::{ mutex::Mutex, text::{ @@ -321,8 +319,8 @@ impl Fonts { /// [`Self::layout_delayed_color`]. /// /// The implementation uses memoization so repeated calls are cheap. - pub fn layout_job(&self, job: impl Into>) -> Arc { - self.galley_cache.lock().layout(self, job.into()) + pub fn layout_job(&self, job: LayoutJob) -> Arc { + self.galley_cache.lock().layout(self, job) } /// Will wrap text at the given width and line break at `\n`. @@ -400,19 +398,25 @@ struct CachedGalley { struct GalleyCache { /// Frame counter used to do garbage collection on the cache generation: u32, - cache: AHashMap, CachedGalley>, + cache: nohash_hasher::IntMap, } impl GalleyCache { - fn layout(&mut self, fonts: &Fonts, job: Arc) -> Arc { - match self.cache.entry(job.clone()) { + fn layout(&mut self, fonts: &Fonts, job: LayoutJob) -> Arc { + let hash = { + let mut hasher = ahash::AHasher::new_with_keys(123, 456); // TODO: even faster hasher? + job.hash(&mut hasher); + hasher.finish() + }; + + match self.cache.entry(hash) { std::collections::hash_map::Entry::Occupied(entry) => { let cached = entry.into_mut(); cached.last_used = self.generation; cached.galley.clone() } std::collections::hash_map::Entry::Vacant(entry) => { - let galley = super::layout(fonts, job); + let galley = super::layout(fonts, job.into()); let galley = Arc::new(galley); entry.insert(CachedGalley { last_used: self.generation, diff --git a/epaint/src/text/text_layout_types.rs b/epaint/src/text/text_layout_types.rs index b38352b32..ded3d2c47 100644 --- a/epaint/src/text/text_layout_types.rs +++ b/epaint/src/text/text_layout_types.rs @@ -1,3 +1,5 @@ +#![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine + use std::ops::Range; use std::sync::Arc; @@ -10,7 +12,7 @@ use emath::*; /// This supports mixing different fonts, color and formats (underline etc). /// /// Pass this to [`Fonts::layout_job]` or [`crate::text::layout`]. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct LayoutJob { /// The complete text of this job, referenced by `LayoutSection`. pub text: String, // TODO: Cow<'static, str> @@ -120,22 +122,9 @@ impl std::hash::Hash for LayoutJob { } } -impl PartialEq for LayoutJob { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.text == other.text - && self.sections == other.sections - && crate::f32_eq(self.wrap_width, other.wrap_width) - && crate::f32_eq(self.first_row_min_height, other.first_row_min_height) - && self.break_on_newline == other.break_on_newline - } -} - -impl std::cmp::Eq for LayoutJob {} - // ---------------------------------------------------------------------------- -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct LayoutSection { /// Can be used for first row indentation. pub leading_space: f32, @@ -158,20 +147,9 @@ impl std::hash::Hash for LayoutSection { } } -impl PartialEq for LayoutSection { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - crate::f32_eq(self.leading_space, other.leading_space) - && self.byte_range == other.byte_range - && self.format == other.format - } -} - -impl std::cmp::Eq for LayoutSection {} - // ---------------------------------------------------------------------------- -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq)] pub struct TextFormat { pub style: TextStyle, /// Text color