From 86901b67daa3c9255d9b58c8f14c2344d9d8ddad Mon Sep 17 00:00:00 2001 From: Matt Ferraro Date: Tue, 9 Apr 2024 22:02:16 -0400 Subject: [PATCH] start working on EvolutionLog and OpLog --- packages/cadmium/Cargo.lock | 7 + packages/cadmium/Cargo.toml | 1 + packages/cadmium/src/lib.rs | 1 + packages/cadmium/src/main.rs | 48 +++++- packages/cadmium/src/oplog/mod.rs | 258 ++++++++++++++++++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 packages/cadmium/src/oplog/mod.rs diff --git a/packages/cadmium/Cargo.lock b/packages/cadmium/Cargo.lock index 21261849..ba55bf66 100644 --- a/packages/cadmium/Cargo.lock +++ b/packages/cadmium/Cargo.lock @@ -105,6 +105,7 @@ dependencies = [ "console_error_panic_hook", "crc32fast", "geo", + "highhash", "indexmap 2.1.0", "itertools 0.11.0", "serde", @@ -454,6 +455,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "highhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "208b08088d46b46f504b584a4eed9c4d93f86e2ff820e028362b5651312ddf6a" + [[package]] name = "iana-time-zone" version = "0.1.58" diff --git a/packages/cadmium/Cargo.toml b/packages/cadmium/Cargo.toml index 006132ca..d68a375c 100644 --- a/packages/cadmium/Cargo.toml +++ b/packages/cadmium/Cargo.toml @@ -27,6 +27,7 @@ geo = "0.26.0" serde_with = "3.4.0" crc32fast = "1.3.2" indexmap = "2.1.0" +highhash = "0.2.0" [patch.crates-io] tsify = { git = "https://github.com/siefkenj/tsify" } diff --git a/packages/cadmium/src/lib.rs b/packages/cadmium/src/lib.rs index 8d5084d9..ce4403df 100644 --- a/packages/cadmium/src/lib.rs +++ b/packages/cadmium/src/lib.rs @@ -3,6 +3,7 @@ use wasm_bindgen::prelude::*; extern crate console_error_panic_hook; pub mod extrusion; +pub mod oplog; pub mod project; pub mod sketch; diff --git a/packages/cadmium/src/main.rs b/packages/cadmium/src/main.rs index b1977039..84503d6b 100644 --- a/packages/cadmium/src/main.rs +++ b/packages/cadmium/src/main.rs @@ -1,17 +1,63 @@ #![allow(dead_code, unused)] use std::ops::{Sub, SubAssign}; +use std::sync::Arc; use cadmium::extrusion::fuse; +use cadmium::oplog::EvolutionLog; +use cadmium::oplog::Operation; +use cadmium::project::Plane; +use cadmium::{oplog, Realization}; use truck_meshalgo::filters::OptimizingFilter; use truck_meshalgo::tessellation::{MeshableShape, MeshedShape}; use truck_modeling::builder::{translated, tsweep, vertex}; -use truck_modeling::{Plane, Point3, Surface, Vector3}; +use truck_modeling::{Point3, Surface, Vector3}; use truck_polymesh::{obj, InnerSpace, Invertible, ParametricSurface, Tolerance}; use truck_shapeops::{and, or, ShapeOpsCurve, ShapeOpsSurface}; use truck_topology::{Shell, Solid}; +// use oplog::Operation; + fn main() { + let mut el = EvolutionLog::new(); + el.append(Operation::NewPlane { + name: "Front".to_string(), + plane: Plane::front(), + }); + let new_sketch_hash = el.append(Operation::NewSketch { + name: "Sketch1".to_string(), + plane_name: "Front".to_string(), + }); + el.append(Operation::NewRectangle { + sketch_name: "Sketch1".to_string(), + x: 0.0, + y: 0.0, + width: 100.0, + height: 100.0, + }); + let extrude_sha = el.append(Operation::NewExtrusion { + name: "Extrude1".to_string(), + sketch_name: "Sketch1".to_string(), + click_x: 50.0, + click_y: 50.0, + depth: 100.0, + }); + + // Actually, let's try something different + el.checkout(new_sketch_hash); + el.append(Operation::NewCircle { + sketch_name: "Sketch1".to_string(), + x: 50.0, + y: 50.0, + radius: 50.0, + }); + el.cherry_pick(extrude_sha); + + el.pretty_print(); + +} + +fn main_old() { let point_a = vertex(Point3::new(0.0, 0.0, 0.0)); let line_a = tsweep(&point_a, Vector3::new(1.0, 0.0, 0.0)); let square_a = tsweep(&line_a, Vector3::new(0.0, 1.0, 0.0)); diff --git a/packages/cadmium/src/oplog/mod.rs b/packages/cadmium/src/oplog/mod.rs new file mode 100644 index 00000000..89a5d480 --- /dev/null +++ b/packages/cadmium/src/oplog/mod.rs @@ -0,0 +1,258 @@ +use highhash::Murmur3Hasher; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use std::process::id; + +use crate::project::Plane; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpLog { + commits: Vec, +} + +impl OpLog { + pub fn new() -> Self { + Self { commits: vec![] } + } + + pub fn init(&mut self) { + let creation_commit = Commit::init(); + self.commits.push(creation_commit); + } + + pub fn append(&mut self, parent: &Sha, operation: Operation) -> Commit { + let op_hash = operation.hash(); + let parent = parent.clone(); + let new_commit = Commit { + id: id_from_op_and_parent(&operation, &parent), + operation, + content_hash: op_hash, + parent, + }; + self.commits.push(new_commit.clone()); + new_commit + } + + pub fn last(&self) -> Option { + match self.commits.last() { + Some(commit) => Some(commit.clone()), + None => None, + } + } + + pub fn get_length(&self) -> usize { + self.commits.len() + } +} + +fn id_from_op_and_parent(operation: &Operation, parent: &Sha) -> Sha { + let h = operation.hash(); + let mut hasher = Murmur3Hasher::default(); + hasher.write(format!("{h}-{parent}").as_bytes()); + format!("{:x}", hasher.finish()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EvolutionLog { + pub cursor: Sha, + pub oplog: OpLog, // TODO: work out the lifetimes here so that we can have multiple evolutionLogs at once? +} + +impl EvolutionLog { + pub fn new() -> Self { + let mut ol = OpLog::new(); + ol.init(); + Self { + cursor: ol.last().unwrap().id.clone(), + oplog: ol, + } + } + + pub fn append(&mut self, operation: Operation) -> Sha { + self.cursor = self.oplog.append(&self.cursor, operation).id; + self.cursor.clone() + } + + pub fn pretty_print(&self) { + for commit in &self.oplog.commits { + println!("{}", commit.pretty_print()); + } + } + + pub fn checkout(&mut self, sha: Sha) -> Result<(), String> { + // check that the sha exists in the oplog before doing this + for commit in &self.oplog.commits { + if commit.id == sha { + self.cursor = sha; + return Ok(()); + } + } + Err(format!("SHA {} not found in oplog", sha)) + } + + pub fn cherry_pick(&mut self, sha: Sha) -> Result<(), String> { + // check that the sha exists in the oplog before doing this + for commit in &self.oplog.commits { + if commit.id == sha { + self.append(commit.operation.clone()); + return Ok(()); + } + } + Err(format!("SHA {} not found in oplog", sha)) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Commit { + pub operation: Operation, + pub content_hash: Sha, + pub parent: Sha, + pub id: Sha, // this is the SHA of "operation + parent" +} + +impl Commit { + pub fn init() -> Self { + let init_op = Operation::Create { + nonce: "Hello World".to_string(), // TODO: replace with actual seeded random string + }; + let parent_sha = "".to_owned(); + Self { + id: id_from_op_and_parent(&init_op, &parent_sha), + content_hash: init_op.hash(), + operation: init_op, + parent: parent_sha, + } + } + + pub fn pretty_print(&self) -> String { + format!("{}: {}", self.id, self.operation.pretty_print()) + } +} + +pub type Sha = String; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Operation { + Create { + nonce: String, + }, + Describe { + description: String, + commit: Sha, + }, + NewPlane { + name: String, + plane: Plane, + }, + NewSketch { + name: String, + plane_name: String, + }, + NewRectangle { + sketch_name: String, + x: f64, + y: f64, + width: f64, + height: f64, + }, + NewExtrusion { + name: String, + sketch_name: String, + click_x: f64, + click_y: f64, + depth: f64, + }, + NewCircle { + sketch_name: String, + x: f64, + y: f64, + radius: f64, + }, +} + +impl Operation { + pub fn hash(&self) -> Sha { + let mut hasher = Murmur3Hasher::default(); + hasher.write("cadmium".as_bytes()); // mm, salt + match self { + Operation::Create { nonce } => hasher.write(format!("{nonce}").as_bytes()), + Operation::Describe { + description, + commit, + } => hasher.write(format!("{description}-{commit}").as_bytes()), + Operation::NewPlane { name, plane } => { + hasher.write(format!("{name}-{plane:?}").as_bytes()) + } + Operation::NewSketch { name, plane_name } => { + hasher.write(format!("{name}-{plane_name:?}").as_bytes()) + } + Operation::NewRectangle { + sketch_name, + x, + y, + width, + height, + } => hasher.write(format!("{sketch_name}-{x}-{y}-{width}-{height}").as_bytes()), + Operation::NewExtrusion { + name, + sketch_name, + click_x, + click_y, + depth, + } => { + hasher.write(format!("{name}-{sketch_name}-{click_x}-{click_y}-{depth}").as_bytes()) + } + Operation::NewCircle { + sketch_name, + x, + y, + radius, + } => hasher.write(format!("{sketch_name}-{x}-{y}-{radius}").as_bytes()), + } + + format!("{:x}", hasher.finish()) + } + + pub fn pretty_print(&self) -> String { + match self { + Operation::Create { nonce } => format!("Create: {}", nonce), + Operation::Describe { + description, + commit, + } => format!("Describe: {} '{}'", commit, description), + Operation::NewPlane { name, plane } => format!("NewPlane: '{}'", name), + Operation::NewSketch { name, plane_name } => { + format!("NewSketch: '{}' on plane '{}'", name, plane_name) + } + Operation::NewRectangle { + sketch_name, + x, + y, + width, + height, + } => format!( + "NewRectangle: {} {} {} {} on '{}'", + x, y, width, height, sketch_name + ), + Operation::NewExtrusion { + name, + sketch_name, + click_x, + click_y, + depth, + } => format!( + "NewExtrusion: '{}' on '{}' ({},{}) depth: {}", + name, sketch_name, click_x, click_y, depth + ), + Operation::NewCircle { + sketch_name, + x, + y, + radius, + } => format!( + "NewCircle: ({},{}) radius: {} on '{}'", + x, y, radius, sketch_name + ), + } + } +}