Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic rust documentation and some cleanup #113

Merged
merged 10 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,37 @@

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

- package-ecosystem: "npm"
directory: "/"
schedule:
interval: monthly
- package-ecosystem: "npm"
directory: "/applications/web"
schedule:
interval: monthly
- package-ecosystem: "npm"
directory: "/packages/shared"
schedule:
interval: monthly

- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: monthly
- package-ecosystem: "cargo"
directory: "/packages/cadmium"
schedule:
interval: monthly
- package-ecosystem: "cargo"
directory: "/packages/cadmium-macros"
schedule:
interval: monthly
- package-ecosystem: "cargo"
directory: "/applications/tauri"
schedule:
interval: monthly
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ jobs:
run: pnpm exec playwright install
- name: Run rust tests, vitest unit tests, and playwright e2e tests
run: pnpm turbo run test

- name: Generate rust docs
run: cargo doc --no-deps --package cadmium --release && mv target/doc applications/web/dist/docs
- uses: actions/upload-artifact@v4
with:
name: cadmium
Expand Down
11 changes: 11 additions & 0 deletions packages/cadmium-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "cadmium-api"
version = "0.1.0"
edition = "2021"

[dependencies]
cadmium = { path = "../cadmium" }

[build-dependencies]
convert_case = "0.6.0"
cadmium = { path = "../cadmium" }
49 changes: 49 additions & 0 deletions packages/cadmium-api/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::env;
use std::fs::File;
use std::io::Write;

use convert_case::{Case, Casing};

fn main() {
let mut output =
File::create(format!("{}/generated.rs", env::var("OUT_DIR").unwrap())).unwrap();

writeln!(
output,
"// This file is generated by build script, do not edit"
)
.unwrap();

let all_defs = cadmium::Project::gen_typescript_defs();

for (name, fields) in &all_defs {
let iface_fields = fields.join("; ");
let fn_name = name.to_case(Case::Camel);
let fn_params_full = fields.join(", ");
let fn_params = fn_params_full.trim_end_matches(", ");
let iface_params_full = fields
.iter()
.map(|f| f.split(":").next().unwrap().trim())
.collect::<Vec<_>>()
.join(", ");
let iface_params = iface_params_full.trim_end_matches(", ");

writeln!(
output,
r#"
pub fn {fn_name}({fn_params}) -> MessageResult {{
const message: Message = {{ type: "{name}", {iface_params} }};
return sendWasmMessage(message);
}}"#
)
.unwrap();
}

let message_variants = all_defs
.iter()
.map(|(name, _)| format!("{{ type: \"{name}\" }} & {name}"))
.collect::<Vec<_>>()
.join(" | ");

writeln!(output, "export type Message = {message_variants};").unwrap();
}
8 changes: 8 additions & 0 deletions packages/cadmium-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// #![allow(unused_imports)]
use cadmium::feature::extrusion::{Direction, Mode};
use cadmium::isketch::ISketch;
use cadmium::message::{Message, MessageResult};
use cadmium::step::StepHash;
use cadmium::IDType;

include!(concat!(env!("OUT_DIR"), "/generated.rs"));
5 changes: 5 additions & 0 deletions packages/cadmium/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use thiserror::Error;
#[derive(Error, Debug)]
pub enum CADmiumError {
// Message errors
#[error("The project ID {0} was not found")]
ProjectIDNotFound(usize),
#[error("The workbench ID {0} was not found")]
WorkbenchIDNotFound(u64),
#[error("The workbench name {0} was not found")]
Expand All @@ -28,4 +30,7 @@ pub enum CADmiumError {

#[error("This function is not implemented yet")]
NotImplemented,

#[error(transparent)]
Other(#[from] anyhow::Error),
}
5 changes: 0 additions & 5 deletions packages/cadmium/src/feature/solid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ use crate::archetypes::Vector3;

use super::prelude::*;

#[derive(Tsify, Debug, Serialize, Deserialize, Clone)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[repr(transparent)]
pub struct SolidArray(pub Vec<Solid>);

#[derive(Tsify, Debug, Serialize, Deserialize, Clone)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Solid {
Expand Down
192 changes: 178 additions & 14 deletions packages/cadmium/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,79 @@
//! CADmium is a library for building parametric CAD applications in Rust.
//!
//! Its main target is the web, as a WASM module, but it can also be used in native applications.
//!
//! The library is mostly an interface and interoperability layer for the [`ISOtope`](https://github.com/CADmium-Co/ISOtope)
//! 2D constraint solver and the [`Truck`](https://github.com/ricosjp/truck) CAD kernel.
//!
//! It is designed to be used in a functional way, where the state
//! of the application is stored in a single data structure in the global memory.
//!
//! ## Architecture
//!
//! The high-level architecture is as follows (tree-like, breadth-first):
//!
//! - A [`Project`] is the main data structure that holds every kind of
//! information about the project - mainly an array of [`Workbench`]es.
//! - A [`Workbench`] holds both the history and the internal state of the
//! result of the operations in the project.
//! The following arrays of structs are held:
//! - A [`Step`] is a single operation that takes place in a [`Workbench`].
//! Comprised of a [`Message`], its [`StepHash`] and its [`StepResult`],
//! an array of steps is also known as the history of the workbench.
//! - A [`Point3`] represents a point in 3D space. In the context of direct
//! [`Workbench`] descendant, it's a free-standing point, not part of a sketch
//! or solid.
//! - A [`Plane`] is a 2D plane that can be used to create sketches.
//! - An [`ISketch`] is a 2D sketch that can be used to create 3D models.
//! It holds an ISOtope `Sketch` and a list of [`Compound`]s (a way to
//! describe complex 2D shapes using a set of ISOtope primitives and constraints).
//! - A [`Feature`] is a 3D operation that mostly results in a 3D object -
//! either by creating or modifying it. For example, an [`Extrusion`] is a feature.
//!
//! ## Usage
//! The way to interact with CADmium is through messages. A message is a single,
//! pre-defined operation that can be applied to a project. For example, a message
//! could be `ProjectRename` or `FeatureExtrusionAdd`, both variants of the
//! [`Message`] enum.
//!
// TODO: Give a better example (e.g. a simple sketch and extrusion)
//! ```rust
//! use cadmium::{create_project, get_project, send_message};
//! use cadmium::message::Message;
//! use cadmium::project::ProjectRename;
//!
//! let project_id = create_project("My Project");
//! let mut project = get_project(project_id).unwrap();
//! let message = Message::ProjectRename(ProjectRename { new_name: "New Name".to_string() });
//! let result = send_message(project_id, &message);
//! assert!(result.success);
//!
//! let project = get_project(project_id).unwrap();
//! assert_eq!(project.name, "New Name");
//! ```
//!
//! ## WASM Usage
//!
//! CADmium is designed to be used in the browser as a WebAssembly module.
//! It can be compiled with `wasm-pack` and automatically produces a TypeScript
//! definition file that can be used in a web application.
// TODO: Add a javascript example
//!
//! [`Compound`]: crate::isketch::compound::Compound
//! [`Extrusion`]: crate::feature::extrusion::Extrusion
//! [`Feature`]: crate::feature::Feature
//! [`ISketch`]: crate::isketch::ISketch
//! [`Point3`]: crate::feature::point::Point3
//! [`Plane`]: crate::archetypes::Plane
//! [`Step`]: crate::step::Step
//! [`StepHash`]: crate::step::StepHash
//! [`StepResult`]: crate::step::StepResult
//! [`Workbench`]: crate::workbench::Workbench

use std::cell::RefCell;
use std::collections::BTreeMap;

use feature::solid::SolidArray;
use error::CADmiumError;
use message::{Message, MessageResult};
use step::StepHash;
use tsify_next::declare;
Expand All @@ -17,14 +89,118 @@ pub mod project;
pub mod step;
pub mod workbench;

/// The primary type used to describe an internal ID in CADmium
///
/// Could be an index to a vector, a key in a map, etc.
#[declare]
pub type IDType = u64;

thread_local! {
// TODO: This is a bad solution to the hash <-> crate-internal-ID mapping problem
/// This is a global map to keep track of hashes against local IDs
/// The hash is the unique identifier for a step
/// The ID could be any kind of ID, e.g. a isotope sketch primitive
///
/// <div class="warning">
///
/// Using this map in reverse (from local ID to hash) requires manual check logic
/// (that the resulting hash is a step of the correct type, points to the correct parent, etc.)
///
/// </div>
static ID_MAP: RefCell<BTreeMap<StepHash, IDType>> = RefCell::new(BTreeMap::new());

/// Global project list - this is the preferred way to store & access projects
static PROJECTS: RefCell<Vec<project::Project>> = RefCell::new(Vec::new());
}

/// Creates a new [`Project`](project::Project) and returns the index of the project in the global project list
///
/// # Examples
///
/// ```rust
/// use cadmium::{create_project, get_project};
///
/// let project_id = create_project("My Project");
/// let project = get_project(project_id).unwrap();
///
/// assert_eq!(project.name, "My Project");
/// ```
#[wasm_bindgen]
pub fn create_project(name: &str) -> usize {
let p = project::Project::new(name);
PROJECTS.with(|projects_ref| {
let mut projects = projects_ref.borrow_mut();
projects.push(p);
projects.len() - 1
})
}

/// Returns a concrete [`Project`](project::Project) from the global project list.
///
/// A new project can be created with [`create_project`] function.
#[wasm_bindgen]
pub fn get_project(project_index: usize) -> Result<project::Project, String> {
PROJECTS.with(|projects_ref| {
let projects = projects_ref.borrow();
Ok(projects
.get(project_index)
.ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?
.clone())
})
}

/// Sends a message to a [`Project`](project::Project) and returns the result
///
/// [`Message`]s are the primary way to interact with CADmium.
/// They describe any kind of action that can be taken on a project.
///
/// # Examples
///
/// ```rust
/// use cadmium::{create_project, get_project, send_message};
/// use cadmium::message::Message;
/// use cadmium::project::ProjectRename;
///
/// let project_id = create_project("My Project");
/// let message = Message::ProjectRename(ProjectRename { new_name: "New Name".to_string() });
/// let result = send_message(project_id, &message);
/// assert!(result.success);
///
/// let project = get_project(project_id).unwrap();
/// assert_eq!(project.name, "New Name");
/// ```
#[wasm_bindgen]
pub fn send_message(project_index: usize, message: &Message) -> MessageResult {
PROJECTS.with(|projects_ref| {
let mut projects = projects_ref.borrow_mut();
let Some(mut p) = projects.get_mut(project_index as usize) else {
return CADmiumError::ProjectIDNotFound(project_index).into();
};

message.handle(&mut p).into()
})
}

/// Returns a concrete [`Workbench`](workbench::Workbench) from a [`Project`](project::Project).
#[wasm_bindgen]
pub fn get_workbench(
project_index: usize,
workbench_index: usize,
) -> Result<workbench::Workbench, String> {
PROJECTS.with(|projects_ref| {
let projects = projects_ref.borrow();
let p = projects
.get(project_index)
.ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?;
let wb = p
.workbenches
.get(workbench_index)
.ok_or(CADmiumError::WorkbenchIDNotFound(workbench_index as u64).to_string())?
.borrow();
Ok(wb.clone())
})
}

#[derive(Debug, Clone)]
#[wasm_bindgen]
pub struct Project {
native: project::Project,
Expand Down Expand Up @@ -88,16 +264,4 @@ impl Project {
pub fn send_message(&mut self, message: &Message) -> MessageResult {
message.handle(&mut self.native).into()
}

#[wasm_bindgen]
pub fn get_workbench_solids(&self, workbench_index: u32) -> SolidArray {
SolidArray(
self.native
.workbenches
.get(workbench_index as usize)
.unwrap()
.borrow()
.get_solids(),
)
}
}
Loading
Loading