Deprecate LetNodes in favor of new scope API (#1814)

* WIP

* Start deprecating let nodes

* Replace WasmEditorApi network imports with new Scope input

* Add missing unwrap

* Add #[serde(default)] to scope_injections

* Restructure WasmEditorApi definition to be available as a TaggedValue

* Fix text node

* Use stable toolchain in nix shell again

* Code review

* FIx text node and remove all remaining warnings

* Require executor input to be 'static

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-07-10 14:18:21 +02:00
committed by GitHub
parent a17ed68008
commit 3657b37574
55 changed files with 774 additions and 980 deletions

View File

@@ -1,16 +1,15 @@
use crate::raster::ImageFrame;
use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::ViewMode;
use crate::{Color, Node};
use dyn_any::{StaticType, StaticTypeSized};
use dyn_any::{DynAny, StaticType, StaticTypeSized};
use alloc::sync::Arc;
use core::fmt::Debug;
use core::future::Future;
use core::hash::{Hash, Hasher};
use core::pin::Pin;
use core::ptr::addr_of;
use glam::DAffine2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -133,6 +132,12 @@ pub trait NodeGraphUpdateSender {
fn send(&self, message: NodeGraphUpdateMessage);
}
impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
fn send(&self, message: NodeGraphUpdateMessage) {
self.lock().as_mut().unwrap().send(message)
}
}
pub trait GetImaginatePreferences {
fn get_host_name(&self) -> &str;
}
@@ -148,7 +153,7 @@ pub enum ExportFormat {
Canvas,
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
pub struct RenderConfig {
pub viewport: Footprint,
pub export_format: ExportFormat,
@@ -157,82 +162,69 @@ pub struct RenderConfig {
pub for_export: bool,
}
pub struct EditorApi<'a, Io> {
// TODO: Is `image_frame` still used? I think it's only ever set to None.
pub image_frame: Option<ImageFrame<Color>>,
pub font_cache: &'a FontCache,
pub application_io: &'a Io,
pub node_graph_message_sender: &'a dyn NodeGraphUpdateSender,
pub imaginate_preferences: &'a dyn GetImaginatePreferences,
pub render_config: RenderConfig,
struct Logger;
impl NodeGraphUpdateSender for Logger {
fn send(&self, message: NodeGraphUpdateMessage) {
log::warn!("dispatching message with fallback node graph update sender {:?}", message);
}
}
impl<'a, Io> Clone for EditorApi<'a, Io> {
fn clone(&self) -> Self {
struct DummyPreferences;
impl GetImaginatePreferences for DummyPreferences {
fn get_host_name(&self) -> &str {
"dummy_endpoint"
}
}
pub struct EditorApi<Io> {
/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].
pub font_cache: FontCache,
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
pub application_io: Option<Arc<Io>>,
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
/// Imaginate preferences made available to the graph through the [`WasmEditorApi`].
pub imaginate_preferences: Box<dyn GetImaginatePreferences + Send + Sync>,
}
impl<Io> Eq for EditorApi<Io> {}
impl<Io: Default> Default for EditorApi<Io> {
fn default() -> Self {
Self {
image_frame: self.image_frame.clone(),
font_cache: self.font_cache,
application_io: self.application_io,
node_graph_message_sender: self.node_graph_message_sender,
imaginate_preferences: self.imaginate_preferences,
render_config: self.render_config,
font_cache: FontCache::default(),
application_io: None,
node_graph_message_sender: Box::new(Logger),
imaginate_preferences: Box::new(DummyPreferences),
}
}
}
impl<'a, T> PartialEq for EditorApi<'a, T> {
fn eq(&self, other: &Self) -> bool {
self.image_frame == other.image_frame && self.font_cache == other.font_cache
}
}
impl<'a, T> Hash for EditorApi<'a, T> {
impl<Io> Hash for EditorApi<Io> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.image_frame.hash(state);
self.font_cache.hash(state);
self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);
(self.imaginate_preferences.as_ref() as *const dyn GetImaginatePreferences).hash(state);
}
}
impl<'a, T> Debug for EditorApi<'a, T> {
impl<Io> PartialEq for EditorApi<Io> {
fn eq(&self, other: &Self) -> bool {
self.font_cache == other.font_cache
&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)
&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)
&& std::ptr::eq(self.imaginate_preferences.as_ref() as *const _, other.imaginate_preferences.as_ref() as *const _)
}
}
impl<T> Debug for EditorApi<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EditorApi").field("image_frame", &self.image_frame).field("font_cache", &self.font_cache).finish()
f.debug_struct("EditorApi").field("font_cache", &self.font_cache).finish()
}
}
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<'_, T> {
type Static = EditorApi<'static, T::Static>;
}
impl<'a, T> AsRef<EditorApi<'a, T>> for EditorApi<'a, T> {
fn as_ref(&self) -> &EditorApi<'a, T> {
self
}
}
// Required for the EndLetNode
impl<'a, IO> From<EditorApi<'a, IO>> for Footprint {
fn from(value: EditorApi<'a, IO>) -> Self {
value.render_config.viewport
}
}
// Required for the EndLetNode
impl<'a, IO> From<EditorApi<'a, IO>> for () {
fn from(_value: EditorApi<'a, IO>) -> Self {}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtractImageFrame;
impl<'a: 'input, 'input, T> Node<'input, EditorApi<'a, T>> for ExtractImageFrame {
type Output = ImageFrame<Color>;
fn eval(&'input self, editor_api: EditorApi<'a, T>) -> Self::Output {
editor_api.image_frame.unwrap_or(ImageFrame::identity())
}
}
impl ExtractImageFrame {
pub fn new() -> Self {
Self
}
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> {
type Static = EditorApi<T::Static>;
}

View File

@@ -24,7 +24,7 @@ impl ClickTarget {
/// Does the click target intersect the rectangle
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
// Check if the matrix is not invertible
if layer_transform.matrix2.determinant().abs() <= std::f64::EPSILON {
if layer_transform.matrix2.determinant().abs() <= f64::EPSILON {
return false;
}
let quad = layer_transform.inverse() * document_quad;

View File

@@ -171,7 +171,7 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> +
}
#[cfg(feature = "alloc")]
pub use crate::application_io::{ExtractImageFrame, SurfaceFrame, SurfaceId};
pub use crate::application_io::{SurfaceFrame, SurfaceId};
#[cfg(feature = "wasm")]
pub type WasmSurfaceHandle = application_io::SurfaceHandle<web_sys::HtmlCanvasElement>;
#[cfg(feature = "wasm")]

View File

@@ -4,7 +4,6 @@ use core::future::Future;
#[cfg(feature = "alloc")]
use alloc::sync::Arc;
use core::cell::Cell;
use core::marker::PhantomData;
use core::pin::Pin;
/// Caches the output of a given Node and acts as a proxy
@@ -46,7 +45,7 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
}
/// Caches the output of a given Node and acts as a proxy.
/// In contrast to the relgular `MemoNode`. This node ignores all input.
/// In contrast to the regular `MemoNode`. This node ignores all input.
/// Using this node might result in the document not updating properly,
/// use with caution.
#[derive(Default)]
@@ -137,85 +136,3 @@ impl<I, T, N> MonitorNode<I, T, N> {
MonitorNode { io: Cell::new(None), node }
}
}
// Caches the output of a given Node and acts as a proxy
/// It provides two modes of operation, it can either be set
/// when calling the node with a `Some<T>` variant or the last
/// value that was added is returned when calling it with `None`
#[derive(Default)]
pub struct LetNode<T> {
// We have to use an append only data structure to make sure the references
// to the cache entries are always valid
// TODO: We only ever access the last value so there is not really a reason for us
// to store the previous entries. This should be reworked in the future
cache: Cell<Option<T>>,
}
impl<'i, T: 'i + Clone> Node<'i, Option<T>> for LetNode<T> {
type Output = T;
fn eval(&'i self, input: Option<T>) -> Self::Output {
if let Some(input) = input {
self.cache.set(Some(input.clone()));
input
} else {
let value = self.cache.take();
self.cache.set(value.clone());
value.expect("LetNode was not initialized. This can happen if you try to evaluate a node that depends on the EditorApi in the node_registry")
}
}
fn reset(&self) {
self.cache.set(None);
}
}
impl<T> LetNode<T> {
pub fn new() -> LetNode<T> {
LetNode { cache: Default::default() }
}
}
/// Caches the output of a given Node and acts as a proxy
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EndLetNode<Input, Parameter> {
input: Input,
parameter: PhantomData<Parameter>,
}
impl<'i, T: 'i, Parameter: 'i + From<T>, Input> Node<'i, T> for EndLetNode<Input, Parameter>
where
Input: Node<'i, Parameter>,
{
type Output = <Input>::Output;
fn eval(&'i self, t: T) -> Self::Output {
let result = self.input.eval(Parameter::from(t));
result
}
}
impl<Input, Parameter> EndLetNode<Input, Parameter> {
pub const fn new(input: Input) -> EndLetNode<Input, Parameter> {
EndLetNode { input, parameter: PhantomData }
}
}
pub use crate::ops::SomeNode as InitNode;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct RefNode<T, Let> {
let_node: Let,
_t: PhantomData<T>,
}
impl<'i, T: 'i, Let> Node<'i, ()> for RefNode<T, Let>
where
Let: for<'a> Node<'a, Option<T>>,
{
type Output = <Let as Node<'i, Option<T>>>::Output;
fn eval(&'i self, _: ()) -> Self::Output {
self.let_node.eval(None)
}
}
impl<Let, T> RefNode<T, Let> {
pub const fn new(let_node: Let) -> RefNode<T, Let> {
RefNode { let_node, _t: PhantomData }
}
}

View File

@@ -319,7 +319,7 @@ fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, o
};
// Input levels (Range: 0-1)
let highlights_minus_shadows = (input_highlights - input_shadows).max(f32::EPSILON).min(1.);
let highlights_minus_shadows = (input_highlights - input_shadows).clamp(f32::EPSILON, 1.);
let color = color.map_rgb(|c| ((c - input_shadows).max(0.) / highlights_minus_shadows).min(1.));
// Midtones (Range: 0-1)

View File

@@ -110,7 +110,7 @@ impl CubicSplines {
// Eliminate the current column in all rows below the current one
for row_below_current in row + 1..4 {
assert!(augmented_matrix[row][row].abs() > core::f32::EPSILON);
assert!(augmented_matrix[row][row].abs() > f32::EPSILON);
let scale_factor = augmented_matrix[row_below_current][row] / augmented_matrix[row][row];
for col in row..5 {
@@ -122,7 +122,7 @@ impl CubicSplines {
// Gaussian elimination: back substitution
let mut solutions = [0.; 4];
for col in (0..4).rev() {
assert!(augmented_matrix[col][col].abs() > core::f32::EPSILON);
assert!(augmented_matrix[col][col].abs() > f32::EPSILON);
solutions[col] = augmented_matrix[col][4] / augmented_matrix[col][col];

View File

@@ -15,7 +15,7 @@ pub struct TextGeneratorNode<Text, FontName, Size> {
}
#[node_fn(TextGeneratorNode)]
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
fn generate_text<'a: 'input, T: 'a>(editor: &'a EditorApi<T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
}

View File

@@ -21,7 +21,7 @@ impl Default for Font {
}
}
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)]
pub struct FontCache {
/// Actual font file data used for rendering a font with ttf_parser and rustybuzz
font_file_data: HashMap<Font, Vec<u8>>,

View File

@@ -39,6 +39,23 @@ impl<T> From<T> for ValueNode<T> {
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);
impl<'i, T: 'i + AsRef<U>, U: 'i> Node<'i, ()> for AsRefNode<T, U> {
type Output = &'i U;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
self.0.as_ref()
}
}
impl<T: AsRef<U>, U> AsRefNode<T, U> {
pub const fn new(value: T) -> AsRefNode<T, U> {
AsRefNode(value, PhantomData)
}
}
#[derive(Default, Debug, Clone)]
pub struct RefCellMutNode<T>(pub RefCell<T>);