Reduce history system's excessive memory usage from storing copies of images each step (#845)
* Limit saved undo history * Do not commit path every mouse move event * Reference count images in history system instead of cloning the image data * Increase history storage step limit to 100 Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
a756a2dd3b
commit
9d27781c37
|
|
@ -77,5 +77,6 @@ pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
|
||||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.15"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.15"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||||
|
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||||
|
|
||||||
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
|
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ pub struct FrontendDocumentDetails {
|
||||||
pub struct FrontendImageData {
|
pub struct FrontendImageData {
|
||||||
pub path: Vec<LayerId>,
|
pub path: Vec<LayerId>,
|
||||||
pub mime: String,
|
pub mime: String,
|
||||||
#[serde(rename = "imageData")]
|
#[serde(skip)]
|
||||||
pub image_data: Vec<u8>,
|
pub image_data: std::rc::Rc<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ pub struct DocumentMessageHandler {
|
||||||
pub overlays_visible: bool,
|
pub overlays_visible: bool,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub document_undo_history: Vec<DocumentSave>,
|
pub document_undo_history: VecDeque<DocumentSave>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub document_redo_history: Vec<DocumentSave>,
|
pub document_redo_history: VecDeque<DocumentSave>,
|
||||||
|
|
||||||
#[serde(with = "vectorize_layer_metadata")]
|
#[serde(with = "vectorize_layer_metadata")]
|
||||||
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
|
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
|
||||||
|
|
@ -80,8 +80,8 @@ impl Default for DocumentMessageHandler {
|
||||||
snapping_enabled: true,
|
snapping_enabled: true,
|
||||||
overlays_visible: true,
|
overlays_visible: true,
|
||||||
|
|
||||||
document_undo_history: Vec::new(),
|
document_undo_history: VecDeque::new(),
|
||||||
document_redo_history: Vec::new(),
|
document_redo_history: VecDeque::new(),
|
||||||
|
|
||||||
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
|
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
|
||||||
layer_range_selection_reference: Vec::new(),
|
layer_range_selection_reference: Vec::new(),
|
||||||
|
|
@ -508,7 +508,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
MoveSelectedManipulatorPoints { layer_path, delta } => {
|
MoveSelectedManipulatorPoints { layer_path, delta } => {
|
||||||
self.backup(responses);
|
|
||||||
if let Ok(_layer) = self.graphene_document.layer(&layer_path) {
|
if let Ok(_layer) = self.graphene_document.layer(&layer_path) {
|
||||||
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta }.into());
|
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta }.into());
|
||||||
}
|
}
|
||||||
|
|
@ -541,6 +540,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
let image_data = std::rc::Rc::new(image_data);
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
FrontendMessage::UpdateImageData {
|
FrontendMessage::UpdateImageData {
|
||||||
document_id,
|
document_id,
|
||||||
|
|
@ -1322,7 +1322,10 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
|
||||||
self.document_redo_history.clear();
|
self.document_redo_history.clear();
|
||||||
self.document_undo_history.push((self.graphene_document.clone(), self.layer_metadata.clone()));
|
self.document_undo_history.push_back((self.graphene_document.clone(), self.layer_metadata.clone()));
|
||||||
|
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||||
|
self.document_undo_history.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||||
|
|
@ -1340,7 +1343,7 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
|
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
|
||||||
|
|
||||||
match self.document_undo_history.pop() {
|
match self.document_undo_history.pop_back() {
|
||||||
Some((document, layer_metadata)) => {
|
Some((document, layer_metadata)) => {
|
||||||
// Update the currently displayed layer on the Properties panel if the selection changes after an undo action
|
// Update the currently displayed layer on the Properties panel if the selection changes after an undo action
|
||||||
// Also appropriately update the Properties panel if an undo action results in a layer being deleted
|
// Also appropriately update the Properties panel if an undo action results in a layer being deleted
|
||||||
|
|
@ -1352,7 +1355,10 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
let document = std::mem::replace(&mut self.graphene_document, document);
|
let document = std::mem::replace(&mut self.graphene_document, document);
|
||||||
let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata);
|
let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata);
|
||||||
self.document_redo_history.push((document, layer_metadata));
|
self.document_redo_history.push_back((document, layer_metadata));
|
||||||
|
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||||
|
self.document_redo_history.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
for layer in self.layer_metadata.keys() {
|
for layer in self.layer_metadata.keys() {
|
||||||
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
|
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
|
||||||
|
|
@ -1370,7 +1376,7 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
|
let selected_paths: Vec<Vec<LayerId>> = self.selected_layers().map(|path| path.to_vec()).collect();
|
||||||
|
|
||||||
match self.document_redo_history.pop() {
|
match self.document_redo_history.pop_back() {
|
||||||
Some((document, layer_metadata)) => {
|
Some((document, layer_metadata)) => {
|
||||||
// Update currently displayed layer on property panel if selection changes after redo action
|
// Update currently displayed layer on property panel if selection changes after redo action
|
||||||
// Also appropriately update property panel if redo action results in a layer being added
|
// Also appropriately update property panel if redo action results in a layer being added
|
||||||
|
|
@ -1382,7 +1388,10 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
let document = std::mem::replace(&mut self.graphene_document, document);
|
let document = std::mem::replace(&mut self.graphene_document, document);
|
||||||
let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata);
|
let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata);
|
||||||
self.document_undo_history.push((document, layer_metadata));
|
self.document_undo_history.push_back((document, layer_metadata));
|
||||||
|
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||||
|
self.document_undo_history.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
for layer in self.layer_metadata.keys() {
|
for layer in self.layer_metadata.keys() {
|
||||||
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
|
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into())
|
||||||
|
|
@ -1398,6 +1407,7 @@ impl DocumentMessageHandler {
|
||||||
// We can use the last state of the document to serve as the identifier to compare against
|
// We can use the last state of the document to serve as the identifier to compare against
|
||||||
// This is useful since when the document is empty the identifier will be 0
|
// This is useful since when the document is empty the identifier will be 0
|
||||||
self.document_undo_history
|
self.document_undo_history
|
||||||
|
.iter()
|
||||||
.last()
|
.last()
|
||||||
.map(|(graphene_document, _)| graphene_document.current_state_identifier())
|
.map(|(graphene_document, _)| graphene_document.current_state_identifier())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
let mime = "image/bmp".to_string();
|
let mime = "image/bmp".to_string();
|
||||||
|
let image_data = std::rc::Rc::new(image_data);
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
FrontendMessage::UpdateImageData {
|
FrontendMessage::UpdateImageData {
|
||||||
document_id,
|
document_id,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub fn set_random_seed(seed: u64) {
|
||||||
/// This avoids creating a json with a list millions of numbers long.
|
/// This avoids creating a json with a list millions of numbers long.
|
||||||
#[wasm_bindgen(module = "@/wasm-communication/editor")]
|
#[wasm_bindgen(module = "@/wasm-communication/editor")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn updateImage(path: Vec<u64>, mime: String, imageData: Vec<u8>, document_id: u64);
|
fn updateImage(path: Vec<u64>, mime: String, imageData: &[u8], document_id: u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides a handle to access the raw WASM memory
|
/// Provides a handle to access the raw WASM memory
|
||||||
|
|
@ -100,7 +100,7 @@ impl JsEditorHandle {
|
||||||
// Special case for update image data to avoid serialization times.
|
// Special case for update image data to avoid serialization times.
|
||||||
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
||||||
for image in image_data {
|
for image in image_data {
|
||||||
updateImage(image.path, image.mime, image.image_data, document_id);
|
updateImage(image.path, image.mime, &image.image_data, document_id);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,7 @@ impl Document {
|
||||||
image_data,
|
image_data,
|
||||||
mime,
|
mime,
|
||||||
} => {
|
} => {
|
||||||
|
let image_data = std::rc::Rc::new(image_data);
|
||||||
let layer = Layer::new(LayerDataType::Image(ImageLayer::new(mime, image_data)), transform);
|
let layer = Layer::new(LayerDataType::Image(ImageLayer::new(mime, image_data)), transform);
|
||||||
|
|
||||||
self.set_layer(&path, layer, insert_index)?;
|
self.set_layer(&path, layer, insert_index)?;
|
||||||
|
|
@ -606,6 +607,7 @@ impl Document {
|
||||||
Operation::SetNodeGraphFrameImageData { layer_path, image_data } => {
|
Operation::SetNodeGraphFrameImageData { layer_path, image_data } => {
|
||||||
let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer");
|
let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer");
|
||||||
if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data {
|
if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data {
|
||||||
|
let image_data = std::rc::Rc::new(image_data);
|
||||||
node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data });
|
node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data });
|
||||||
} else {
|
} else {
|
||||||
panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type");
|
panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type");
|
||||||
|
|
@ -831,6 +833,7 @@ impl Document {
|
||||||
Operation::ImaginateSetImageData { layer_path, image_data } => {
|
Operation::ImaginateSetImageData { layer_path, image_data } => {
|
||||||
let layer = self.layer_mut(&layer_path).expect("Setting Imaginate image data for invalid layer");
|
let layer = self.layer_mut(&layer_path).expect("Setting Imaginate image data for invalid layer");
|
||||||
if let LayerDataType::Imaginate(imaginate) = &mut layer.data {
|
if let LayerDataType::Imaginate(imaginate) = &mut layer.data {
|
||||||
|
let image_data = std::rc::Rc::new(image_data);
|
||||||
imaginate.image_data = Some(ImaginateImageData { image_data });
|
imaginate.image_data = Some(ImaginateImageData { image_data });
|
||||||
} else {
|
} else {
|
||||||
panic!("Incorrectly trying to set image data for a layer that is not an Imaginate layer type");
|
panic!("Incorrectly trying to set image data for a layer that is not an Imaginate layer type");
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,21 @@
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serializer};
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
pub fn as_base64<T, S>(key: &T, serializer: S) -> Result<S::Ok, S::Error>
|
pub fn as_base64<S>(key: &std::rc::Rc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
T: AsRef<[u8]>,
|
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&base64::encode(key.as_ref()))
|
serializer.serialize_str(&base64::encode(key.as_slice()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_base64<'a, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
pub fn from_base64<'a, D>(deserializer: D) -> Result<std::rc::Rc<Vec<u8>>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'a>,
|
D: Deserializer<'a>,
|
||||||
{
|
{
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
|
|
||||||
String::deserialize(deserializer)
|
String::deserialize(deserializer)
|
||||||
.and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string())))
|
.and_then(|string| base64::decode(string).map_err(|err| Error::custom(err.to_string())))
|
||||||
|
.map(std::rc::Rc::new)
|
||||||
.map_err(serde::de::Error::custom)
|
.map_err(serde::de::Error::custom)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use std::fmt::Write;
|
||||||
pub struct ImageLayer {
|
pub struct ImageLayer {
|
||||||
pub mime: String,
|
pub mime: String,
|
||||||
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
||||||
pub image_data: Vec<u8>,
|
pub image_data: std::rc::Rc<Vec<u8>>,
|
||||||
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
|
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub blob_url: Option<String>,
|
pub blob_url: Option<String>,
|
||||||
|
|
@ -75,7 +75,7 @@ impl LayerData for ImageLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageLayer {
|
impl ImageLayer {
|
||||||
pub fn new(mime: String, image_data: Vec<u8>) -> Self {
|
pub fn new(mime: String, image_data: std::rc::Rc<Vec<u8>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mime,
|
mime,
|
||||||
image_data,
|
image_data,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ pub enum ImaginateStatus {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct ImaginateImageData {
|
pub struct ImaginateImageData {
|
||||||
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
||||||
pub image_data: Vec<u8>,
|
pub image_data: std::rc::Rc<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ pub struct NodeGraphFrameLayer {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct ImageData {
|
pub struct ImageData {
|
||||||
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
||||||
pub image_data: Vec<u8>,
|
pub image_data: std::rc::Rc<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for NodeGraphFrameLayer {
|
impl LayerData for NodeGraphFrameLayer {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue