Skip to content

Commit efcabdf

Browse files
Merge pull request #75 from nannou-org/persistence
feat: Persist the graph and gantz GUI state between runs in `egui_graph` demo
2 parents 04fc443 + 7cc98d5 commit efcabdf

8 files changed

Lines changed: 154 additions & 45 deletions

File tree

Cargo.lock

Lines changed: 38 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ repository = "https://github.com/nannou-org/gantz"
1111

1212
[workspace.dependencies]
1313
dyn-hash = "0.2.2"
14-
eframe = { git = "https://github.com/mitchmindtree/egui.git", branch = "scene-drag-pan-buttons" }
14+
eframe = { git = "https://github.com/mitchmindtree/egui.git", branch = "scene-drag-pan-buttons", features = ["persistence"] }
1515
egui = { git = "https://github.com/mitchmindtree/egui.git", branch = "scene-drag-pan-buttons", default-features = false }
1616
egui_extras = { git = "https://github.com/mitchmindtree/egui.git", branch = "scene-drag-pan-buttons", default-features = false, features = ["syntect"] }
1717
egui_graph = { git = "https://github.com/mitchmindtree/egui_graph.git", branch = "develop" }
1818
env_logger = "0.10"
1919
gantz_core = { path = "crates/gantz_core" }
20-
gantz_egui = { path = "crates/gantz_egui" }
20+
gantz_egui = { path = "crates/gantz_egui", features = ["serde"] }
2121
gantz_std = { path = "crates/gantz_std" }
2222
humantime = "2.2"
2323
log = { version = "0.4", features = ["serde"] }
2424
petgraph = { version = "0.6", features = ["serde-1"] }
25+
ron = "0.10"
2526
serde = { version = "1", features = ["derive"] }
2627
serde_json = "1"
2728
steel-core = { git = "https://github.com/mattwparas/steel.git" }

crates/gantz_egui/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ gantz_std.workspace = true
1616
humantime.workspace = true
1717
log.workspace = true
1818
petgraph.workspace = true
19+
serde.workspace = true
1920
steel-core.workspace = true
2021
sublime_fuzzy.workspace = true
2122

2223
[dev-dependencies]
2324
dyn-hash.workspace = true
2425
eframe.workspace = true
25-
serde.workspace = true
26+
ron.workspace = true
2627
typetag.workspace = true

crates/gantz_egui/examples/demo.rs

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
use dyn_hash::DynHash;
88
use eframe::egui;
9-
use gantz_core::{Edge, steel::steel_vm::engine::Engine};
9+
use gantz_core::steel::steel_vm::engine::Engine;
1010
use gantz_egui::widget::gantz::{INLET_NAME, OUTLET_NAME};
1111
use petgraph::visit::EdgeRef;
1212
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeRef};
@@ -125,27 +125,6 @@ fn node_type_registry() -> NodeTypeRegistry {
125125
type Graph = gantz_core::node::graph::Graph<Box<dyn Node>>;
126126
type GraphNode = gantz_core::node::GraphNode<Box<dyn Node>>;
127127

128-
/// Setup a simple demo graph.
129-
fn new_graph() -> GraphNode {
130-
let mut graph = GraphNode::default();
131-
132-
let button = graph.add_node(Box::new(gantz_std::Bang::default()) as Box<dyn Node>);
133-
let n0 = graph.add_node(Box::new(gantz_std::Number::default()) as Box<_>);
134-
let n1 = graph.add_node(Box::new(gantz_std::Number::default()) as Box<_>);
135-
let add = graph.add_node(Box::new(gantz_std::ops::Add::default()) as Box<_>);
136-
let n2 = graph.add_node(Box::new(gantz_std::Number::default()) as Box<_>);
137-
let log = graph.add_node(Box::new(gantz_std::log::Log::default()) as Box<_>);
138-
139-
graph.add_edge(button, n0, Edge::from((0, 0)));
140-
graph.add_edge(button, n1, Edge::from((0, 0)));
141-
graph.add_edge(n0, add, Edge::from((0, 0)));
142-
graph.add_edge(n1, add, Edge::from((0, 1)));
143-
graph.add_edge(add, n2, Edge::from((0, 0)));
144-
graph.add_edge(n2, log, Edge::from((0, 0)));
145-
146-
graph
147-
}
148-
149128
// ----------------------------------------------
150129
// Model
151130
// ----------------------------------------------
@@ -158,6 +137,7 @@ struct State {
158137
graph: GraphNode,
159138
graph_hash: u64,
160139
compiled_module: String,
140+
logger: gantz_egui::widget::log_view::Logger,
161141
gantz: gantz_egui::widget::GantzState,
162142
node_ty_reg: NodeTypeRegistry,
163143
vm: Engine,
@@ -168,9 +148,41 @@ struct State {
168148
// ----------------------------------------------
169149

170150
impl App {
151+
/// The key at which the graph is to be saved/loaded.
152+
const GRAPH_KEY: &str = "graph";
153+
/// The key at which the gantz widget state is to be saved/loaded.
154+
const GANTZ_GUI_STATE_KEY: &str = "gantz-widget-state";
155+
171156
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
172-
// Graph setup
173-
let graph = new_graph();
157+
// Setup logging.
158+
let logger = gantz_egui::widget::log_view::Logger::default();
159+
log::set_boxed_logger(Box::new(logger.clone())).unwrap();
160+
log::set_max_level(log::LevelFilter::Info);
161+
162+
// Load the graph or fallback to a default empty one.
163+
let graph = cc
164+
.storage
165+
.as_ref()
166+
.and_then(|storage| {
167+
let Some(graph_str) = storage.get_string(Self::GRAPH_KEY) else {
168+
log::info!("No existing graph to load");
169+
return None;
170+
};
171+
match ron::de::from_str(&graph_str) {
172+
Ok(graph) => {
173+
log::info!("Successfully loaded graph from storage");
174+
Some(graph)
175+
}
176+
Err(e) => {
177+
log::error!("Failed to deserialize graph: {e}");
178+
None
179+
}
180+
}
181+
})
182+
.unwrap_or_else(|| {
183+
log::info!("Initialising default graph");
184+
GraphNode::default()
185+
});
174186
let graph_hash = graph_hash(&graph);
175187

176188
// VM setup
@@ -183,9 +195,34 @@ impl App {
183195
// GUI setup
184196
let ctx = &cc.egui_ctx;
185197
ctx.set_fonts(egui::FontDefinitions::default());
186-
let gantz = gantz_egui::widget::GantzState::new();
198+
199+
// Load the gantz GUI state or fallback to default.
200+
let gantz = cc
201+
.storage
202+
.as_ref()
203+
.and_then(|storage| {
204+
let Some(gantz_str) = storage.get_string(Self::GANTZ_GUI_STATE_KEY) else {
205+
log::info!("No existing gantz GUI state to load");
206+
return None;
207+
};
208+
match ron::de::from_str(&gantz_str) {
209+
Ok(gantz) => {
210+
log::info!("Successfully loaded gantz GUI state from storage");
211+
Some(gantz)
212+
}
213+
Err(e) => {
214+
log::error!("Failed to deserialize gantz GUI state: {e}");
215+
None
216+
}
217+
}
218+
})
219+
.unwrap_or_else(|| {
220+
log::info!("Initialising default gantz GUI state");
221+
gantz_egui::widget::GantzState::new()
222+
});
187223

188224
let state = State {
225+
logger,
189226
gantz,
190227
graph,
191228
graph_hash,
@@ -213,6 +250,35 @@ impl eframe::App for App {
213250
// Process any pending commands generated from the UI.
214251
process_cmds(&mut self.state.gantz, &mut self.state.vm);
215252
}
253+
254+
fn save(&mut self, storage: &mut dyn eframe::Storage) {
255+
// Save the graph.
256+
let graph_str = match ron::to_string(&self.state.graph) {
257+
Err(e) => {
258+
log::error!("Failed to serialize and save graph: {e}");
259+
return;
260+
}
261+
Ok(s) => s,
262+
};
263+
storage.set_string(Self::GRAPH_KEY, graph_str);
264+
log::info!("Successfully persisted graph");
265+
266+
// Save the gantz GUI state.
267+
let gantz_str = match ron::to_string(&self.state.gantz) {
268+
Err(e) => {
269+
log::error!("Failed to serialize and save gantz GUI state: {e}");
270+
return;
271+
}
272+
Ok(s) => s,
273+
};
274+
storage.set_string(Self::GANTZ_GUI_STATE_KEY, gantz_str);
275+
log::info!("Successfully persisted gantz GUI state");
276+
}
277+
278+
// Persist GUI state.
279+
fn persist_egui_memory(&self) -> bool {
280+
true
281+
}
216282
}
217283

218284
// Drain the commands provided by the UI and process them.
@@ -281,6 +347,7 @@ fn gui(ctx: &egui::Context, state: &mut State) {
281347
.show(ctx, |ui| {
282348
gantz_egui::widget::Gantz::new(&state.node_ty_reg, &mut state.graph).show(
283349
&mut state.gantz,
350+
&state.logger,
284351
&state.compiled_module,
285352
&mut state.vm,
286353
ui,

crates/gantz_egui/src/widget/command_palette.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub trait Command: Copy + Sized {
1717
}
1818
}
1919

20-
#[derive(Default)]
20+
#[derive(Default, serde::Deserialize, serde::Serialize)]
2121
pub struct CommandPalette {
2222
visible: bool,
2323
query: String,

crates/gantz_egui/src/widget/gantz.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ where
4242
root: &'a mut gantz_core::node::GraphNode<T::Node>,
4343
}
4444

45+
#[derive(serde::Deserialize, serde::Serialize)]
4546
pub struct GantzState {
4647
pub graph_scene: GraphSceneState,
4748
pub path: Vec<node::Id>,
@@ -51,18 +52,18 @@ pub struct GantzState {
5152
pub auto_layout: bool,
5253
pub layout_flow: egui::Direction,
5354
pub center_view: bool,
54-
pub logger: widget::log_view::Logger,
5555
}
5656

5757
/// UI state relevant to each nested graph within the tree.
5858
pub type Graphs = HashMap<Vec<node::Id>, GraphState>;
5959

6060
/// UI state relevant to a graph at a certain path within the root.
61+
#[derive(serde::Deserialize, serde::Serialize)]
6162
pub struct GraphState {
6263
pub view: egui_graph::View,
6364
}
6465

65-
#[derive(Default)]
66+
#[derive(Default, serde::Deserialize, serde::Serialize)]
6667
pub struct ViewToggles {
6768
pub node_inspector: bool,
6869
pub logs: bool,
@@ -92,6 +93,7 @@ where
9293
pub fn show(
9394
self,
9495
state: &mut GantzState,
96+
logger: &widget::log_view::Logger,
9597
compiled_steel: &str,
9698
vm: &mut Engine,
9799
ui: &mut egui::Ui,
@@ -102,7 +104,7 @@ where
102104
graph_config(self.root, state, ui);
103105
}
104106
if state.view_toggles.logs {
105-
log_view(&state.logger, ui);
107+
log_view(logger, ui);
106108
}
107109
if state.view_toggles.node_inspector {
108110
node_inspector(self.root, vm, state, ui);
@@ -123,7 +125,6 @@ impl GantzState {
123125
}
124126

125127
pub fn from_graphs(graphs: Graphs) -> Self {
126-
let logger = widget::log_view::setup_logging();
127128
Self {
128129
graph_scene: Default::default(),
129130
path: vec![],
@@ -132,7 +133,6 @@ impl GantzState {
132133
center_view: false,
133134
command_palette: widget::CommandPalette::default(),
134135
layout_flow: Self::DEFAULT_DIRECTION,
135-
logger,
136136
view_toggles: ViewToggles::default(),
137137
}
138138
}

crates/gantz_egui/src/widget/graph_scene.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,22 @@ pub struct GraphScene<'a, N> {
3838

3939
/// State associated with the [`GraphScene`] widget that can be useful to access
4040
/// outside the widget.
41-
#[derive(Default)]
41+
#[derive(Default, serde::Deserialize, serde::Serialize)]
4242
pub struct GraphSceneState {
4343
pub interaction: Interaction,
4444
/// Commands queued within the graph scene widget to be handled externally.
45+
#[serde(default, skip)]
4546
pub cmds: Vec<Cmd>,
4647
}
4748

48-
#[derive(Default)]
49+
#[derive(Default, serde::Deserialize, serde::Serialize)]
4950
pub struct Interaction {
5051
pub selection: Selection,
52+
#[serde(default, skip)]
5153
pub edge_in_progress: Option<(NodeIndex, SocketKind, usize)>,
5254
}
5355

54-
#[derive(Default)]
56+
#[derive(Default, serde::Deserialize, serde::Serialize)]
5557
pub struct Selection {
5658
pub nodes: HashSet<NodeIndex>,
5759
pub edges: HashSet<EdgeIndex>,

0 commit comments

Comments
 (0)