Skip to content

Commit

Permalink
Merge pull request #2415 from iced-rs/feature/canvas-cache-groups
Browse files Browse the repository at this point in the history
`canvas::Cache` Grouping
  • Loading branch information
hecrj authored Apr 30, 2024
2 parents 24501fd + 62433a6 commit 89892f1
Show file tree
Hide file tree
Showing 15 changed files with 398 additions and 148 deletions.
1 change: 1 addition & 0 deletions examples/clock/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ iced.workspace = true
iced.features = ["canvas", "tokio", "debug"]

time = { version = "0.3", features = ["local-offset"] }
tracing-subscriber = "0.3"
2 changes: 2 additions & 0 deletions examples/clock/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use iced::{
};

pub fn main() -> iced::Result {
tracing_subscriber::fmt::init();

iced::program("Clock - Iced", Clock::update, Clock::view)
.subscription(Clock::subscription)
.theme(Clock::theme)
Expand Down
1 change: 1 addition & 0 deletions examples/the_matrix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ iced.workspace = true
iced.features = ["canvas", "tokio", "debug"]

rand = "0.8"
tracing-subscriber = "0.3"
39 changes: 19 additions & 20 deletions examples/the_matrix/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
use iced::mouse;
use iced::time::{self, Instant};
use iced::widget::canvas;
use iced::widget::canvas::{Cache, Geometry};
use iced::{
Color, Element, Font, Length, Point, Rectangle, Renderer, Subscription,
Theme,
};

use std::cell::RefCell;

pub fn main() -> iced::Result {
tracing_subscriber::fmt::init();

iced::program("The Matrix - Iced", TheMatrix::update, TheMatrix::view)
.subscription(TheMatrix::subscription)
.antialiasing(true)
.run()
}

#[derive(Default)]
struct TheMatrix {
ticks: usize,
backgrounds: Vec<Cache>,
tick: usize,
}

#[derive(Debug, Clone, Copy)]
Expand All @@ -28,7 +31,7 @@ impl TheMatrix {
fn update(&mut self, message: Message) {
match message {
Message::Tick(_now) => {
self.ticks += 1;
self.tick += 1;
}
}
}
Expand All @@ -45,35 +48,31 @@ impl TheMatrix {
}
}

impl Default for TheMatrix {
fn default() -> Self {
let mut backgrounds = Vec::with_capacity(30);
backgrounds.resize_with(30, Cache::default);

Self {
ticks: 0,
backgrounds,
}
}
}

impl<Message> canvas::Program<Message> for TheMatrix {
type State = ();
type State = RefCell<Vec<canvas::Cache>>;

fn draw(
&self,
_state: &Self::State,
state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<Geometry> {
) -> Vec<canvas::Geometry> {
use rand::distributions::Distribution;
use rand::Rng;

const CELL_SIZE: f32 = 10.0;

vec![self.backgrounds[self.ticks % self.backgrounds.len()].draw(
let mut caches = state.borrow_mut();

if caches.is_empty() {
let group = canvas::Group::unique();

caches.resize_with(30, || canvas::Cache::with_group(group));
}

vec![caches[self.tick % caches.len()].draw(
renderer,
bounds.size(),
|frame| {
Expand Down
189 changes: 189 additions & 0 deletions graphics/src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//! Cache computations and efficiently reuse them.
use std::cell::RefCell;
use std::fmt;
use std::sync::atomic::{self, AtomicU64};

/// A simple cache that stores generated values to avoid recomputation.
///
/// Keeps track of the last generated value after clearing.
pub struct Cache<T> {
group: Group,
state: RefCell<State<T>>,
}

impl<T> Cache<T> {
/// Creates a new empty [`Cache`].
pub fn new() -> Self {
Cache {
group: Group::singleton(),
state: RefCell::new(State::Empty { previous: None }),
}
}

/// Creates a new empty [`Cache`] with the given [`Group`].
///
/// Caches within the same group may reuse internal rendering storage.
///
/// You should generally group caches that are likely to change
/// together.
pub fn with_group(group: Group) -> Self {
assert!(
!group.is_singleton(),
"The group {group:?} cannot be shared!"
);

Cache {
group,
state: RefCell::new(State::Empty { previous: None }),
}
}

/// Returns the [`Group`] of the [`Cache`].
pub fn group(&self) -> Group {
self.group
}

/// Puts the given value in the [`Cache`].
///
/// Notice that, given this is a cache, a mutable reference is not
/// necessary to call this method. You can safely update the cache in
/// rendering code.
pub fn put(&self, value: T) {
*self.state.borrow_mut() = State::Filled { current: value };
}

/// Returns a reference cell to the internal [`State`] of the [`Cache`].
pub fn state(&self) -> &RefCell<State<T>> {
&self.state
}

/// Clears the [`Cache`].
pub fn clear(&self)
where
T: Clone,
{
use std::ops::Deref;

let previous = match self.state.borrow().deref() {
State::Empty { previous } => previous.clone(),
State::Filled { current } => Some(current.clone()),
};

*self.state.borrow_mut() = State::Empty { previous };
}
}

/// A cache group.
///
/// Caches that share the same group generally change together.
///
/// A cache group can be used to implement certain performance
/// optimizations during rendering, like batching or sharing atlases.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Group {
id: u64,
is_singleton: bool,
}

impl Group {
/// Generates a new unique cache [`Group`].
pub fn unique() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(0);

Self {
id: NEXT.fetch_add(1, atomic::Ordering::Relaxed),
is_singleton: false,
}
}

/// Returns `true` if the [`Group`] can only ever have a
/// single [`Cache`] in it.
///
/// This is the default kind of [`Group`] assigned when using
/// [`Cache::new`].
///
/// Knowing that a [`Group`] will never be shared may be
/// useful for rendering backends to perform additional
/// optimizations.
pub fn is_singleton(self) -> bool {
self.is_singleton
}

fn singleton() -> Self {
Self {
is_singleton: true,
..Self::unique()
}
}
}

impl<T> fmt::Debug for Cache<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::ops::Deref;

let state = self.state.borrow();

match state.deref() {
State::Empty { previous } => {
write!(f, "Cache::Empty {{ previous: {previous:?} }}")
}
State::Filled { current } => {
write!(f, "Cache::Filled {{ current: {current:?} }}")
}
}
}
}

impl<T> Default for Cache<T> {
fn default() -> Self {
Self::new()
}
}

/// The state of a [`Cache`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State<T> {
/// The [`Cache`] is empty.
Empty {
/// The previous value of the [`Cache`].
previous: Option<T>,
},
/// The [`Cache`] is filled.
Filled {
/// The current value of the [`Cache`]
current: T,
},
}

/// A piece of data that can be cached.
pub trait Cached: Sized {
/// The type of cache produced.
type Cache: Clone;

/// Loads the [`Cache`] into a proper instance.
///
/// [`Cache`]: Self::Cache
fn load(cache: &Self::Cache) -> Self;

/// Caches this value, producing its corresponding [`Cache`].
///
/// [`Cache`]: Self::Cache
fn cache(self, group: Group, previous: Option<Self::Cache>) -> Self::Cache;
}

#[cfg(debug_assertions)]
impl Cached for () {
type Cache = ();

fn load(_cache: &Self::Cache) -> Self {}

fn cache(
self,
_group: Group,
_previous: Option<Self::Cache>,
) -> Self::Cache {
}
}
24 changes: 0 additions & 24 deletions graphics/src/cached.rs

This file was deleted.

2 changes: 1 addition & 1 deletion graphics/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pub use text::Text;

pub use crate::gradient::{self, Gradient};

use crate::cache::Cached;
use crate::core::{self, Size};
use crate::Cached;

/// A renderer capable of drawing some [`Self::Geometry`].
pub trait Renderer: core::Renderer {
Expand Down
Loading

0 comments on commit 89892f1

Please sign in to comment.