Remove unsafe code and clean up the code base in general (#1263)

* Remove unsafe code

* Make node graph test syncronous

* Add miri step to ci

* Remove unsafe from node graph evaluation

* Replace operation pseudo_hash with hash based on discriminant

* Fix test

* Move memo module to core and make it safe

* Fix formatting

* Remove unused stuff from gstd

* Use safe casting for creating key variants

* Fix memo node types

* Fix ref node

* "fix" ub

* Use correct input types for ExtractImageFrame

* Fix types for async nodes

* Fix missing implementation

* Manually override output type for async nodes

* Fix types for EditorApi

* Fix output type for WasmSurfaceHandle

* Remove unused miri.yml

* Fix incorrect type for cache node
This commit is contained in:
Dennis Kobert
2023-06-02 11:05:32 +02:00
committed by Keavon Chambers
parent 259dcdc628
commit 4e1bfddcd8
43 changed files with 520 additions and 1252 deletions

View File

@@ -12,10 +12,7 @@ license = "Apache-2.0"
[features]
gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "graphene-core/gpu"]
quantization = [
"graphene-std/quantization",
"interpreted-executor/quantization",
]
quantization = ["graphene-std/quantization", "interpreted-executor/quantization"]
[dependencies]
log = "0.4"
@@ -41,10 +38,10 @@ image = { version = "0.24", default-features = false, features = [
] }
graph-craft = { path = "../node-graph/graph-craft" }
interpreted-executor = { path = "../node-graph/interpreted-executor" }
borrow_stack = { path = "../node-graph/borrow_stack" }
dyn-any = { path = "../libraries/dyn-any" }
graphene-core = { path = "../node-graph/gcore" }
graphene-std = { path = "../node-graph/gstd" }
num_enum = "0.6.1"
[dependencies.document-legacy]
path = "../document-legacy"

View File

@@ -323,6 +323,7 @@ mod test {
}
#[test]
#[cfg_attr(miri, ignore)]
/// - create rect, shape and ellipse
/// - select shape
/// - copy
@@ -362,6 +363,7 @@ mod test {
}
#[test]
#[cfg_attr(miri, ignore)]
fn copy_paste_folder() {
let mut editor = create_editor_with_three_layers();
@@ -450,6 +452,7 @@ mod test {
}
#[test]
#[cfg_attr(miri, ignore)]
/// - create rect, shape and ellipse
/// - select ellipse and rect
/// - copy

View File

@@ -37,9 +37,9 @@ impl InputMapperMessageHandler {
.filter_map(|(i, m)| {
let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant())));
ma.map(|a| unsafe { (std::mem::transmute_copy::<usize, Key>(&i), a) })
ma.map(|a| ((i as u8).try_into().unwrap(), a))
})
.for_each(|(k, a)| {
.for_each(|(k, a): (Key, _)| {
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
});
output.replace("Key", "")
@@ -72,7 +72,7 @@ impl InputMapperMessageHandler {
"Attempting to convert a Key with enum index {}, which is larger than the number of Key enums",
i
);
unsafe { std::mem::transmute_copy::<usize, Key>(&i) }
(i as u8).try_into().unwrap()
})
.collect::<Vec<_>>();

View File

@@ -50,7 +50,8 @@ bitflags! {
// (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout
// is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`.
#[impl_message(Message, InputMapperMessage, KeyDown)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, specta::Type)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, specta::Type, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum Key {
// Writing system keys
Digit0,

View File

@@ -41,28 +41,6 @@ pub enum Message {
Workspace(WorkspaceMessage),
}
impl Message {
/// Returns the byte representation of the message.
///
/// # Safety
/// This function reads from uninitialized memory!!!
/// Only use if you know what you are doing.
unsafe fn as_slice(&self) -> &[u8] {
core::slice::from_raw_parts(self as *const Message as *const u8, std::mem::size_of::<Message>())
}
/// Returns a pseudo hash that should uniquely identify the message.
/// This is needed because `Hash` is not implemented for `f64`s
///
/// # Safety
/// This function reads from uninitialized memory but the generated value should be fine.
pub fn pseudo_hash(&self) -> u64 {
let mut s = DefaultHasher::new();
unsafe { self.as_slice() }.hash(&mut s);
s.finish()
}
}
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
/// Specta isn't integrated with `impl_message`, so a remote impl must be provided using this
/// struct.

View File

@@ -1001,6 +1001,10 @@ impl DocumentMessageHandler {
// Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region
let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap();
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
// TODO: Fix this hack
// This is a hack to prevent the compiler from optimizing out the size calculation which likely is due
// to undefined behavior. THIS IS NOT A FIX.
log::trace!("size: {:?}", size);
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
self.restore_document_transform(old_transforms);

View File

@@ -148,7 +148,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
1,
DocumentNode {
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"),
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"),
..Default::default()
},
),
@@ -198,7 +198,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
// We currently just clone by default
@@ -262,7 +262,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
]
@@ -305,7 +305,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNode {
name: "Cache".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::node(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
@@ -355,13 +355,13 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNode {
name: "LetNode".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::LetNode<_>")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::LetNode<_>")),
..Default::default()
},
DocumentNode {
name: "RefNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::RefNode<_, _>")),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")),
..Default::default()
},
]
@@ -392,7 +392,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType {
name: "End Scope",
category: "Ignore",
identifier: NodeImplementation::proto("graphene_std::memo::EndLetNode<_>"),
identifier: NodeImplementation::proto("graphene_core::memo::EndLetNode<_>"),
inputs: vec![
DocumentInputType {
name: "Scope",
@@ -666,47 +666,6 @@ fn static_nodes() -> Vec<DocumentNodeType> {
],
properties: node_properties::no_properties,
},
DocumentNodeType {
name: "Gaussian Blur",
category: "Ignore",
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0, 1, 1],
outputs: vec![NodeOutput::new(1, 0)],
nodes: vec![
(
0,
DocumentNode {
name: "CacheNode".to_string(),
inputs: vec![NodeInput::Network(concrete!(Image<Color>))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
..Default::default()
},
),
(
1,
DocumentNode {
name: "BlurNode".to_string(),
inputs: vec![NodeInput::node(0, 0), NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(f64)), NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode")),
..Default::default()
},
),
]
.into_iter()
.collect(),
..Default::default()
}),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
DocumentInputType::value("Radius", TaggedValue::U32(3), false),
DocumentInputType::value("Sigma", TaggedValue::F64(1.), false),
],
outputs: vec![DocumentOutputType {
name: "Image",
data_type: FrontendGraphDataType::Raster,
}],
properties: node_properties::blur_image_properties,
},
DocumentNodeType {
name: "Brush",
category: "Brush",
@@ -735,34 +694,9 @@ fn static_nodes() -> Vec<DocumentNodeType> {
properties: node_properties::no_properties,
},
DocumentNodeType {
name: "Cache",
name: "Memoize",
category: "Structural",
identifier: NodeImplementation::DocumentNode(NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(1, 0)],
nodes: [
(
0,
DocumentNode {
name: "CacheNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::Network(concrete!(ImageFrame<Color>))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::memo::CacheNode")),
..Default::default()
},
),
(
1,
DocumentNode {
name: "CloneNode".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::CloneNode<_>")),
..Default::default()
},
),
]
.into(),
..Default::default()
}),
identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties,
@@ -778,7 +712,7 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType {
name: "Ref",
category: "Structural",
identifier: NodeImplementation::proto("graphene_std::memo::CacheNode"),
identifier: NodeImplementation::proto("graphene_core::memo::MemoNode<_, _>"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
properties: node_properties::no_properties,
@@ -1330,7 +1264,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
let mut network_inputs = Vec::new();
let mut input_type = None;
for (id, node) in network.nodes.iter() {
for (index, input) in node.inputs.iter().enumerate() {
for input in node.inputs.iter() {
if let NodeInput::Network(_) = input {
if input_type.is_none() {
input_type = Some(input.clone());

View File

@@ -1222,9 +1222,7 @@ fn edit_layer_deepest_manipulation(intersect: &Layer, responses: &mut VecDeque<M
fn recursive_search(document: &DocumentMessageHandler, layer_path: &Vec<u64>, incoming_layer_path_vector: &Vec<u64>) -> bool {
let layer_paths = document.document_legacy.folder_children_paths(layer_path);
for path in layer_paths {
if path == *incoming_layer_path_vector {
return true;
} else if document.document_legacy.is_folder(path.clone()) && recursive_search(document, &path, incoming_layer_path_vector) {
if path == *incoming_layer_path_vector || document.document_legacy.is_folder(path.clone()) && recursive_search(document, &path, incoming_layer_path_vector) {
return true;
}
}

View File

@@ -7,7 +7,6 @@ use crate::messages::prelude::*;
use document_legacy::layers::layer_info::LayerDataType;
use document_legacy::{LayerId, Operation};
use dyn_any::DynAny;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::executor::Compiler;
@@ -18,7 +17,7 @@ use graphene_core::renderer::{SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache;
use graphene_core::vector::style::ViewMode;
use graphene_core::wasm_application_io::{WasmApplicationIo, WasmSurfaceHandleFrame};
use graphene_core::wasm_application_io::WasmApplicationIo;
use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId};
use interpreted_executor::executor::DynamicExecutor;
@@ -110,7 +109,7 @@ impl NodeRuntime {
updates: responses,
new_thumbnails: self.thumbnails.clone(),
};
self.sender.send(response);
self.sender.send(response).expect("Failed to send response");
}
}
}
@@ -118,7 +117,7 @@ impl NodeRuntime {
/// Wraps a network in a scope and returns the new network and the paths to the monitor nodes.
fn wrap_network(network: NodeNetwork) -> (NodeNetwork, Vec<Vec<NodeId>>) {
let mut scoped_network = wrap_network_in_scope(network);
let scoped_network = wrap_network_in_scope(network);
//scoped_network.generate_node_paths(&[]);
let monitor_nodes = scoped_network
@@ -126,7 +125,6 @@ impl NodeRuntime {
.filter(|(node, _, _)| node.implementation == DocumentNodeImplementation::proto("graphene_std::memo::MonitorNode<_>"))
.map(|(_, _, path)| path)
.collect();
//scoped_network.remove_dead_nodes();
(scoped_network, monitor_nodes)
}
@@ -149,40 +147,23 @@ impl NodeRuntime {
return Err(e);
}
use dyn_any::IntoDynAny;
use graph_craft::executor::Executor;
let result = match self.executor.input_type() {
Some(t) if t == concrete!(EditorApi) => self.executor.execute(editor_api.into_dyn()).await.map_err(|e| e.to_string()),
Some(t) if t == concrete!(()) => self.executor.execute(().into_dyn()).await.map_err(|e| e.to_string()),
Some(t) if t == concrete!(EditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()),
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
_ => Err("Invalid input type".to_string()),
};
}?;
match result {
Ok(result) => {
if DynAny::type_id(result.as_ref()) == core::any::TypeId::of::<WasmSurfaceHandleFrame>() {
let Ok(value) = dyn_any::downcast::<WasmSurfaceHandleFrame>(result) else { unreachable!()};
let new_id = value.surface_handle.surface_id;
let old_id = self.canvas_cache.insert(path.to_vec(), new_id);
if let Some(old_id) = old_id {
if old_id != new_id {
self.wasm_io.destroy_surface(old_id);
}
}
return Ok(TaggedValue::SurfaceFrame(SurfaceFrame {
surface_id: new_id,
transform: value.transform,
}));
}
let type_name = DynAny::type_name(result.as_ref());
match TaggedValue::try_from_any(result) {
Some(x) => Ok(x),
None => Err(format!("Invalid output type: {}", type_name)),
if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) = result {
let old_id = self.canvas_cache.insert(path.to_vec(), surface_id);
if let Some(old_id) = old_id {
if old_id != surface_id {
self.wasm_io.destroy_surface(old_id);
}
}
Err(e) => Err(e),
}
Ok(result)
}
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
@@ -289,13 +270,13 @@ impl NodeGraphExecutor {
image_frame,
generation_id,
};
self.sender.send(NodeRuntimeMessage::GenerationRequest(request));
self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request");
generation_id
}
pub fn update_font_cache(&self, font_cache: FontCache) {
self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache));
self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
}
pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {