Merge remote-tracking branch 'origin/master' into spiral-node

This commit is contained in:
0SlowPoke0
2025-07-10 13:27:21 +05:30
119 changed files with 5708 additions and 4312 deletions

View File

@@ -403,7 +403,7 @@ mod test {
blend_mode: BlendMode::Normal,
},
}],
BrushCache::new_proto(),
BrushCache::default(),
)
.await;
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);

View File

@@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU;
use graphene_core::raster_types::Raster;
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::Arc;
use std::sync::Mutex;
use std::hash::Hasher;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten.
static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
struct BrushCacheImpl {
unique_id: u64,
// The full previous input that was cached.
prev_input: Vec<BrushStroke>,
@@ -90,9 +95,29 @@ impl BrushCacheImpl {
}
}
impl Default for BrushCacheImpl {
fn default() -> Self {
Self {
unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst),
prev_input: Vec::new(),
background: Default::default(),
blended_image: Default::default(),
last_stroke_texture: Default::default(),
brush_texture_cache: HashMap::new(),
}
}
}
impl PartialEq for BrushCacheImpl {
fn eq(&self, other: &Self) -> bool {
self.unique_id == other.unique_id
}
}
impl Hash for BrushCacheImpl {
// Zero hash.
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
fn hash<H: Hasher>(&self, state: &mut H) {
self.unique_id.hash(state);
}
}
#[derive(Clone, Debug, Default)]
@@ -103,46 +128,26 @@ pub struct BrushPlan {
pub first_stroke_point_skip: usize,
}
#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub struct BrushCache {
inner: Arc<Mutex<BrushCacheImpl>>,
proto: bool,
}
impl Default for BrushCache {
fn default() -> Self {
Self::new_proto()
}
}
#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
pub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);
// A bit of a cursed implementation to work around the current node system.
// The original object is a 'prototype' that when cloned gives you a independent
// new object. Any further clones however are all the same underlying cache object.
impl Clone for BrushCache {
fn clone(&self) -> Self {
if self.proto {
let inner_val = self.inner.lock().unwrap();
Self {
inner: Arc::new(Mutex::new(inner_val.clone())),
proto: false,
}
} else {
Self {
inner: Arc::clone(&self.inner),
proto: false,
}
}
Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))
}
}
impl PartialEq for BrushCache {
fn eq(&self, other: &Self) -> bool {
if Arc::ptr_eq(&self.inner, &other.inner) {
if Arc::ptr_eq(&self.0, &other.0) {
return true;
}
let s = self.inner.lock().unwrap();
let o = other.inner.lock().unwrap();
let s = self.0.lock().unwrap();
let o = other.0.lock().unwrap();
*s == *o
}
@@ -150,35 +155,28 @@ impl PartialEq for BrushCache {
impl Hash for BrushCache {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.lock().unwrap().hash(state);
self.0.lock().unwrap().hash(state);
}
}
impl BrushCache {
pub fn new_proto() -> Self {
Self {
inner: Default::default(),
proto: true,
}
}
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.0.lock().unwrap();
inner.compute_brush_plan(background, input)
}
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.0.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture)
}
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
let inner = self.inner.lock().unwrap();
let inner = self.0.lock().unwrap();
inner.brush_texture_cache.get(style).cloned()
}
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
let mut inner = self.inner.lock().unwrap();
let mut inner = self.0.lock().unwrap();
inner.brush_texture_cache.insert(style, brush);
}
}

View File

@@ -356,7 +356,7 @@ pub struct ContextImpl<'a> {
}
impl<'a> ContextImpl<'a> {
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f>
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
where
'a: 'f,
{

View File

@@ -12,7 +12,7 @@ pub mod debug;
pub mod extract_xy;
pub mod generic;
pub mod gradient;
mod graphic_element;
pub mod graphic_element;
pub mod instances;
pub mod logic;
pub mod math;
@@ -35,7 +35,7 @@ pub use blending::*;
pub use context::*;
pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphic_element::*;
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::MemoHash;
pub use num_traits;
pub use raster::Color;
@@ -161,7 +161,7 @@ where
pub trait NodeInputDecleration {
const INDEX: usize;
fn identifier() -> &'static str;
fn identifier() -> ProtoNodeIdentifier;
type Result;
}

View File

@@ -3,6 +3,7 @@ use crate::Color;
use crate::GraphicElement;
use crate::GraphicGroupTable;
use crate::gradient::GradientStops;
use crate::graphene_core::registry::types::TextArea;
use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::{Context, Ctx};
@@ -14,12 +15,12 @@ fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f6
}
#[node_macro::node(category("Text"))]
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, #[implementations(String)] second: String) -> String {
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String {
first.clone() + &second
}
#[node_macro::node(category("Text"))]
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: String, to: String) -> String {
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: TextArea, to: TextArea) -> String {
string.replace(&from, &to)
}

View File

@@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend};
use dyn_any::DynFuture;
use std::future::Future;
use std::hash::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
use std::sync::Mutex;
@@ -49,6 +50,10 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
}
}
pub mod memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
}
/// Caches the output of a given Node and acts as a proxy.
/// In contrast to the regular `MemoNode`. This node ignores all input.
/// Using this node might result in the document not updating properly,
@@ -98,6 +103,10 @@ impl<T, I, CachedNode> ImpureMemoNode<I, T, CachedNode> {
}
}
pub mod impure_memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
}
/// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)]
pub struct IORecord<I, O> {
@@ -142,7 +151,10 @@ impl<I, T, N> MonitorNode<I, T, N> {
}
}
use std::hash::{Hash, Hasher};
pub mod monitor {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct MemoHash<T: Hash> {
hash: u64,

View File

@@ -1,4 +1,4 @@
use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend};
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType};
use std::borrow::Cow;
use std::collections::HashMap;
@@ -30,6 +30,8 @@ pub mod types {
pub type Resolution = glam::UVec2;
/// DVec2 with px unit
pub type PixelSize = glam::DVec2;
/// String with one or more than one line
pub type TextArea = String;
}
// Translation struct between macro and definition
@@ -101,11 +103,11 @@ pub enum RegistryValueSource {
Scope(&'static str),
}
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>;
type NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<(NodeConstructor, NodeIOTypes)>>>>;
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;

View File

@@ -20,7 +20,6 @@ async fn transform<T: 'n + 'static>(
rotate: f64,
scale: DVec2,
skew: DVec2,
_pivot: DVec2,
) -> Instances<T> {
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);

View File

@@ -1,6 +1,7 @@
use std::any::TypeId;
pub use std::borrow::Cow;
use std::ops::Deref;
#[macro_export]
macro_rules! concrete {
@@ -128,12 +129,37 @@ impl std::fmt::Debug for NodeIOTypes {
pub struct ProtoNodeIdentifier {
pub name: Cow<'static, str>,
}
impl From<String> for ProtoNodeIdentifier {
fn from(value: String) -> Self {
Self { name: Cow::Owned(value) }
}
}
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}
impl Deref for ProtoNodeIdentifier {
type Target = str;
fn deref(&self) -> &Self::Target {
self.name.as_ref()
}
}
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize;
@@ -306,6 +332,13 @@ impl Type {
Self::Future(output) => output.replace_nested(f),
}
}
pub fn to_cow_string(&self) -> Cow<'static, str> {
match self {
Type::Generic(name) => name.clone(),
_ => Cow::Owned(self.to_string()),
}
}
}
fn format_type(ty: &str) -> String {
@@ -343,19 +376,3 @@ impl std::fmt::Display for Type {
write!(f, "{}", result)
}
}
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}

View File

@@ -67,6 +67,10 @@ impl ClickTarget {
self.bounding_box
}
pub fn bounding_box_center(&self) -> Option<DVec2> {
self.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.)
}
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])
}

View File

@@ -1,7 +1,7 @@
use super::*;
use crate::Ctx;
use crate::instances::Instance;
use crate::uuid::generate_uuid;
use crate::uuid::{NodeId, generate_uuid};
use bezier_rs::BezierHandles;
use dyn_any::DynAny;
use kurbo::{BezPath, PathEl, Point};
@@ -420,12 +420,17 @@ impl Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
if vector_data.is_empty() {
vector_data.push(Instance::default());
}
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item");
modification.apply(vector_data_instance.instance);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path);
if vector_data.len() > 1 {
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len());
}

View File

@@ -352,8 +352,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) {
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2);
instance.transform = transform * local_matrix;
instance.transform = transform * instance.transform;
result_table.push(instance);
}

View File

@@ -1,6 +1,6 @@
use glam::DVec2;
use graphene_core::gradient::GradientStops;
use graphene_core::registry::types::{Fraction, Percentage};
use graphene_core::registry::types::{Fraction, Percentage, TextArea};
use graphene_core::{Color, Ctx, num_traits};
use log::warn;
use math_parser::ast;
@@ -603,7 +603,7 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> Gradien
/// Constructs a string value which may be set to any plain text.
#[node_macro::node(category("Value"))]
fn string_value(_: impl Ctx, _primary: (), string: String) -> String {
fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String {
string
}
@@ -612,6 +612,20 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
vector_a.dot(vector_b)
}
/// Gets the length or magnitude of a vector.
#[node_macro::node(category("Math: Vector"))]
fn length(_: impl Ctx, vector: DVec2) -> f64 {
vector.length()
}
/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude.
///
/// Returns zero when the input vector is zero.
#[node_macro::node(category("Math: Vector"))]
fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 {
vector.normalize_or_zero()
}
#[cfg(test)]
mod test {
use super::*;
@@ -625,6 +639,12 @@ mod test {
assert_eq!(dot_product((), vector_a, vector_b), 11.);
}
#[test]
pub fn length_function() {
let vector = DVec2::new(3., 4.);
assert_eq!(length((), vector), 5.);
}
#[test]
fn test_basic_expression() {
let result = math((), 0., "2 + 2".to_string(), 0.);

View File

@@ -363,17 +363,6 @@ impl NodeInput {
NodeInput::Reflection(_) => false,
}
}
/// Network node inputs in the document network are not displayed, but still exist in the compiled network
pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool {
match self {
NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network { .. } => !is_document_network,
NodeInput::Inline(_) => false,
NodeInput::Scope(_) => false,
NodeInput::Reflection(_) => false,
}
}
pub fn ty(&self) -> Type {
match self {
@@ -497,10 +486,6 @@ impl DocumentNodeImplementation {
}
}
pub const fn proto(name: &'static str) -> Self {
Self::ProtoNode(ProtoNodeIdentifier::new(name))
}
pub fn output_count(&self) -> usize {
match self {
DocumentNodeImplementation::Network(network) => network.exports.len(),
@@ -1250,24 +1235,28 @@ impl NodeNetwork {
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> {
let nodes = self.nodes.iter().collect();
let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect();
RecursiveNodeIter { nodes }
}
}
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
pub struct RecursiveNodeIter<'a> {
nodes: Vec<(&'a NodeId, &'a DocumentNode)>,
nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,
}
impl<'a> Iterator for RecursiveNodeIter<'a> {
type Item = (&'a NodeId, &'a DocumentNode);
type Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>);
fn next(&mut self) -> Option<Self::Item> {
let node = self.nodes.pop()?;
if let DocumentNodeImplementation::Network(network) = &node.1.implementation {
self.nodes.extend(network.nodes.iter());
let (current_id, node, path) = self.nodes.pop()?;
if let DocumentNodeImplementation::Network(network) = &node.implementation {
self.nodes.extend(network.nodes.iter().map(|(id, node)| {
let mut nested_path = path.clone();
nested_path.push(*current_id);
(id, node, nested_path)
}));
}
Some(node)
Some((current_id, node, path))
}
}
@@ -1275,7 +1264,6 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
mod test {
use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::ProtoNodeIdentifier;
use std::sync::atomic::AtomicU64;
fn gen_node_id() -> NodeId {
@@ -1547,7 +1535,7 @@ mod test {
NodeId(1),
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default()
},
),
@@ -1555,7 +1543,7 @@ mod test {
NodeId(2),
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default()
},
),
@@ -1582,7 +1570,7 @@ mod test {
NodeId(2),
DocumentNode {
inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default()
},
),

View File

@@ -97,7 +97,7 @@ macro_rules! tagged_value {
}
}
/// Attempts to downcast the dynamic type to a tagged value
pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result<Self, String> {
pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
use std::any::TypeId;
match input.type_id() {
@@ -190,9 +190,9 @@ tagged_value! {
VectorData(graphene_core::vector::VectorDataTable),
#[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
RasterData(graphene_core::raster_types::RasterDataTable<CPU>),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(graphene_core::ArtboardGroupTable),
// ============
// STRUCT TYPES

View File

@@ -18,7 +18,6 @@ use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegme
use base64::Engine;
#[cfg(target_arch = "wasm32")]
use glam::DAffine2;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
@@ -278,12 +277,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
#[cfg(all(feature = "vello", not(test)))]
let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata {
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
};
let mut metadata = RenderMetadata::default();
data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format;

View File

@@ -198,6 +198,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
pub struct RenderMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
}
@@ -1090,6 +1091,7 @@ impl GraphicElementRendered for GraphicElement {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() {
metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform);
}
}

View File

@@ -20,7 +20,7 @@ mod tests {
NodeId(0),
DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
..Default::default()
},
),

View File

@@ -39,7 +39,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER),
..Default::default()
},
// TODO: Add conversion step
@@ -68,7 +68,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
inner_network,
render_node,
DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default()
},

View File

@@ -2,7 +2,7 @@ use crate::parsing::*;
use convert_case::{Case, Casing};
use proc_macro_crate::FoundCrate;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned};
use quote::{ToTokens, format_ident, quote, quote_spanned};
use std::sync::atomic::AtomicU64;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
@@ -330,11 +330,15 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
})
}
};
let path = match parsed.attributes.path {
Some(ref path) => quote!(stringify!(#path).replace(' ', "")),
None => quote!(std::module_path!().rsplit_once("::").unwrap().0),
let identifier = format_ident!("{}_proto_ident", fn_name);
let identifier_path = match parsed.attributes.path.as_ref() {
Some(path) => {
let path = path.to_token_stream().to_string().replace(' ', "");
quote!(#path)
}
None => quote!(std::module_path!()),
};
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
@@ -354,6 +358,11 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
{
#eval_impl
}
const fn #identifier() -> #graphene_core::ProtoNodeIdentifier {
#graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name)))
}
#[doc(inline)]
pub use #mod_name::#struct_name;
@@ -418,67 +427,63 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
)*
],
};
NODE_METADATA.lock().unwrap().insert(#identifier, metadata);
NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
}
}
})
}
/// Generates strongly typed utilites to access inputs
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &TokenStream2) -> TokenStream2 {
if parsed.attributes.skip_impl {
return quote! {};
}
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 {
let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake));
let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);
let mut generated_input_accessor = Vec::new();
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
let mut ty = match parsed_input {
ParsedField::Regular { ty, .. } => ty,
ParsedField::Node { output_type, .. } => output_type,
if !parsed.attributes.skip_impl {
let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
let mut ty = match parsed_input {
ParsedField::Regular { ty, .. } => ty,
ParsedField::Node { output_type, .. } => output_type,
}
.clone();
// We only want the necessary generics.
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
// TODO: figure out a better name that doesn't conflict with so many types
let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal));
let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter());
// Only create structs with phantom data where necessary.
generated_input_accessor.push(if phantom_data_declerations.is_empty() {
quote! {
pub struct #struct_name;
}
} else {
quote! {
pub struct #struct_name <#(#used),*>{
#(#phantom_data_declerations,)*
}
}
});
generated_input_accessor.push(quote! {
impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index;
fn identifier() -> #graphene_core::ProtoNodeIdentifier {
#inputs_module_name::IDENTIFIER.clone()
}
type Result = #ty;
}
})
}
.clone();
// We only want the necessary generics.
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
// TODO: figure out a better name that doesn't conflict with so many types
let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal));
let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter());
// Only create structs with phantom data where necessary.
generated_input_accessor.push(if phantom_data_declerations.is_empty() {
quote! {
pub struct #struct_name;
}
} else {
quote! {
pub struct #struct_name <#(#used),*>{
#(#phantom_data_declerations,)*
}
}
});
generated_input_accessor.push(quote! {
impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index;
fn identifier() -> &'static str {
protonode_identifier()
}
type Result = #ty;
}
})
}
quote! {
pub mod #inputs_module_name {
use super::*;
pub fn protonode_identifier() -> &'static str {
// Storing the string in a once lock should reduce allocations (since we call this in a loop)?
static NODE_NAME: std::sync::OnceLock<String> = std::sync::OnceLock::new();
NODE_NAME.get_or_init(|| #identifier )
}
/// The `ProtoNodeIdentifier` of this node without any generics attached to it
pub const IDENTIFIER: #graphene_core::ProtoNodeIdentifier = #identifier();
#(#generated_input_accessor)*
}
}
@@ -511,7 +516,7 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a crate::Generi
(fn_generic_params, phantom_data_declerations)
}
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &TokenStream2) -> Result<TokenStream2, Error> {
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result<TokenStream2, Error> {
if parsed.attributes.skip_impl {
return Ok(quote!());
}
@@ -604,7 +609,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
fn register_node() {
let mut registry = NODE_REGISTRY.lock().unwrap();
registry.insert(
#identifier,
#identifier(),
vec![
#(#constructors,)*
]

View File

@@ -6,7 +6,7 @@ use graphene_std::registry::*;
use graphene_std::*;
use std::collections::{HashMap, HashSet};
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String, DocumentNode>) {
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>) {
if network.generated {
return;
}
@@ -15,7 +15,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
match &mut node.implementation {
DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions),
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) {
if let Some(new_node) = substitutions.get(proto_node_identifier) {
node.implementation = new_node.implementation.clone();
}
}
@@ -24,7 +24,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
}
}
pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> {
pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {
let mut custom = HashMap::new();
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() {
@@ -49,7 +49,7 @@ pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> {
let input_count = inputs.len();
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode");
let identity_node = ops::identity::IDENTIFIER;
let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY;