mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Deduplicate the network interface query helpers and collapse TransientMetadata into TransientCache (#4382)
* Route the remaining view queries through the logging query helper * Name the cache readers by their loading contract and stop cloning the owned nodes per collision check * Deduplicate the port center, output count, and upstream connector helpers and drop dead accessors * Collapse the TransientMetadata enum into the Option inside TransientCache * Extract the shared history snapshot install from undo and redo
This commit is contained in:
@@ -2414,22 +2414,26 @@ impl DocumentMessageHandler {
|
||||
self.drive_storage_undo_redo(document_id, resource_storage, legacy_applied, true, responses);
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
// If there is no history return and don't broadcast SelectionChanged
|
||||
let mut network_interface = self.history.pop_undo()?;
|
||||
|
||||
/// Installs a history snapshot as the active network interface, carrying over the current view state and structure load, and returns the replaced interface.
|
||||
fn install_history_snapshot(&mut self, mut network_interface: NodeNetworkInterface, viewport: &ViewportMessageHandler) -> NodeNetworkInterface {
|
||||
// Set the previous network navigation metadata to the current navigation metadata
|
||||
network_interface.copy_all_navigation_metadata(&self.network_interface);
|
||||
std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);
|
||||
|
||||
//Update the metadata transform based on document PTZ
|
||||
// Update the metadata transform based on document PTZ
|
||||
let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
network_interface.set_document_to_viewport_transform(transform);
|
||||
|
||||
// Ensure document structure is loaded so that updating the selected nodes has the correct metadata
|
||||
network_interface.load_structure();
|
||||
|
||||
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
|
||||
std::mem::replace(&mut self.network_interface, network_interface)
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
// If there is no history return and don't broadcast SelectionChanged
|
||||
let network_interface = self.history.pop_undo()?;
|
||||
let previous_network = self.install_history_snapshot(network_interface, viewport);
|
||||
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
@@ -2454,20 +2458,9 @@ impl DocumentMessageHandler {
|
||||
|
||||
pub fn redo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
// If there is no history return and don't broadcast SelectionChanged
|
||||
let mut network_interface = self.history.pop_redo()?;
|
||||
let network_interface = self.history.pop_redo()?;
|
||||
let previous_network = self.install_history_snapshot(network_interface, viewport);
|
||||
|
||||
// Set the previous network navigation metadata to the current navigation metadata
|
||||
network_interface.copy_all_navigation_metadata(&self.network_interface);
|
||||
std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);
|
||||
|
||||
//Update the metadata transform based on document PTZ
|
||||
let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
network_interface.set_document_to_viewport_transform(transform);
|
||||
|
||||
// Ensure document structure is loaded so that updating the selected nodes has the correct metadata
|
||||
network_interface.load_structure();
|
||||
|
||||
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
|
||||
@@ -64,7 +64,7 @@ pub struct NodeNetworkInterface {
|
||||
network: MemoNetwork,
|
||||
/// Stores all editor information for a NodeNetwork. Should automatically kept in sync by the setter methods when changes to the document network are made.
|
||||
network_metadata: NodeNetworkMetadata,
|
||||
// TODO: Wrap in TransientMetadata Option
|
||||
// TODO: Wrap in a TransientCache
|
||||
/// Stores the document network's structural topology. Should automatically kept in sync by the setter methods when changes to the document network are made.
|
||||
#[serde(skip)]
|
||||
document_metadata: DocumentMetadata,
|
||||
|
||||
@@ -91,7 +91,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Reads the stack dependents through &self if they are already loaded.
|
||||
pub(crate) fn with_stack_dependents<R>(&self, network_path: &[NodeId], read: impl FnOnce(&HashMap<NodeId, LayerOwner>) -> R) -> Option<R> {
|
||||
pub(crate) fn with_stack_dependents_if_loaded<R>(&self, network_path: &[NodeId], read: impl FnOnce(&HashMap<NodeId, LayerOwner>) -> R) -> Option<R> {
|
||||
self.network_metadata(network_path)?.transient_metadata.stack_dependents.with_loaded(read)
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Reads the owned nodes of a layer through &self if they are loaded.
|
||||
pub(crate) fn with_owned_nodes<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&HashSet<NodeId>) -> R) -> Option<R> {
|
||||
pub(crate) fn with_owned_nodes_if_loaded<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&HashSet<NodeId>) -> R) -> Option<R> {
|
||||
let layer_node = self.node_metadata(node_id, network_path)?;
|
||||
if !layer_node.persistent_metadata.is_layer() {
|
||||
return None;
|
||||
@@ -679,38 +679,20 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn get_input_center(&self, input: &InputConnector, network_path: &[NodeId]) -> Option<DVec2> {
|
||||
fn port_center(ports: &Ports, index: usize) -> Option<DVec2> {
|
||||
ports
|
||||
.input_ports
|
||||
.iter()
|
||||
.find_map(|(input_index, click_target)| if index == *input_index { click_target.bounding_box_center() } else { None })
|
||||
}
|
||||
|
||||
match input {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
self.try_load_node_click_targets(node_id, network_path);
|
||||
self.with_node_click_targets(node_id, network_path, |click_targets| port_center(&click_targets.port_click_targets, *input_index))
|
||||
.flatten()
|
||||
}
|
||||
InputConnector::Export(export_index) => self.with_import_export_ports(network_path, |ports| port_center(ports, *export_index)).flatten(),
|
||||
InputConnector::Node { node_id, input_index } => self
|
||||
.with_node_click_targets(node_id, network_path, |click_targets| click_targets.port_click_targets.input_port_position(*input_index))
|
||||
.flatten(),
|
||||
InputConnector::Export(export_index) => self.with_import_export_ports(network_path, |ports| ports.input_port_position(*export_index)).flatten(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_output_center(&self, output: &OutputConnector, network_path: &[NodeId]) -> Option<DVec2> {
|
||||
fn port_center(ports: &Ports, index: usize) -> Option<DVec2> {
|
||||
ports
|
||||
.output_ports
|
||||
.iter()
|
||||
.find_map(|(output_index, click_target)| if index == *output_index { click_target.bounding_box_center() } else { None })
|
||||
}
|
||||
|
||||
match output {
|
||||
OutputConnector::Node { node_id, output_index } => {
|
||||
self.try_load_node_click_targets(node_id, network_path);
|
||||
self.with_node_click_targets(node_id, network_path, |click_targets| port_center(&click_targets.port_click_targets, *output_index))
|
||||
.flatten()
|
||||
}
|
||||
OutputConnector::Import(import_index) => self.with_import_export_ports(network_path, |ports| port_center(ports, *import_index)).flatten(),
|
||||
OutputConnector::Node { node_id, output_index } => self
|
||||
.with_node_click_targets(node_id, network_path, |click_targets| click_targets.port_click_targets.output_port_position(*output_index))
|
||||
.flatten(),
|
||||
OutputConnector::Import(import_index) => self.with_import_export_ports(network_path, |ports| ports.output_port_position(*import_index)).flatten(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,25 +920,29 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Loads the node click targets if needed, then reads them through &self.
|
||||
pub(crate) fn with_loaded_node_click_targets<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option<R> {
|
||||
pub(crate) fn with_node_click_targets<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option<R> {
|
||||
self.try_load_node_click_targets(node_id, network_path);
|
||||
self.with_node_click_targets(node_id, network_path, read)
|
||||
self.with_node_click_targets_if_loaded(node_id, network_path, read)
|
||||
}
|
||||
|
||||
/// Reads the modify import/export click targets through &self, loading them first if needed.
|
||||
pub(crate) fn with_modify_import_export<R>(&self, network_path: &[NodeId], read: impl FnOnce(&ModifyImportExportClickTarget) -> R) -> Option<R> {
|
||||
self.try_load_modify_import_export(network_path);
|
||||
self.network_metadata(network_path)?.transient_metadata.modify_import_export.with_loaded(read)
|
||||
}
|
||||
|
||||
fn try_load_modify_import_export(&self, network_path: &[NodeId]) {
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in modify_import_export");
|
||||
return None;
|
||||
return;
|
||||
};
|
||||
if !network_metadata.transient_metadata.modify_import_export.is_loaded() {
|
||||
self.load_modify_import_export(network_path);
|
||||
}
|
||||
self.network_metadata(network_path)?.transient_metadata.modify_import_export.with_loaded(read)
|
||||
}
|
||||
|
||||
/// Reads the node click targets through &self if they are already loaded.
|
||||
pub(crate) fn with_node_click_targets<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option<R> {
|
||||
pub(crate) fn with_node_click_targets_if_loaded<R>(&self, node_id: &NodeId, network_path: &[NodeId], read: impl FnOnce(&DocumentNodeClickTargets) -> R) -> Option<R> {
|
||||
let node_metadata = self.node_metadata(node_id, network_path)?;
|
||||
let result = node_metadata.transient_metadata.click_targets.with_loaded(read);
|
||||
if result.is_none() {
|
||||
@@ -1095,8 +1081,8 @@ impl NodeNetworkInterface {
|
||||
let name_left = node_top_left.x + NAME_LEFT_OFFSET;
|
||||
let icons_reserve = VISIBILITY_INSET_FROM_LAYER_RIGHT + icons_width + GRIP_WIDTH;
|
||||
let name_right_max = node_top_left.x + width as f64 - icons_reserve;
|
||||
let text_w = text_width(&display_name, FONT_SIZE);
|
||||
let name_right = (name_left + text_w).min(name_right_max);
|
||||
let name_width = text_width(&display_name, FONT_SIZE);
|
||||
let name_right = (name_left + name_width).min(name_right_max);
|
||||
if name_right > name_left {
|
||||
// The 1-grid-tall name strip is centered vertically in the 2-grid-tall layer.
|
||||
let name_top = node_top_left.y + HALF_GRID_SIZE as f64;
|
||||
@@ -1142,7 +1128,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn try_get_node_bounding_box(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
|
||||
self.with_node_click_targets(node_id, network_path, |click_targets| click_targets.node_click_target.bounding_box())
|
||||
self.with_node_click_targets_if_loaded(node_id, network_path, |click_targets| click_targets.node_click_target.bounding_box())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
});
|
||||
nodes.into_iter().for_each(|node_id| {
|
||||
self.with_loaded_node_click_targets(&node_id, network_path, |node_click_targets| {
|
||||
self.with_node_click_targets(&node_id, network_path, |node_click_targets| {
|
||||
let mut node_path = String::new();
|
||||
|
||||
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
|
||||
@@ -146,7 +146,7 @@ impl NodeNetworkInterface {
|
||||
let clicked_nodes = nodes
|
||||
.iter()
|
||||
.filter(|node_id| {
|
||||
self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
self.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
transient_node_metadata.node_click_target.intersect_point_no_stroke(point)
|
||||
}) == Some(true)
|
||||
})
|
||||
@@ -181,7 +181,7 @@ impl NodeNetworkInterface {
|
||||
node_ids
|
||||
.iter()
|
||||
.filter_map(|node_id| {
|
||||
self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
self.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
if let NodeTypeClickTargets::Layer(layer) = &transient_node_metadata.node_type_metadata {
|
||||
match click_target_type {
|
||||
LayerClickTargetTypes::Visibility => layer.visibility_click_target.intersect_point_no_stroke(point).then_some(*node_id),
|
||||
@@ -216,7 +216,7 @@ impl NodeNetworkInterface {
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.filter_map(|node_id| {
|
||||
self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
self.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
transient_node_metadata
|
||||
.port_click_targets
|
||||
.clicked_input_port_from_point(point)
|
||||
@@ -246,7 +246,7 @@ impl NodeNetworkInterface {
|
||||
nodes
|
||||
.iter()
|
||||
.filter_map(|node_id| {
|
||||
self.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
self.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
transient_node_metadata
|
||||
.port_click_targets
|
||||
.clicked_output_port_from_point(point)
|
||||
@@ -266,7 +266,7 @@ impl NodeNetworkInterface {
|
||||
pub fn input_position(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<DVec2> {
|
||||
match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => self
|
||||
.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
transient_node_metadata.port_click_targets.input_port_position(*input_index)
|
||||
})
|
||||
.flatten(),
|
||||
@@ -279,7 +279,7 @@ impl NodeNetworkInterface {
|
||||
pub fn output_position(&self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<DVec2> {
|
||||
match output_connector {
|
||||
OutputConnector::Node { node_id, output_index } => self
|
||||
.with_loaded_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
.with_node_click_targets(node_id, network_path, |transient_node_metadata| {
|
||||
transient_node_metadata.port_click_targets.output_port_position(*output_index)
|
||||
})
|
||||
.flatten(),
|
||||
|
||||
@@ -76,7 +76,6 @@ impl NodeNetworkInterface {
|
||||
else {
|
||||
log::error!("Could not set chain position for layer node {node_id}");
|
||||
}
|
||||
// let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id());
|
||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||
// Reload click target of the layer which encapsulate the chain
|
||||
if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) {
|
||||
@@ -282,7 +281,7 @@ impl NodeNetworkInterface {
|
||||
self.try_load_stack_dependents(network_path);
|
||||
for node_id in node_ids.clone() {
|
||||
if self.is_layer(&node_id, network_path) {
|
||||
self.with_owned_nodes(&node_id, network_path, |owned_nodes| {
|
||||
self.with_owned_nodes_if_loaded(&node_id, network_path, |owned_nodes| {
|
||||
for owned_node in owned_nodes {
|
||||
node_ids.remove(owned_node);
|
||||
}
|
||||
@@ -405,7 +404,7 @@ impl NodeNetworkInterface {
|
||||
shifted_nodes.insert(*node_id);
|
||||
self.shift_node(node_id, IVec2::new(0, shift_sign), network_path);
|
||||
|
||||
if self.with_stack_dependents(network_path, |stack_dependents| matches!(stack_dependents.get(node_id), Some(LayerOwner::None))) == Some(true) {
|
||||
if self.with_stack_dependents_if_loaded(network_path, |stack_dependents| matches!(stack_dependents.get(node_id), Some(LayerOwner::None))) == Some(true) {
|
||||
self.add_drag_offset(node_id, shift_sign, network_path);
|
||||
}
|
||||
|
||||
@@ -445,7 +444,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
if seed_nodes
|
||||
.iter()
|
||||
.any(|seed_node| seed_node == node_id || self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.contains(seed_node)) == Some(true))
|
||||
.any(|seed_node| seed_node == node_id || self.with_owned_nodes_if_loaded(node_id, network_path, |owned_nodes| owned_nodes.contains(seed_node)) == Some(true))
|
||||
{
|
||||
return None;
|
||||
};
|
||||
@@ -499,7 +498,7 @@ impl NodeNetworkInterface {
|
||||
|
||||
self.shift_node(node_id, IVec2::new(0, shift_sign), network_path);
|
||||
|
||||
match self.with_stack_dependents(network_path, |stack_dependents| stack_dependents.get(node_id).cloned()) {
|
||||
match self.with_stack_dependents_if_loaded(network_path, |stack_dependents| stack_dependents.get(node_id).cloned()) {
|
||||
Some(Some(LayerOwner::None)) => self.add_drag_offset(node_id, shift_sign, network_path),
|
||||
Some(Some(LayerOwner::Layer(_))) => log::error!("Node being shifted with a push should not be owned"),
|
||||
Some(None) => log::error!("Could not get layer owner in vertical_shift_with_push for node {node_id}"),
|
||||
@@ -519,7 +518,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
// Shift the nodes that are owned by the layer (if any)
|
||||
if let Some(owned_nodes) = self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.clone()) {
|
||||
if let Some(owned_nodes) = self.with_owned_nodes_if_loaded(node_id, network_path, |owned_nodes| owned_nodes.clone()) {
|
||||
for owned_node in owned_nodes {
|
||||
if self.is_absolute(&owned_node, network_path) {
|
||||
self.try_shift_node(&owned_node, IVec2::new(0, shift_sign), shifted_nodes, network_path);
|
||||
@@ -533,50 +532,54 @@ impl NodeNetworkInterface {
|
||||
self.try_load_stack_dependents(network_path);
|
||||
|
||||
// Check collisions and for all owned nodes and recursively shift them
|
||||
let nodes_to_shift = self.with_stack_dependents(network_path, |stack_dependents| {
|
||||
let mut nodes_to_shift = Vec::new();
|
||||
let nodes_to_shift = self.with_stack_dependents_if_loaded(network_path, |stack_dependents| {
|
||||
// Hot path during drags: borrow the owned nodes for the sweep rather than cloning them per call
|
||||
let collect_collisions = |owned_nodes: &HashSet<NodeId>| {
|
||||
let mut nodes_to_shift = Vec::new();
|
||||
|
||||
let owned_nodes = self.with_owned_nodes(node_id, network_path, |owned_nodes| owned_nodes.clone()).unwrap_or_default();
|
||||
for current_node in owned_nodes.iter().chain(std::iter::once(node_id)) {
|
||||
for node_to_check_collision in stack_dependents {
|
||||
// Do not check collision between any of the owned nodes or the shifted node
|
||||
if owned_nodes.contains(node_to_check_collision.0) || node_to_check_collision.0 == node_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
for current_node in owned_nodes.iter().chain(std::iter::once(node_id)) {
|
||||
for node_to_check_collision in stack_dependents {
|
||||
// Do not check collision between any of the owned nodes or the shifted node
|
||||
if owned_nodes.contains(node_to_check_collision.0) || node_to_check_collision.0 == node_id {
|
||||
continue;
|
||||
}
|
||||
if node_to_check_collision.0 == current_node {
|
||||
continue;
|
||||
}
|
||||
let Some(mut current_node_bounding_box) = self.try_get_node_bounding_box(current_node, network_path) else {
|
||||
log::error!("Could not get bounding box for node {node_id} in shift_selected_nodes");
|
||||
continue;
|
||||
};
|
||||
|
||||
if node_to_check_collision.0 == current_node {
|
||||
continue;
|
||||
}
|
||||
let Some(mut current_node_bounding_box) = self.try_get_node_bounding_box(current_node, network_path) else {
|
||||
log::error!("Could not get bounding box for node {node_id} in shift_selected_nodes");
|
||||
continue;
|
||||
};
|
||||
let Some(node_bounding_box) = self.try_get_node_bounding_box(node_to_check_collision.0, network_path) else {
|
||||
log::error!("Could not get bounding box for node {node_to_check_collision:?} in shift_selected_nodes");
|
||||
continue;
|
||||
};
|
||||
// If the nodes do not intersect horizontally, then there is no collision
|
||||
if current_node_bounding_box[1].x < node_bounding_box[0].x || current_node_bounding_box[0].x > node_bounding_box[1].x {
|
||||
continue;
|
||||
}
|
||||
// Do not check collision if the nodes are currently intersecting
|
||||
if current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(node_bounding_box) = self.try_get_node_bounding_box(node_to_check_collision.0, network_path) else {
|
||||
log::error!("Could not get bounding box for node {node_to_check_collision:?} in shift_selected_nodes");
|
||||
continue;
|
||||
};
|
||||
// If the nodes do not intersect horizontally, then there is no collision
|
||||
if current_node_bounding_box[1].x < node_bounding_box[0].x || current_node_bounding_box[0].x > node_bounding_box[1].x {
|
||||
continue;
|
||||
}
|
||||
// Do not check collision if the nodes are currently intersecting
|
||||
if current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1 {
|
||||
continue;
|
||||
}
|
||||
current_node_bounding_box[1].y += GRID_SIZE as f64 * shift_sign as f64;
|
||||
current_node_bounding_box[0].y += GRID_SIZE as f64 * shift_sign as f64;
|
||||
|
||||
current_node_bounding_box[1].y += GRID_SIZE as f64 * shift_sign as f64;
|
||||
current_node_bounding_box[0].y += GRID_SIZE as f64 * shift_sign as f64;
|
||||
|
||||
let collision = current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1;
|
||||
if collision {
|
||||
nodes_to_shift.push((*node_to_check_collision.0, node_to_check_collision.1.clone()));
|
||||
let collision = current_node_bounding_box[1].y >= node_bounding_box[0].y - 0.1 && current_node_bounding_box[0].y <= node_bounding_box[1].y + 0.1;
|
||||
if collision {
|
||||
nodes_to_shift.push((*node_to_check_collision.0, node_to_check_collision.1.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes_to_shift
|
||||
nodes_to_shift
|
||||
};
|
||||
|
||||
self.with_owned_nodes_if_loaded(node_id, network_path, collect_collisions)
|
||||
.unwrap_or_else(|| collect_collisions(&HashSet::new()))
|
||||
});
|
||||
|
||||
let Some(nodes_to_shift) = nodes_to_shift else {
|
||||
|
||||
@@ -80,14 +80,11 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// Runs an encapsulating-node query, staying silent for the document network which has no encapsulating node.
|
||||
fn query_encapsulating<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result<T, NetworkError>) -> Option<T> {
|
||||
match self.view(network_path).and_then(query) {
|
||||
Ok(value) => Some(value),
|
||||
Err(NetworkError::NoEncapsulatingNode) => None,
|
||||
Err(error) => {
|
||||
log::error!("{error} in {caller}");
|
||||
None
|
||||
}
|
||||
}
|
||||
self.query(network_path, caller, |view| match query(view) {
|
||||
Err(NetworkError::NoEncapsulatingNode) => Ok(None),
|
||||
result => result.map(Some),
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.
|
||||
@@ -153,11 +150,11 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn number_of_imports(&self, network_path: &[NodeId]) -> usize {
|
||||
self.view(network_path).map(|view| view.number_of_imports()).unwrap_or_default()
|
||||
self.query(network_path, "number_of_imports", |view| Ok(view.number_of_imports())).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_exports(&self, network_path: &[NodeId]) -> usize {
|
||||
self.view(network_path).map(|view| view.number_of_exports()).unwrap_or_default()
|
||||
self.query(network_path, "number_of_exports", |view| Ok(view.number_of_exports())).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
|
||||
@@ -172,7 +169,7 @@ impl NodeNetworkInterface {
|
||||
/// Whether the node has an exposed input at index 0 to accept the horizontal flow from upstream.
|
||||
/// A node without one (e.g. a generator) can only be the most-upstream node in a chain.
|
||||
pub fn has_primary_input(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.view(network_path).and_then(|view| view.has_primary_input(node_id)).unwrap_or_default()
|
||||
self.query(network_path, "has_primary_input", |view| view.has_primary_input(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
|
||||
@@ -837,7 +834,7 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
|
||||
pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
|
||||
self.view(network_path).map(|view| view.ordered_pinned_nodes()).unwrap_or_default()
|
||||
self.query(network_path, "ordered_pinned_nodes", |view| Ok(view.ordered_pinned_nodes())).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
@@ -871,15 +868,15 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool {
|
||||
self.view(network_path).map(|view| view.hidden_primary_export()).unwrap_or_default()
|
||||
self.query(network_path, "hidden_primary_export", |view| Ok(view.hidden_primary_export())).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.view(network_path).and_then(|view| view.hidden_primary_output(node_id)).unwrap_or_default()
|
||||
self.query(network_path, "hidden_primary_output", |view| view.hidden_primary_output(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool {
|
||||
self.view(network_path).map(|view| view.hidden_primary_import()).unwrap_or_default()
|
||||
self.query(network_path, "hidden_primary_import", |view| Ok(view.hidden_primary_import())).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
@@ -897,7 +894,7 @@ impl NodeNetworkInterface {
|
||||
/// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene.
|
||||
/// Callers that care about scene membership should source their layers from the document structure or check connectivity separately.
|
||||
pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.view(network_path).map(|view| view.is_artboard(node_id)).unwrap_or_default()
|
||||
self.query(network_path, "is_artboard", |view| Ok(view.is_artboard(node_id))).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// All artboard layers that participate in the scene, excluding disconnected Artboard nodes.
|
||||
|
||||
@@ -195,11 +195,6 @@ impl NodeTemplate {
|
||||
(document_node, persistent_node_metadata)
|
||||
}
|
||||
|
||||
/// The [`DocumentNode`] half alone, for callers performing raw network surgery.
|
||||
pub fn into_document_node(self) -> DocumentNode {
|
||||
self.into_parts().0
|
||||
}
|
||||
|
||||
/// Resizes `input_metadata` to match `inputs` at every nesting level, filling gaps with defaults.
|
||||
pub fn normalize_input_metadata(&mut self) {
|
||||
self.input_metadata.resize_with(self.inputs.len(), InputMetadata::default);
|
||||
|
||||
@@ -239,23 +239,15 @@ impl Ports {
|
||||
}
|
||||
|
||||
pub fn input_port_position(&self, index: usize) -> Option<DVec2> {
|
||||
self.input_ports.iter().find_map(|(port_index, click_target)| {
|
||||
if *port_index == index {
|
||||
click_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.input_ports
|
||||
.iter()
|
||||
.find_map(|(port_index, click_target)| if *port_index == index { click_target.bounding_box_center() } else { None })
|
||||
}
|
||||
|
||||
pub fn output_port_position(&self, index: usize) -> Option<DVec2> {
|
||||
self.output_ports.iter().find_map(|(port_index, click_target)| {
|
||||
if *port_index == index {
|
||||
click_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.output_ports
|
||||
.iter()
|
||||
.find_map(|(port_index, click_target)| if *port_index == index { click_target.bounding_box_center() } else { None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,62 +350,37 @@ pub struct NodeNetworkPersistentMetadata {
|
||||
pub selection_redo_history: VecDeque<SelectedNodes>,
|
||||
}
|
||||
|
||||
/// This is the same as Option, but more clear in the context of having cached metadata either being loaded or unloaded
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum TransientMetadata<T> {
|
||||
Loaded(T),
|
||||
#[default]
|
||||
Unloaded,
|
||||
}
|
||||
|
||||
impl<T> TransientMetadata<T> {
|
||||
/// Set the current transient metadata to unloaded
|
||||
pub fn unload(&mut self) {
|
||||
*self = TransientMetadata::Unloaded;
|
||||
}
|
||||
|
||||
pub fn is_loaded(&self) -> bool {
|
||||
matches!(self, TransientMetadata::Loaded(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// A lazily computed cache slot whose load and read paths work through &self, with interior mutability guarding the stored value.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct TransientCache<T>(std::cell::RefCell<TransientMetadata<T>>);
|
||||
pub(crate) struct TransientCache<T>(std::cell::RefCell<Option<T>>);
|
||||
|
||||
impl<T> Default for TransientCache<T> {
|
||||
fn default() -> Self {
|
||||
TransientCache(std::cell::RefCell::new(TransientMetadata::Unloaded))
|
||||
TransientCache(std::cell::RefCell::new(None))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TransientCache<T> {
|
||||
pub(crate) fn is_loaded(&self) -> bool {
|
||||
self.0.borrow().is_loaded()
|
||||
self.0.borrow().is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn store(&self, value: T) {
|
||||
*self.0.borrow_mut() = TransientMetadata::Loaded(value);
|
||||
*self.0.borrow_mut() = Some(value);
|
||||
}
|
||||
|
||||
pub(crate) fn unload(&self) {
|
||||
*self.0.borrow_mut() = TransientMetadata::Unloaded;
|
||||
*self.0.borrow_mut() = None;
|
||||
}
|
||||
|
||||
/// Runs `read` on the cached value if it is loaded.
|
||||
pub(crate) fn with_loaded<R>(&self, read: impl FnOnce(&T) -> R) -> Option<R> {
|
||||
match &*self.0.borrow() {
|
||||
TransientMetadata::Loaded(value) => Some(read(value)),
|
||||
TransientMetadata::Unloaded => None,
|
||||
}
|
||||
self.0.borrow().as_ref().map(read)
|
||||
}
|
||||
|
||||
/// Direct access without runtime borrow tracking, for callers already holding exclusive access.
|
||||
pub(crate) fn get_loaded_mut(&mut self) -> Option<&mut T> {
|
||||
match self.0.get_mut() {
|
||||
TransientMetadata::Loaded(value) => Some(value),
|
||||
TransientMetadata::Unloaded => None,
|
||||
}
|
||||
self.0.get_mut().as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,6 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
impl<'a, 'p> NetworkView<'a, 'p> {
|
||||
pub fn network(&self) -> &'a NodeNetwork {
|
||||
self.network
|
||||
}
|
||||
|
||||
pub fn network_metadata(&self) -> &'a NodeNetworkMetadata {
|
||||
self.metadata
|
||||
}
|
||||
@@ -291,11 +287,7 @@ impl<'a, 'p> NetworkView<'a, 'p> {
|
||||
}
|
||||
|
||||
pub fn upstream_output_connector(&self, input_connector: &InputConnector) -> Result<Option<OutputConnector>, NetworkError> {
|
||||
Ok(match self.input(input_connector)? {
|
||||
NodeInput::Node { node_id, output_index, .. } => Some(OutputConnector::node(*node_id, *output_index)),
|
||||
NodeInput::Import { import_index, .. } => Some(OutputConnector::Import(*import_index)),
|
||||
_ => None,
|
||||
})
|
||||
Ok(OutputConnector::from_input(self.input(input_connector)?))
|
||||
}
|
||||
|
||||
/// Whether the node reaches the exports by following wires downstream.
|
||||
@@ -326,16 +318,16 @@ impl<'a, 'p> NetworkView<'a, 'p> {
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
if already_visited.contains(&ref_id) {
|
||||
if let &NodeInput::Node { node_id: upstream_id, .. } = input {
|
||||
if already_visited.contains(&upstream_id) {
|
||||
continue;
|
||||
}
|
||||
if ref_id == *target_node_id {
|
||||
if upstream_id == *target_node_id {
|
||||
return true;
|
||||
}
|
||||
let Some(ref_node) = self.network.nodes.get(&ref_id) else { continue };
|
||||
already_visited.insert(ref_id);
|
||||
stack.push(ref_node);
|
||||
let Some(upstream_node) = self.network.nodes.get(&upstream_id) else { continue };
|
||||
already_visited.insert(upstream_id);
|
||||
stack.push(upstream_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user