Skip to content

Commit 1b08a3a

Browse files
Add migrations
1 parent ec422b6 commit 1b08a3a

8 files changed

Lines changed: 54 additions & 42 deletions

File tree

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ pub struct DocumentMessageHandler {
109109
pub graph_view_overlay_open: bool,
110110
/// The current opacity of the faded node graph background that covers up the artwork.
111111
pub graph_fade_artwork_percentage: f64,
112-
/// Resources embedded in the document. Must be None for autosaved documents.
113-
#[serde(rename = "resources", skip_serializing_if = "Option::is_none")]
114-
pub embedded_resources: Option<EmbeddedResources>,
112+
/// Resources embedded in the document. Only propagated if saving to an external file and never for autosaved documents.
113+
#[serde(rename = "resources", default, skip_serializing_if = "EmbeddedResources::is_empty")]
114+
pub embedded_resources: EmbeddedResources,
115115

116116
// =============================================
117117
// Fields omitted from the saved document format
@@ -174,7 +174,7 @@ impl Default for DocumentMessageHandler {
174174
graph_view_overlay_open: false,
175175
snapping_state: SnappingState::default(),
176176
graph_fade_artwork_percentage: 80.,
177-
embedded_resources: None,
177+
embedded_resources: EmbeddedResources::default(),
178178
// =============================================
179179
// Fields omitted from the saved document format
180180
// =============================================
@@ -926,12 +926,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
926926
let folder = self.path.as_ref().and_then(|path| path.parent()).map(|parent| parent.to_path_buf());
927927

928928
let exported = resources.export(&Vec::from_iter(self.used_resources()));
929-
let embedded = EmbeddedResources::from_iter(exported);
930-
if !embedded.is_empty() {
931-
self.embedded_resources = Some(embedded);
932-
}
929+
self.embedded_resources = EmbeddedResources::from_iter(exported);
933930
let content = self.serialize_document();
934-
self.embedded_resources = None;
931+
932+
// Clear embedded resources after serialization to free memory.
933+
let _ = std::mem::take(&mut self.embedded_resources);
935934

936935
responses.add(FrontendMessage::TriggerSaveDocument {
937936
document_id,

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,13 @@ impl<'a> ModifyInputsContext<'a> {
303303
let hash = graphene_std::application_io::ResourceHash::from(png_bytes.as_ref());
304304
self.responses.add(ResourceMessage::Write { data: png_bytes });
305305

306-
let resource_image = resolve_proto_node_type(graphene_std::platform_application_io::resource_image::IDENTIFIER)
307-
.expect("Resource Image node does not exist")
306+
let image_node = resolve_proto_node_type(graphene_std::platform_application_io::image::IDENTIFIER)
307+
.expect("Image node does not exist")
308308
.node_template_input_override([Some(NodeInput::value(TaggedValue::Resource(hash), false))]);
309309

310-
let resource_image_id = NodeId::new();
311-
self.network_interface.insert_node(resource_image_id, resource_image, &[]);
312-
self.network_interface.move_node_to_chain_start(&resource_image_id, layer, &[], self.import);
310+
let image_node_id = NodeId::new();
311+
self.network_interface.insert_node(image_node_id, image_node, &[]);
312+
self.network_interface.move_node_to_chain_start(&image_node_id, layer, &[], self.import);
313313

314314
let transform_id = NodeId::new();
315315
self.network_interface.insert_node(transform_id, transform, &[]);

editor/src/messages/portfolio/document/utility_types/embedded_resources.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ impl EmbeddedResources {
1313
pub fn is_empty(&self) -> bool {
1414
self.resources.is_empty()
1515
}
16+
17+
pub fn store(&mut self, resource: Resource) -> ResourceHash {
18+
let hash = ResourceHash::from(resource.as_ref());
19+
self.resources.insert(hash, resource);
20+
hash
21+
}
1622
}
1723

1824
impl FromIterator<(ResourceHash, Resource)> for EmbeddedResources {

editor/src/messages/portfolio/document_migration.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -549,12 +549,13 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
549549
],
550550
},
551551
NodeReplacement {
552-
node: graphene_std::raster_nodes::std_nodes::image::IDENTIFIER,
552+
node: graphene_std::platform_application_io::image::IDENTIFIER,
553553
aliases: &[
554554
"raster_nodes::std_nodes::ImageValueNode",
555555
"graphene_raster_nodes::std_nodes::ImageValueNode",
556556
"graphene_std::raster::ImageValueNode",
557557
"graphene_std::raster::ImageNode",
558+
"graphene_std::raster_nodes::std_nodes::ImageNode",
558559
],
559560
},
560561
NodeReplacement {
@@ -1588,12 +1589,24 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
15881589
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
15891590
}
15901591

1591-
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) && inputs_count == 1 {
1592-
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
1593-
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
1592+
// Upgrade `image` nodes that stored image data as `Image<Color>`` to `image` nodes that store a reference to the image data as a resource.
1593+
// Encodes the image data as PNG and stores it in the document's embedded resources and rewires the node to reference that resource.
1594+
if reference == DefinitionIdentifier::ProtoNode(graphene_std::platform_application_io::image::IDENTIFIER) && inputs_count == 2 {
1595+
let image = node.inputs.iter().find_map(|input| match input.as_value()? {
1596+
TaggedValue::ImageData(image) => Some(image.clone()),
1597+
_ => None,
1598+
});
1599+
1600+
if let Some(image) = image {
1601+
let hash = document.embedded_resources.store(graphene_std::application_io::Resource::new(image.to_png()));
15941602

1595-
// Insert a new empty input for the image
1596-
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
1603+
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
1604+
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
1605+
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut node_template);
1606+
document
1607+
.network_interface
1608+
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::Resource(hash), false), network_path);
1609+
}
15971610
}
15981611

15991612
if reference == DefinitionIdentifier::ProtoNode(graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER) && inputs_count == 15 {

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -778,20 +778,19 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
778778
}
779779
};
780780

781-
if let Some(resources) = document.embedded_resources.take() {
782-
resources.into_iter().for_each(|(hash, resource)| {
783-
let data: Arc<[u8]> = Arc::from(resource.as_ref());
784-
if ResourceHash::from(data.as_ref()) != hash {
785-
log::error!("Resource hash mismatch for resource with hash {hash}");
786-
return;
787-
}
788-
responses.add(ResourceMessage::Write { data });
789-
});
790-
}
791-
792781
// Upgrade the document's nodes to be compatible with the latest version
793782
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
794783

784+
// Load the document's embedded resources into the resource storage
785+
std::mem::take(&mut document.embedded_resources).into_iter().for_each(|(hash, resource)| {
786+
let data: Arc<[u8]> = Arc::from(resource.as_ref());
787+
if ResourceHash::from(data.as_ref()) != hash {
788+
log::error!("Resource hash mismatch for resource with hash {hash}");
789+
return;
790+
}
791+
responses.add(ResourceMessage::Write { data });
792+
});
793+
795794
// Ensure each node has the metadata for its inputs
796795
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
797796
document.network_interface.validate_input_metadata(node_id, node, &path);

node-graph/graph-craft/src/application_io/resource/indexed_db.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl IndexedDbResourceStorage {
7676
}
7777
};
7878

79-
let key = JsValue::from_str(&hash.to_hex());
79+
let key = JsValue::from_str(&String::from(&hash));
8080

8181
let request = match store.delete(&key) {
8282
Ok(request) => request,
@@ -113,7 +113,7 @@ impl IndexedDbResourceStorage {
113113
}
114114
};
115115

116-
let key = JsValue::from_str(&hash.to_hex());
116+
let key = JsValue::from_str(&String::from(&hash));
117117
let value = js_sys::Uint8Array::from(data.as_slice());
118118

119119
let request = match store.put_with_key(&value, &key) {
@@ -234,7 +234,7 @@ async fn fetch_known_keys(db: &IdbDatabase, store_name: &str) -> Result<HashSet<
234234
async fn fetch_one(db: &IdbDatabase, store_name: &str, hash: ResourceHash) -> Result<Option<Vec<u8>>, JsValue> {
235235
let transaction = db.transaction_with_str(store_name)?;
236236
let store = transaction.object_store(store_name)?;
237-
let key = JsValue::from_str(&hash.to_hex());
237+
let key = JsValue::from_str(&String::from(&hash));
238238
let request = store.get(&key)?;
239239
let result = await_request(&request).await?;
240240
if result.is_undefined() || result.is_null() {

node-graph/nodes/gstd/src/platform_application_io.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
161161
/// Works with standard image format (PNG, JPEG, WebP, etc.). Automatically converts the color space to linear sRGB for accurate compositing.
162162
#[node_macro::node(category("Web Request"))]
163163
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> List<Raster<CPU>> {
164-
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
164+
let Some(image) = ::image::load_from_memory(data.as_ref()).ok() else {
165165
return List::new();
166166
};
167167
let image = image.to_rgba32f();
@@ -269,10 +269,10 @@ pub async fn resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
269269
}
270270

271271
#[node_macro::node(category(""))]
272-
pub fn resource_image<'a: 'n>(_: impl Ctx, resource: Resource) -> List<Raster<CPU>> {
272+
pub fn image<'a: 'n>(_: impl Ctx, resource: Resource) -> List<Raster<CPU>> {
273273
let image_data = resource.as_ref();
274274

275-
let Some(image) = image::load_from_memory(image_data).ok() else {
275+
let Some(image) = ::image::load_from_memory(image_data).ok() else {
276276
return List::new();
277277
};
278278
let image = image.to_rgba32f();

node-graph/nodes/raster/src/std_nodes.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,6 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: List<Color>) -> List
288288
result_list
289289
}
290290

291-
#[node_macro::node(category(""))]
292-
pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> List<Raster<CPU>> {
293-
List::new_from_element(Raster::new_cpu(image))
294-
}
295-
296291
/// Generates customizable procedural noise patterns.
297292
#[node_macro::node(category("Raster: Pattern"))]
298293
#[allow(clippy::too_many_arguments)]

0 commit comments

Comments
 (0)