mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 18:48:11 +08:00
Add node for executing rhai scripts
This commit is contained in:
Generated
+88
@@ -46,6 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"const-random",
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
@@ -1102,6 +1103,26 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||||
|
dependencies = [
|
||||||
|
"const-random-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random-macro"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.15",
|
||||||
|
"once_cell",
|
||||||
|
"tiny-keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2533,6 +2554,7 @@ dependencies = [
|
|||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"reqwest 0.12.12",
|
"reqwest 0.12.12",
|
||||||
"resvg",
|
"resvg",
|
||||||
|
"rhai",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3378,6 +3400,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4404,6 +4429,9 @@ name = "once_cell"
|
|||||||
version = "1.20.3"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
@@ -5609,6 +5637,36 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rhai"
|
||||||
|
version = "1.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"getrandom 0.2.15",
|
||||||
|
"instant",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"rhai_codegen",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"smartstring",
|
||||||
|
"thin-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rhai_codegen"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.99",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.13"
|
version = "0.17.13"
|
||||||
@@ -6155,6 +6213,18 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smartstring"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -6838,6 +6908,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thin-vec"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -6930,6 +7009,15 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-keccak"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl DialogLayoutHolder for CloseAllDocumentsDialog {
|
|||||||
|
|
||||||
impl LayoutHolder for CloseAllDocumentsDialog {
|
impl LayoutHolder for CloseAllDocumentsDialog {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let unsaved_list = "• ".to_string() + &self.unsaved_document_names.join("\n• ");
|
let unsaved_list = "• ".to_string() + self.unsaved_document_names.join("\n• ").as_str();
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::Row {
|
||||||
|
|||||||
@@ -130,6 +130,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
description: Cow::Borrowed("The identity node passes its data through. You can use this to organize your node graph."),
|
description: Cow::Borrowed("The identity node passes its data through. You can use this to organize your node graph."),
|
||||||
properties: Some("identity_properties"),
|
properties: Some("identity_properties"),
|
||||||
},
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Script",
|
||||||
|
category: "General",
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::proto("graphene_std::rhai::RhaiNode"),
|
||||||
|
manual_composition: Some(concrete!(Context)),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::F64(0.), true), NodeInput::value(TaggedValue::String("input".into()), false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
input_properties: vec!["In".into(), "String".into()],
|
||||||
|
output_names: vec!["Out".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed(""),
|
||||||
|
properties: Some("script_properties"),
|
||||||
|
},
|
||||||
// TODO: Auto-generate this from its proto node macro
|
// TODO: Auto-generate this from its proto node macro
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Monitor",
|
identifier: "Monitor",
|
||||||
@@ -2897,6 +2916,7 @@ fn static_node_properties() -> NodeProperties {
|
|||||||
"monitor_properties".to_string(),
|
"monitor_properties".to_string(),
|
||||||
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
||||||
);
|
);
|
||||||
|
map.insert("script_properties".to_string(), Box::new(node_properties::script_properties));
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ pub(crate) fn property_from_type(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
||||||
|
Type::Dynamic => vec![TextLabel::new("Dynamic type (not supported)").widget_holder()].into(),
|
||||||
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context),
|
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context),
|
||||||
Type::Future(out) => return property_from_type(node_id, index, out, number_options, context),
|
Type::Future(out) => return property_from_type(node_id, index, out, number_options, context),
|
||||||
};
|
};
|
||||||
@@ -2555,3 +2556,16 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||||||
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected"#),
|
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected"#),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn script_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
Ok(document_node) => document_node,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Could not get document node in script_properties: {err}");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let source = text_area_widget(document_node, node_id, 1, "Code", false);
|
||||||
|
|
||||||
|
vec![LayoutGroup::Row { widgets: source }]
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use graphene_std::vector::style::FillChoice;
|
|||||||
|
|
||||||
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||||
let origin = document.snapping_state.grid.origin;
|
let origin = document.snapping_state.grid.origin;
|
||||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
let grid_color = "#".to_string() + document.snapping_state.grid.grid_color.to_rgba_hex_srgb().as_str();
|
||||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -48,7 +48,7 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
|||||||
// TODO: Implement this with a dashed line (`set_line_dash`), with integer spacing which is continuously adjusted to correct the accumulated error.
|
// TODO: Implement this with a dashed line (`set_line_dash`), with integer spacing which is continuously adjusted to correct the accumulated error.
|
||||||
fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||||
let origin = document.snapping_state.grid.origin;
|
let origin = document.snapping_state.grid.origin;
|
||||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
let grid_color = "#".to_string() + document.snapping_state.grid.grid_color.to_rgba_hex_srgb().as_str();
|
||||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -82,7 +82,7 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
||||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
let grid_color = "#".to_string() + document.snapping_state.grid.grid_color.to_rgba_hex_srgb().as_str();
|
||||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||||
let origin = document.snapping_state.grid.origin;
|
let origin = document.snapping_state.grid.origin;
|
||||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||||
@@ -125,7 +125,7 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
||||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
let grid_color = "#".to_string() + document.snapping_state.grid.grid_color.to_rgba_hex_srgb().as_str();
|
||||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||||
let origin = document.snapping_state.grid.origin;
|
let origin = document.snapping_state.grid.origin;
|
||||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ impl Fsm for TextToolFsmState {
|
|||||||
if far.x != 0. && far.y != 0. {
|
if far.x != 0. && far.y != 0. {
|
||||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||||
overlay_context.quad(transformed_quad, Some(&("#".to_string() + &fill_color)));
|
overlay_context.quad(transformed_quad, Some(&("#".to_string() + fill_color.as_str())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,11 +488,11 @@ impl Fsm for TextToolFsmState {
|
|||||||
for layer in document.intersect_quad_no_artboards(quad, input) {
|
for layer in document.intersect_quad_no_artboards(quad, input) {
|
||||||
overlay_context.quad(
|
overlay_context.quad(
|
||||||
Quad::from_box(document.metadata().bounding_box_viewport(layer).unwrap_or([DVec2::ZERO; 2])),
|
Quad::from_box(document.metadata().bounding_box_viewport(layer).unwrap_or([DVec2::ZERO; 2])),
|
||||||
Some(&("#".to_string() + &fill_color)),
|
Some(&("#".to_string() + fill_color.as_str())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay_context.quad(quad, Some(&("#".to_string() + &fill_color)));
|
overlay_context.quad(quad, Some(&("#".to_string() + fill_color.as_str())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement bounding box for multiple layers
|
// TODO: implement bounding box for multiple layers
|
||||||
|
|||||||
@@ -668,10 +668,10 @@ impl NodeGraphExecutor {
|
|||||||
..
|
..
|
||||||
} = export_config;
|
} = export_config;
|
||||||
|
|
||||||
let file_suffix = &format!(".{file_type:?}").to_lowercase();
|
let file_suffix = format!(".{file_type:?}").to_lowercase();
|
||||||
let name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
|
let name = match file_name.ends_with(FILE_SAVE_SUFFIX) {
|
||||||
true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix),
|
true => file_name.replace(FILE_SAVE_SUFFIX, &file_suffix),
|
||||||
false => file_name + file_suffix,
|
false => file_name + file_suffix.as_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if file_type == FileType::Svg {
|
if file_type == FileType::Svg {
|
||||||
|
|||||||
@@ -198,6 +198,31 @@ impl<I, O> DowncastBothNode<I, O> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Boxes the input and downcasts the output.
|
||||||
|
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DowncastNoneNode {
|
||||||
|
node: SharedNodeContainer,
|
||||||
|
}
|
||||||
|
impl<'input> Node<'input, Any<'input>> for DowncastNoneNode {
|
||||||
|
type Output = FutureAny<'input>;
|
||||||
|
#[inline]
|
||||||
|
fn eval(&'input self, input: Any<'input>) -> Self::Output {
|
||||||
|
self.node.eval(input)
|
||||||
|
}
|
||||||
|
fn reset(&self) {
|
||||||
|
self.node.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any + Send + Sync>> {
|
||||||
|
self.node.serialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DowncastNoneNode {
|
||||||
|
pub const fn new(node: SharedNodeContainer) -> Self {
|
||||||
|
Self { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct FutureWrapperNode<Node> {
|
pub struct FutureWrapperNode<Node> {
|
||||||
node: Node,
|
node: Node,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ pub enum Type {
|
|||||||
Fn(Box<Type>, Box<Type>),
|
Fn(Box<Type>, Box<Type>),
|
||||||
/// Represents a future which promises to return the inner type.
|
/// Represents a future which promises to return the inner type.
|
||||||
Future(Box<Type>),
|
Future(Box<Type>),
|
||||||
|
Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Type {
|
impl Default for Type {
|
||||||
@@ -258,6 +259,15 @@ impl Type {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn fn_fut_output(&self) -> Option<&Type> {
|
||||||
|
match self {
|
||||||
|
Type::Fn(_, second) => match second.as_ref() {
|
||||||
|
Type::Future(fut) => Some(fut),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn function(input: &Type, output: &Type) -> Type {
|
pub fn function(input: &Type, output: &Type) -> Type {
|
||||||
Type::Fn(Box::new(input.clone()), Box::new(output.clone()))
|
Type::Fn(Box::new(input.clone()), Box::new(output.clone()))
|
||||||
@@ -281,6 +291,7 @@ impl Type {
|
|||||||
Self::Concrete(ty) => Some(ty.size),
|
Self::Concrete(ty) => Some(ty.size),
|
||||||
Self::Fn(_, _) => None,
|
Self::Fn(_, _) => None,
|
||||||
Self::Future(_) => None,
|
Self::Future(_) => None,
|
||||||
|
Self::Dynamic => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +301,7 @@ impl Type {
|
|||||||
Self::Concrete(ty) => Some(ty.align),
|
Self::Concrete(ty) => Some(ty.align),
|
||||||
Self::Fn(_, _) => None,
|
Self::Fn(_, _) => None,
|
||||||
Self::Future(_) => None,
|
Self::Future(_) => None,
|
||||||
|
Self::Dynamic => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +311,7 @@ impl Type {
|
|||||||
Self::Concrete(_) => self,
|
Self::Concrete(_) => self,
|
||||||
Self::Fn(_, output) => output.nested_type(),
|
Self::Fn(_, output) => output.nested_type(),
|
||||||
Self::Future(output) => output.nested_type(),
|
Self::Future(output) => output.nested_type(),
|
||||||
|
Self::Dynamic => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,6 +333,7 @@ impl core::fmt::Debug for Type {
|
|||||||
Self::Concrete(arg0) => write!(f, "Concrete<{}>", format_type(&arg0.name)),
|
Self::Concrete(arg0) => write!(f, "Concrete<{}>", format_type(&arg0.name)),
|
||||||
Self::Fn(arg0, arg1) => write!(f, "{arg0:?} → {arg1:?}"),
|
Self::Fn(arg0, arg1) => write!(f, "{arg0:?} → {arg1:?}"),
|
||||||
Self::Future(arg0) => write!(f, "Future<{arg0:?}>"),
|
Self::Future(arg0) => write!(f, "Future<{arg0:?}>"),
|
||||||
|
Self::Dynamic => write!(f, "Dynamic"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +345,7 @@ impl std::fmt::Display for Type {
|
|||||||
Type::Concrete(ty) => write!(f, "{}", format_type(&ty.name)),
|
Type::Concrete(ty) => write!(f, "{}", format_type(&ty.name)),
|
||||||
Type::Fn(input, output) => write!(f, "{input} → {output}"),
|
Type::Fn(input, output) => write!(f, "{input} → {output}"),
|
||||||
Type::Future(ty) => write!(f, "Future<{ty}>"),
|
Type::Future(ty) => write!(f, "Future<{ty}>"),
|
||||||
|
Self::Dynamic => write!(f, "Dynamic"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ macro_rules! tagged_value {
|
|||||||
Type::Generic(_) => {
|
Type::Generic(_) => {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
Type::Dynamic => None,
|
||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
let internal_id = concrete_type.id?;
|
let internal_id = concrete_type.id?;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
@@ -279,6 +280,7 @@ impl TaggedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match ty {
|
match ty {
|
||||||
|
Type::Dynamic => None,
|
||||||
Type::Generic(_) => None,
|
Type::Generic(_) => None,
|
||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
let internal_id = concrete_type.id?;
|
let internal_id = concrete_type.id?;
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ impl TypingContext {
|
|||||||
// Direct comparison of two concrete types.
|
// Direct comparison of two concrete types.
|
||||||
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
|
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
|
||||||
// Check inner type for futures
|
// Check inner type for futures
|
||||||
(Type::Future(type1), Type::Future(type2)) => type1 == type2,
|
(Type::Future(type1), Type::Future(type2)) => valid_subtype(type1, type2),
|
||||||
// Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality.
|
// Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality.
|
||||||
// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.
|
// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.
|
||||||
// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.
|
// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.
|
||||||
@@ -708,6 +708,8 @@ impl TypingContext {
|
|||||||
// For example, Rust implements these same relations as it describes here: <https://doc.rust-lang.org/nomicon/subtyping.html>
|
// For example, Rust implements these same relations as it describes here: <https://doc.rust-lang.org/nomicon/subtyping.html>
|
||||||
// More details explained here: <https://github.com/GraphiteEditor/Graphite/issues/1741>
|
// More details explained here: <https://github.com/GraphiteEditor/Graphite/issues/1741>
|
||||||
(Type::Fn(in1, out1), Type::Fn(in2, out2)) => valid_subtype(out2, out1) && (valid_subtype(in1, in2) || **in1 == concrete!(())),
|
(Type::Fn(in1, out1), Type::Fn(in2, out2)) => valid_subtype(out2, out1) && (valid_subtype(in1, in2) || **in1 == concrete!(())),
|
||||||
|
// Allow Dynamic types an input to concrete or generic types
|
||||||
|
(Type::Concrete(_), Type::Dynamic) | (Type::Generic(_), Type::Dynamic) => true,
|
||||||
// If either the proposed input or the allowed input are generic, we allow the substitution (meaning this is a valid subtype).
|
// If either the proposed input or the allowed input are generic, we allow the substitution (meaning this is a valid subtype).
|
||||||
// TODO: Add proper generic counting which is not based on the name
|
// TODO: Add proper generic counting which is not based on the name
|
||||||
(Type::Generic(_), _) | (_, Type::Generic(_)) => true,
|
(Type::Generic(_), _) | (_, Type::Generic(_)) => true,
|
||||||
@@ -823,6 +825,10 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
|||||||
let mut generics = inputs
|
let mut generics = inputs
|
||||||
.filter_map(|t| match t {
|
.filter_map(|t| match t {
|
||||||
Type::Generic(out) => Some(out.clone()),
|
Type::Generic(out) => Some(out.clone()),
|
||||||
|
Type::Future(fut) => match fut.as_ref() {
|
||||||
|
Type::Generic(out) => Some(out.clone()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -837,7 +843,9 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
|||||||
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
||||||
let inputs = [(Some(&types.call_argument), Some(input))]
|
let inputs = [(Some(&types.call_argument), Some(input))]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(types.inputs.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));
|
.chain(types.inputs.iter().map(|x| x.fn_fut_output()).zip(parameters.iter().map(|x| x.fn_fut_output())));
|
||||||
|
let inputs: Vec<_> = inputs.collect();
|
||||||
|
let inputs = inputs.into_iter();
|
||||||
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
||||||
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
||||||
let out_ty = outputs
|
let out_ty = outputs
|
||||||
|
|||||||
@@ -90,5 +90,12 @@ web-sys = { workspace = true, optional = true, features = [
|
|||||||
image-compare = { version = "0.4.1", optional = true }
|
image-compare = { version = "0.4.1", optional = true }
|
||||||
ndarray = "0.16.1"
|
ndarray = "0.16.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
rhai = { version = "1.21.0", features = ["serde", "wasm-bindgen"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
rhai = { version = "1.21.0", features = ["serde"] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["macros"] }
|
tokio = { workspace = true, features = ["macros"] }
|
||||||
|
|||||||
@@ -30,3 +30,5 @@ pub mod wasm_application_io;
|
|||||||
pub mod dehaze;
|
pub mod dehaze;
|
||||||
|
|
||||||
pub mod imaginate;
|
pub mod imaginate;
|
||||||
|
|
||||||
|
pub mod rhai;
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
use graph_craft::{
|
||||||
|
document::value::TaggedValue,
|
||||||
|
proto::{Any, FutureAny},
|
||||||
|
};
|
||||||
|
use graphene_core::{Context, Node};
|
||||||
|
use rhai::{Engine, Scope};
|
||||||
|
|
||||||
|
// For Serde conversion
|
||||||
|
use rhai::serde::{from_dynamic, to_dynamic};
|
||||||
|
|
||||||
|
pub struct RhaiNode<Source, Input> {
|
||||||
|
source: Source,
|
||||||
|
input: Input,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'n, S, I> Node<'n, Any<'n>> for RhaiNode<S, I>
|
||||||
|
where
|
||||||
|
S: Node<'n, Any<'n>, Output = FutureAny<'n>>,
|
||||||
|
I: Node<'n, Any<'n>, Output = FutureAny<'n>>,
|
||||||
|
{
|
||||||
|
type Output = FutureAny<'n>;
|
||||||
|
|
||||||
|
fn eval(&'n self, ctx: Any<'n>) -> Self::Output {
|
||||||
|
let ctx: Box<Context> = dyn_any::downcast(ctx).unwrap();
|
||||||
|
let source = self.source.eval(ctx.clone());
|
||||||
|
let input = self.input.eval(ctx);
|
||||||
|
Box::pin(async move {
|
||||||
|
// Get the script source and input value
|
||||||
|
let source = source.await;
|
||||||
|
let input = input.await;
|
||||||
|
|
||||||
|
// Convert to appropriate types
|
||||||
|
let script: String = match dyn_any::downcast::<String>(source) {
|
||||||
|
Ok(script) => *script,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to convert script source to String: {}", err);
|
||||||
|
return Box::new(()) as Any<'n>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tagged_value = match TaggedValue::try_from_any(input) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to convert input to TaggedValue: {}", err);
|
||||||
|
return Box::new(()) as Any<'n>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up Rhai engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Register any additional utility functions
|
||||||
|
register_utility_functions(&mut engine);
|
||||||
|
|
||||||
|
// Create a scope and add the input value
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
// Convert TaggedValue to appropriate Rhai type
|
||||||
|
// This is the key part we need to fix
|
||||||
|
match tagged_value {
|
||||||
|
TaggedValue::F64(val) => {
|
||||||
|
// Directly push as primitive f64
|
||||||
|
scope.push("input", val);
|
||||||
|
}
|
||||||
|
TaggedValue::U64(val) => {
|
||||||
|
// Convert to i64 which Rhai uses for integers
|
||||||
|
scope.push("input", val as i64);
|
||||||
|
}
|
||||||
|
TaggedValue::U32(val) => {
|
||||||
|
// Convert to i64 which Rhai uses for integers
|
||||||
|
scope.push("input", val as i64);
|
||||||
|
}
|
||||||
|
TaggedValue::Bool(val) => {
|
||||||
|
scope.push("input", val);
|
||||||
|
}
|
||||||
|
TaggedValue::String(val) => {
|
||||||
|
scope.push("input", val.clone());
|
||||||
|
}
|
||||||
|
// For complex types, use Serde conversion
|
||||||
|
_ => match to_dynamic(tagged_value.clone()) {
|
||||||
|
Ok(dynamic) => {
|
||||||
|
scope.push("input", dynamic);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to convert input to Rhai Dynamic: {}", err);
|
||||||
|
return Box::new(()) as Any<'n>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the script
|
||||||
|
match engine.eval_with_scope::<rhai::Dynamic>(&mut scope, &script) {
|
||||||
|
Ok(result) => {
|
||||||
|
// Convert Rhai result back to TaggedValue
|
||||||
|
if result.is::<f64>() {
|
||||||
|
let val = result.cast::<f64>();
|
||||||
|
TaggedValue::F64(val).to_any()
|
||||||
|
} else if result.is::<i64>() {
|
||||||
|
let val = result.cast::<i64>();
|
||||||
|
TaggedValue::F64(val as f64).to_any()
|
||||||
|
} else if result.is::<bool>() {
|
||||||
|
let val = result.cast::<bool>();
|
||||||
|
TaggedValue::Bool(val).to_any()
|
||||||
|
} else if result.is::<String>() {
|
||||||
|
let val = result.cast::<String>();
|
||||||
|
TaggedValue::String(val).to_any()
|
||||||
|
} else {
|
||||||
|
// For complex types, use Serde conversion
|
||||||
|
match from_dynamic(&result) {
|
||||||
|
Ok(value) => TaggedValue::to_any(value),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Failed to convert Rhai result to TaggedValue: {}", err);
|
||||||
|
Box::new(()) as Any<'n>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Rhai script evaluation error: {}", err);
|
||||||
|
Box::new(()) as Any<'n>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register utility functions that would be useful in scripts
|
||||||
|
fn register_utility_functions(engine: &mut Engine) {
|
||||||
|
// Logging function
|
||||||
|
engine.register_fn("log", |msg: &str| {
|
||||||
|
log::info!("Rhai script log: {}", msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, I> RhaiNode<S, I> {
|
||||||
|
pub fn new(input: I, source: S) -> RhaiNode<S, I> {
|
||||||
|
RhaiNode { source, input }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
use glam::{DVec2, UVec2};
|
use glam::{DVec2, UVec2};
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
use graph_craft::proto::{DowncastNoneNode, NodeConstructor, TypeErasedBox};
|
||||||
use graphene_core::fn_type;
|
use graphene_core::fn_type;
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
use graphene_core::raster::image::ImageFrameTable;
|
use graphene_core::raster::image::ImageFrameTable;
|
||||||
@@ -66,6 +66,15 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
// |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
|
||||||
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
|
// NodeIOTypes::new(generic!(I), generic!(I), vec![]),
|
||||||
// ),
|
// ),
|
||||||
|
(
|
||||||
|
ProtoNodeIdentifier::new("graphene_std::rhai::RhaiNode"),
|
||||||
|
|mut vec| Box::pin(async move { Box::new(graphene_std::rhai::RhaiNode::new(DowncastNoneNode::new(vec.remove(0)), DowncastNoneNode::new(vec.remove(0)))) as TypeErasedBox }),
|
||||||
|
NodeIOTypes::new(
|
||||||
|
generic!(C),
|
||||||
|
Type::Future(Box::new(Type::Dynamic)),
|
||||||
|
vec![Type::Fn(Box::new(concrete!(Context)), Box::new(Type::Future(Box::new(generic!(I))))), fn_type_fut!(Context, String)],
|
||||||
|
),
|
||||||
|
),
|
||||||
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
|
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<SRGBA8>>, input: ImageFrameTable<Color>, params: []),
|
||||||
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
|
// async_node!(graphene_core::ops::IntoNode<ImageFrameTable<Color>>, input: ImageFrameTable<SRGBA8>, params: []),
|
||||||
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
async_node!(graphene_core::ops::IntoNode<GraphicGroupTable>, input: ImageFrameTable<Color>, params: []),
|
||||||
|
|||||||
Reference in New Issue
Block a user