Skip to content

Commit

Permalink
wip sendable effects #8
Browse files Browse the repository at this point in the history
  • Loading branch information
junkdog committed Sep 15, 2024
1 parent 712cf4e commit c7648cf
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 34 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ crossterm = "0.28.1"

[features]
std-duration = []
sendable = []

[[example]]
name = "open-window"
Expand Down
20 changes: 15 additions & 5 deletions src/buffer_renderer.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use ratatui::buffer::Buffer;
use ratatui::layout::{Offset, Position};
use ratatui::style::{Color, Modifier, Style};
use crate::RefCount;

/// A trait for rendering the contents of one buffer onto another.
///
Expand Down Expand Up @@ -34,6 +36,14 @@ impl BufferRenderer for Rc<RefCell<Buffer>> {
}
}

#[cfg(feature = "sendable")]
impl BufferRenderer for RefCount<Buffer> {
fn render_buffer(&self, offset: Offset, buf: &mut Buffer) {
(*self.lock().unwrap())
.render_buffer(offset, buf);
}
}

impl BufferRenderer for Buffer {
fn render_buffer(&self, offset: Offset, buf: &mut Buffer) {
blit_buffer(self, buf, offset);
Expand Down Expand Up @@ -214,19 +224,19 @@ fn color_code(color: Color, foreground: bool) -> String {
#[cfg(test)]
mod tests {
use ratatui::buffer::Buffer;
use crate::make_ref;
use super::*;

fn assert_buffer_to_buffer_copy(
offset: Offset,
expected: Buffer,
) {

let aux_buffer = Rc::new(RefCell::new(Buffer::with_lines([
let aux_buffer = make_ref(Buffer::with_lines([
"abcd",
"efgh",
"ijkl",
"mnop",
])));
]));

let mut buf = Buffer::with_lines([
". . . . ",
Expand Down Expand Up @@ -307,14 +317,14 @@ mod tests {

#[test]
fn test_render_from_larger_aux_buffer() {
let aux_buffer = Rc::new(RefCell::new(Buffer::with_lines([
let aux_buffer = make_ref(Buffer::with_lines([
"AAAAAAAAAA",
"BBBBBBBBBB",
"CCCCCCCCCC",
"DDDDDDDDDD",
"EEEEEEEEEE",
"FFFFFFFFFF",
])));
]));

let buffer = || Buffer::with_lines([
". . . . ",
Expand Down
30 changes: 27 additions & 3 deletions src/cell_filter.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use ratatui::buffer::Cell;
use ratatui::layout;
use ratatui::layout::{Margin, Position, Rect};
use ratatui::prelude::Color;
use crate::color_ext::ToRgbComponents;

#[cfg(not(feature = "sendable"))]
type PositionFnType = Rc<RefCell<dyn Fn(Position) -> bool>>;
#[cfg(feature = "sendable")]
type PositionFnType = Arc<Mutex<dyn Fn(Position) -> bool + Send>>;

/// A filter mode enables effects to operate on specific cells.
#[derive(Clone, Default)]
pub enum CellFilter {
Expand Down Expand Up @@ -33,16 +39,24 @@ pub enum CellFilter {
/// Selects cells within the specified layout, denoted by the index
Layout(layout::Layout, u16),
/// Selects cells by predicate function
PositionFn(Rc<RefCell<dyn Fn(Position) -> bool>>),
PositionFn(PositionFnType),
}

impl CellFilter {
pub fn position_fn<F>(f: F) -> Self
#[cfg(not(feature = "sendable"))]
pub fn apply_position_fn<F>(f: F) -> Self
where F: Fn(Position) -> bool + 'static
{
CellFilter::PositionFn(Rc::new(RefCell::new(f)))
}

#[cfg(feature = "sendable")]
pub fn apply_position_fn<F>(f: F) -> Self
where F: Fn(Position) -> bool + Send + 'static
{
CellFilter::PositionFn(Arc::new(Mutex::new(f)))
}

pub fn to_string(&self) -> String {
fn to_hex(c: &Color) -> String {
let (r, g, b) = c.to_rgb();
Expand Down Expand Up @@ -114,6 +128,13 @@ impl CellSelector {
}

fn valid_position(&self, pos: Position, mode: &CellFilter) -> bool {
fn apply_position_fn(f: &PositionFnType, pos: Position) -> bool {
#[cfg(not(feature = "sendable"))]
return f.borrow()(pos);
#[cfg(feature = "sendable")]
f.lock().unwrap()(pos)
}

match mode {
CellFilter::All => self.inner_area.contains(pos),
CellFilter::Layout(_, _) => self.inner_area.contains(pos),
Expand All @@ -129,7 +150,7 @@ impl CellSelector {
CellFilter::Not(m) => self.valid_position(pos, m.as_ref()),
CellFilter::FgColor(_) => self.inner_area.contains(pos),
CellFilter::BgColor(_) => self.inner_area.contains(pos),
CellFilter::PositionFn(f) => f.borrow()(pos),
CellFilter::PositionFn(f) => apply_position_fn(f, pos),
}
}

Expand Down Expand Up @@ -211,7 +232,10 @@ mod tests {
let filter = CellFilter::Layout(Layout::horizontal(&[]), 0);
assert_eq!(filter.to_string(), "layout(0)");

#[cfg(not(feature = "sendable"))]
let filter = CellFilter::PositionFn(Rc::new(RefCell::new(|_| true)));
#[cfg(feature = "sendable")]
let filter = CellFilter::PositionFn(Arc::new(Mutex::new(|_| true)));
assert_eq!(filter.to_string(), "position_fn");
}
}
11 changes: 11 additions & 0 deletions src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use ratatui::layout::Rect;
/// The `Effect` struct wraps a shader, allowing it to be configured
/// and applied to a specified area and cell selection.
pub struct Effect {
#[cfg(not(feature = "sendable"))]
shader: Box<dyn Shader>,
#[cfg(feature = "sendable")]
shader: Box<dyn Shader + Send>,
}

impl Effect {
Expand All @@ -20,12 +23,20 @@ impl Effect {
///
/// # Returns
/// * A new `Effect` instance.
#[cfg(not(feature = "sendable"))]
pub fn new<S>(shader: S) -> Self
where S: Shader + 'static
{
Self { shader: Box::new(shader) }
}

#[cfg(feature = "sendable")]
pub fn new<S>(shader: S) -> Self
where S: Shader + Send + 'static
{
Self { shader: Box::new(shader) }
}

/// Creates a new `Effect` with the specified area.
///
/// # Arguments
Expand Down
37 changes: 37 additions & 0 deletions src/features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#[cfg(feature = "sendable")]
pub(super) mod sendable {
use std::sync::{Arc, Mutex};
use ratatui::buffer::Buffer;
use crate::{CellFilter, Duration, Effect, EffectTimer};
use crate::fx::{ShaderFn, ShaderFnSignature};

// #[warn(dead_code)]
const fn ensure_send<T: Send>() {}

const _: () = {
ensure_send::<Buffer>();
ensure_send::<EffectTimer>();
ensure_send::<CellFilter>();
ensure_send::<Duration>();
ensure_send::<ShaderFnSignature<()>>();
ensure_send::<ShaderFn<()>>();
};

pub type RefCount<T> = Arc<Mutex<T>>;

pub fn make_ref<T>(value: T) -> RefCount<T> {
Arc::new(Mutex::new(value))
}
}

#[cfg(not(feature = "sendable"))]
pub(super) mod sendable {
use std::cell::RefCell;
use std::rc::Rc;

pub type RefCount<T> = Rc<RefCell<T>>;

pub fn make_ref<T>(value: T) -> RefCount<T> {
Rc::new(RefCell::new(value))
}
}
10 changes: 10 additions & 0 deletions src/fx/ansi256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,14 @@ impl Shader for Ansi256 {
fn set_cell_selection(&mut self, _strategy: CellFilter) {}

fn reset(&mut self) {}
}

#[cfg(feature = "sendable")]
mod sendable {
use super::*;

const _: () = {
const fn ensure_send<T: Send>() {}
ensure_send::<Ansi256>();
};
}
64 changes: 59 additions & 5 deletions src/fx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use prolong::{Prolong, ProlongPosition};
pub use shader_fn::*;
use slide::SlideCell;
pub use direction::*;
pub use offscreen_buffer::OffscreenRenderTarget;

use crate::{CellIterator, Duration};
use crate::{CellIterator, Duration, RefCount};
use crate::effect::{Effect, IntoEffect};
use crate::effect_timer::EffectTimer;
use crate::fx::ansi256::Ansi256;
Expand Down Expand Up @@ -117,6 +118,23 @@ mod direction;
/// foreground cell based on the elapsed time. Each cell's color is slightly offset by
/// the cell's position.
///
#[cfg(feature = "sendable")]
pub fn effect_fn<F, S, T>(state: S, timer: T, f: F) -> Effect
where
S: Clone + Send + 'static,
T: Into<EffectTimer>,
F: FnMut(&mut S, ShaderFnContext, CellIterator) + Send + 'static,
{
ShaderFn::builder()
.name("shader_fn")
.state(state)
.code(ShaderFnSignature::new_iter(f))
.timer(timer)
.build()
.into_effect()
}

#[cfg(not(feature = "sendable"))]
pub fn effect_fn<F, S, T>(state: S, timer: T, f: F) -> Effect
where
S: Clone + 'static,
Expand Down Expand Up @@ -149,6 +167,23 @@ where
///
/// # Returns
/// * An `Effect` instance that can be used with other effects or applied directly to terminal cells.
#[cfg(feature = "sendable")]
pub fn effect_fn_buf<F, S, T>(state: S, timer: T, f: F) -> Effect
where
S: Clone + Send + 'static,
T: Into<EffectTimer>,
F: FnMut(&mut S, ShaderFnContext, &mut Buffer) + 'static + Send,
{
ShaderFn::builder()
.name("shader_fn_buf")
.state(state)
.code(ShaderFnSignature::new_buffer(f))
.timer(timer)
.build()
.into_effect()
}

#[cfg(not(feature = "sendable"))]
pub fn effect_fn_buf<F, S, T>(state: S, timer: T, f: F) -> Effect
where
S: Clone + 'static,
Expand Down Expand Up @@ -455,7 +490,7 @@ pub fn translate<T: Into<EffectTimer>>(
/// Returns an `Effect` that can be used with other effects or applied directly to a buffer.
pub fn translate_buf<T: Into<EffectTimer>>(
translate_by: Offset,
aux_buffer: Rc<RefCell<Buffer>>,
aux_buffer: RefCount<Buffer>,
timer: T,
) -> Effect {
TranslateBuffer::new(aux_buffer, translate_by, timer.into()).into_effect()
Expand Down Expand Up @@ -521,13 +556,13 @@ pub fn resize_area<T: Into<EffectTimer>>(
/// use std::cell::RefCell;
/// use std::rc::Rc;
/// use ratatui::prelude::{Buffer, Color, Rect};
/// use tachyonfx::{fx, Duration, Effect, EffectTimer, Interpolation, Shader};
/// use tachyonfx::{fx, make_ref, Duration, Effect, EffectTimer, Interpolation, Shader};
///
/// let duration = Duration::from_millis(16);
/// let mut main_buffer = Buffer::empty(Rect::new(0, 0, 80, 24));
///
/// let area = Rect::new(0, 0, 80, 24);
/// let offscreen_buffer = Rc::new(RefCell::new(Buffer::empty(area)));
/// let offscreen_buffer = make_ref(Buffer::empty(area));
///
/// let fade_effect = fx::fade_to_fg(Color::Red, EffectTimer::from_ms(1000, Interpolation::Linear));
/// let mut offscreen_effect = fx::offscreen_buffer(fade_effect, offscreen_buffer.clone());
Expand All @@ -540,7 +575,7 @@ pub fn resize_area<T: Into<EffectTimer>>(
/// This example creates an offscreen buffer and applies a fade effect to it. The effect can be
/// processed independently of the main render buffer, allowing for more complex or
/// performance-intensive effects to be computed separately.
pub fn offscreen_buffer(fx: Effect, render_target: Rc<RefCell<Buffer>>) -> Effect {
pub fn offscreen_buffer(fx: Effect, render_target: OffscreenRenderTarget) -> Effect {
offscreen_buffer::OffscreenBuffer::new(fx, render_target).into_effect()
}

Expand Down Expand Up @@ -771,6 +806,25 @@ fn fade<C: Into<Color>>(
.into_effect()
}


#[cfg(feature = "sendable")]
macro_rules! invoke_fn {
// Arc<Mutex<F>> for sendable
($f:expr, $($args:expr),* $(,)?) => {
$f.lock().unwrap()($($args),*)
};
}

#[cfg(not(feature = "sendable"))]
macro_rules! invoke_fn {
// Rc<Arc<F>> for non-sendable
($f:expr, $($args:expr),* $(,)?) => {
$f.borrow_mut()($($args),*)
};
}

pub (crate) use invoke_fn;

#[cfg(test)]
mod tests {
use ratatui::prelude::Color;
Expand Down
Loading

0 comments on commit c7648cf

Please sign in to comment.