Skip to content

Commit

Permalink
feat(viewport): add caching support for Viewports (#105)
Browse files Browse the repository at this point in the history
This PR adds a efficient caching system for the `Viewport` widget.
Only the minimal set of nodes is now rerun on changes to the passed in
`ProjectView`, considerably speeding up the viewport.
Caching is applied to both the `ViewportPlugins`, and the `SceneGraph`.

To test it, the `cadara` binary now shows a slowly growing cube.

While there are still some issues left to solve (#100 and #104), this
implementation should be enough for the time being.
  • Loading branch information
maximmaxim345 authored Feb 22, 2025
2 parents e2c3c6d + 7de78e2 commit 2056472
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 74 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ modeling-workspace = { path = "crates/modeling-workspace" }
modeling-module = { path = "crates/modeling-module" }
wasm-libc = { path = "crates/wasm-libc" }

iced = { version = "0.13", features = ["advanced", "webgl"] }
iced = { version = "0.13", features = ["advanced", "webgl", "tokio"] }
tracing-subscriber = "0.3"
thiserror = "1.0"
dyn-clone = "1.0"
Expand Down
72 changes: 66 additions & 6 deletions crates/cadara/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::cognitive_complexity)]

use iced::{time, Subscription};
use modeling_module::ModelingModule;
use std::sync::Arc;
use workspace::Workspace;

struct App {
viewport: viewport::Viewport,
project: project::Project,
project_view: Arc<project::ProjectView>,
data_uuid: project::DataId,
reg: project::ModuleRegistry,
tick_counter: u32,
}

impl Default for App {
Expand All @@ -17,7 +24,9 @@ impl Default for App {
}

#[derive(Debug)]
enum Message {}
enum Message {
Tick,
}

impl App {
fn new() -> Self {
Expand All @@ -43,18 +52,63 @@ impl App {
);
project.apply_changes(cb, &reg).unwrap();

let project_view = project.create_view(&reg).unwrap();
let project_view = Arc::new(project.create_view(&reg).unwrap());

let mut viewport = viewport::Viewport::new(project_view);
let mut viewport = viewport::Viewport::new(project_view.clone());
let workspace = modeling_workspace::ModelingWorkspace { data_uuid };
// TODO: this should dynamically select the first fitting plugin
let plugin = workspace.viewport_plugins()[0].clone();
viewport.pipeline.add_dynamic_plugin(plugin).unwrap();
Self { viewport }
Self {
viewport,
project,
project_view,
data_uuid,
reg,
tick_counter: 0,
}
}

#[expect(clippy::unused_self)] // required by `iced::application`
fn update(&mut self, _message: Message) {}
#[expect(clippy::needless_pass_by_value, reason = "required by iced")]
fn update(&mut self, message: Message) {
match message {
Message::Tick => {
let data = self
.project_view
.open_data_by_id::<ModelingModule>(self.data_uuid)
.unwrap();

let mut cb = project::ChangeBuilder::from(&data);
if self.tick_counter > 100 {
data.apply_persistent(
modeling_module::persistent_data::ModelingTransaction::Create(
modeling_module::persistent_data::Create {
before: None,
operation: modeling_module::operation::ModelingOperation::Grow,
},
),
&mut cb,
);

println!("grow");
self.tick_counter = 0;
} else {
let data = self
.project_view
.open_data_by_id::<ModelingModule>(self.data_uuid)
.unwrap();

let mut cb = project::ChangeBuilder::from(&data);

data.apply_session((), &mut cb);
}
self.project.apply_changes(cb, &self.reg).unwrap();
self.project_view = Arc::new(self.project.create_view(&self.reg).unwrap());
self.viewport.update(self.project_view.clone());
self.tick_counter += 1;
}
}
}

fn view(&self) -> iced::Element<'_, Message> {
let viewport_shader = iced::widget::shader(&self.viewport)
Expand All @@ -63,6 +117,11 @@ impl App {

iced::widget::column!(iced::widget::text("Viewport:"), viewport_shader).into()
}

#[expect(clippy::unused_self)]
fn subscription(&self) -> Subscription<Message> {
time::every(time::Duration::from_millis(20)).map(|_| Message::Tick)
}
}

/// Initializes and runs `CADara`.
Expand All @@ -85,6 +144,7 @@ pub fn run_cadara() {
tracing_subscriber::fmt::init();

iced::application("CADara", App::update, App::view)
.subscription(App::subscription)
.run()
.unwrap();
}
2 changes: 2 additions & 0 deletions crates/modeling-module/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ pub mod sketch;
pub enum ModelingOperation {
Sketch(sketch::Sketch),
Extrude(extrude::Extrude),
Grow,
Shrink,
}
27 changes: 20 additions & 7 deletions crates/modeling-module/src/persistent_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use uuid::Uuid;
pub struct Step {
name: String,
operation: ModelingOperation,
uuid: Uuid,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -38,11 +37,9 @@ impl DataSection for PersistentData {
fn apply(&mut self, args: Self::Args) {
match args {
ModelingTransaction::Create(c) => {
let uuid = Uuid::new_v4();
self.steps.push(Step {
name: "new operation".to_string(),
operation: c.operation,
uuid,
});
}
ModelingTransaction::Update => todo!(),
Expand All @@ -58,19 +55,35 @@ impl DataSection for PersistentData {
impl PersistentData {
#[must_use]
pub fn shape(&self) -> occara::shape::Shape {
let mut scale = 1.0;
for _ in self
.steps
.iter()
.filter(|s| matches!(s.operation, ModelingOperation::Grow))
{
scale *= 1.02;
}

for _ in self
.steps
.iter()
.filter(|s| matches!(s.operation, ModelingOperation::Shrink))
{
scale /= 1.02;
}
let wire = {
let p1 = Point::new(0.0, 0.0, 0.0);
let p2 = Point::new(0.0, 1.0, 0.0);
let p3 = Point::new(1.0, 1.0, 0.0);
let p4 = Point::new(1.0, 0.0, 0.0);
let p2 = Point::new(0.0, scale, 0.0);
let p3 = Point::new(scale, scale, 0.0);
let p4 = Point::new(scale, 0.0, 0.0);
Wire::new(&[
&Edge::line(&p1, &p2),
&Edge::line(&p2, &p3),
&Edge::line(&p3, &p4),
&Edge::line(&p4, &p1),
])
};
let b = wire.face().extrude(&Vector::new(0.0, 0.0, 1.0));
let b = wire.face().extrude(&Vector::new(0.0, 0.0, scale));

let mut f = b.fillet();
for e in b.edges() {
Expand Down
7 changes: 2 additions & 5 deletions crates/modeling-workspace/src/viewport.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use computegraph::{node, ComputeGraph};
use viewport::{RenderNodePorts, SceneGraphBuilder, UpdateNodePorts};
use viewport::{ProjectState, RenderNodePorts, SceneGraphBuilder, UpdateNodePorts};

mod camera;
mod rendering;
Expand All @@ -15,10 +15,7 @@ pub struct ModelingViewportPlugin {
}

#[node(ModelingViewportPlugin -> (scene, output))]
fn run(
&self,
_project: &project::ProjectView,
) -> (viewport::SceneGraph, ModelingViewportPluginOutput) {
fn run(&self, _project: &ProjectState) -> (viewport::SceneGraph, ModelingViewportPluginOutput) {
let mut graph = ComputeGraph::new();
let model_node = graph
.add_node(
Expand Down
13 changes: 7 additions & 6 deletions crates/modeling-workspace/src/viewport/scene_nodes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use computegraph::node;
use iced::widget::shader;
use viewport::ViewportEvent;
use project::TrackedDataView;
use viewport::{ProjectState, ViewportEvent};

use super::{
rendering::{MeshData, RenderPrimitive, Vertex},
Expand All @@ -13,11 +14,11 @@ pub struct ModelNode {
}

#[node(ModelNode -> !)]
fn run(&self, project: &project::ProjectView) -> occara::shape::Shape {
let data_view: project::DataView<modeling_module::ModelingModule> =
fn run(&self, project: &ProjectState) -> occara::shape::Shape {
let data_view: TrackedDataView<modeling_module::ModelingModule> =
project.open_data_by_id(self.data_uuid).unwrap();

data_view.persistent.shape()
data_view.persistent().shape()
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -64,10 +65,10 @@ fn run(
&self,
event: &ViewportEvent,
state: &ViewportState,
project: &project::ProjectView,
project: &ProjectState,
) -> ViewportState {
let mut state = (*state).clone();
let _data_view: project::DataView<modeling_module::ModelingModule> =
let _data_view: TrackedDataView<modeling_module::ModelingModule> =
project.open_data_by_id(self.data_uuid).unwrap();
if let shader::Event::Mouse(m) = event.event {
match m {
Expand Down
29 changes: 23 additions & 6 deletions crates/viewport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ mod pipeline;

#[doc(inline)]
pub use pipeline::{
DynamicViewportPlugin, ExecuteError, PipelineAddError, RenderNodePorts, SceneGraph,
SceneGraphBuilder, UpdateNodePorts, ViewportPipeline, ViewportPlugin,
ViewportPluginValidationError,
DynamicViewportPlugin, ExecuteError, PipelineAddError, ProjectState, RenderNodePorts,
SceneGraph, SceneGraphBuilder, UpdateNodePorts, ViewportCache, ViewportPipeline,
ViewportPlugin, ViewportPluginValidationError,
};

#[derive(Debug)]
Expand All @@ -76,17 +76,27 @@ pub struct ViewportState {
#[derive(Clone)]
pub struct Viewport {
pub pipeline: ViewportPipeline,
pub project_view: ProjectView,
pub project_view: Arc<ProjectView>,
pub prev_project_view: Option<Arc<ProjectView>>,
pub project_view_version: u64,
}

impl Viewport {
#[must_use]
pub fn new(project_view: ProjectView) -> Self {
pub fn new(project_view: Arc<ProjectView>) -> Self {
Self {
pipeline: ViewportPipeline::default(),
project_view,
prev_project_view: None,
project_view_version: 1,
}
}

/// Update the viewport with a new version of the [`ProjectView`].
pub fn update(&mut self, project_view: Arc<ProjectView>) {
self.prev_project_view = Some(std::mem::replace(&mut self.project_view, project_view));
self.project_view_version += 1;
}
}

impl<Message> shader::Program<Message> for Viewport {
Expand Down Expand Up @@ -115,6 +125,7 @@ impl<Message> shader::Program<Message> for Viewport {
&mut state.state.lock().unwrap(),
event,
self.project_view.clone(),
self.project_view_version,
)
.unwrap();
(iced::advanced::graphics::core::event::Status::Ignored, None)
Expand All @@ -130,6 +141,8 @@ impl<Message> shader::Program<Message> for Viewport {
pipeline: self.pipeline.clone(),
state: state.clone(),
project_view: self.project_view.clone(),
prev_project_view: self.prev_project_view.clone(),
project_view_version: self.project_view_version,
}
}

Expand All @@ -147,7 +160,9 @@ impl<Message> shader::Program<Message> for Viewport {
pub struct ShaderPrimitive {
pub pipeline: ViewportPipeline,
pub state: ViewportState,
pub project_view: ProjectView,
pub project_view: Arc<ProjectView>,
pub prev_project_view: Option<Arc<ProjectView>>,
pub project_view_version: u64,
}

impl shader::Primitive for ShaderPrimitive {
Expand All @@ -165,6 +180,7 @@ impl shader::Primitive for ShaderPrimitive {
.compute_primitive(
&mut self.state.state.lock().unwrap(),
self.project_view.clone(),
self.project_view_version,
)
.unwrap();
a.prepare(device, queue, format, storage, bounds, viewport);
Expand All @@ -182,6 +198,7 @@ impl shader::Primitive for ShaderPrimitive {
.compute_primitive(
&mut self.state.state.lock().unwrap(),
self.project_view.clone(),
self.project_view_version,
)
.unwrap();
a.render(encoder, storage, target, clip_bounds);
Expand Down
Loading

0 comments on commit 2056472

Please sign in to comment.