From 178448be7bd07d53c438c5173fb56675b66bcd71 Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Tue, 27 Apr 2021 10:44:28 -0400 Subject: [PATCH] reduce string allocations to minimum possible w/ g-code v0.1.1 --- Cargo.lock | 9 ++++----- Cargo.toml | 2 +- src/converter.rs | 20 +++++++++++++------- src/machine.rs | 26 +++++++++++--------------- src/main.rs | 29 +++++++++++++++++------------ src/postprocess.rs | 20 ++++++++++---------- src/turtle.rs | 25 +++++++++++++------------ 7 files changed, 69 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abe12ee..53720d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,13 +133,12 @@ checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" [[package]] name = "g-code" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60958e3fe54d293ae76fb1e0c433161595a8dbe9c1c74663b24f76b9f98b962c" +checksum = "a4b0866fca554553ea6d676c69ce31f6260ceb5680d0e8bb3bf6711f89799a38" dependencies = [ "codespan", "codespan-reporting", - "lazy_static", "num", "num-rational", "paste", @@ -178,9 +177,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "log" diff --git a/Cargo.toml b/Cargo.toml index 9dae530..ec604e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine." [dependencies] -g-code = "0.1.0" +g-code = "0.1.1" lyon_geom = "0" euclid = "0.22" structopt = "0.3" diff --git a/src/converter.rs b/src/converter.rs index 3c5cd3d..a33d23b 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::str::FromStr; use g_code::{command, emit::Token}; @@ -10,7 +11,6 @@ use svgtypes::{ LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox, }; -use crate::machine::*; use crate::turtle::*; /// High-level output options @@ -34,9 +34,11 @@ impl Default for ProgramOptions { } } -pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Vec { - let mut turtle = Turtle::new(mach); - +pub fn svg2program<'input>( + doc: &Document, + options: ProgramOptions, + turtle: &'input mut Turtle<'input>, +) -> Vec> { let mut program = command!(UnitsMillimeters {}) .into_token_vec() .drain(..) @@ -123,9 +125,9 @@ pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Ve comment += &node_name(&node); program.push(Token::Comment { is_inline: false, - inner: comment, + inner: Cow::Owned(comment), }); - program.extend(apply_path(&mut turtle, &options, d)); + program.extend(apply_path(turtle, &options, d)); } else { warn!("There is a path node containing no actual path: {:?}", node); } @@ -188,7 +190,11 @@ fn width_and_height_into_transform( } } -fn apply_path(turtle: &mut Turtle, options: &ProgramOptions, path: &str) -> Vec { +fn apply_path<'a, 'input>( + turtle: &'a mut Turtle<'input>, + options: &ProgramOptions, + path: &str, +) -> Vec> { use PathSegment::*; PathParser::from(path) .map(|segment| segment.expect("could not parse path segment")) diff --git a/src/machine.rs b/src/machine.rs index e202f09..8724ab0 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1,8 +1,4 @@ -use g_code::{ - command, - emit::Token, - parse::{ast::Snippet, token::Field}, -}; +use g_code::{command, emit::Token, parse::ast::Snippet}; /// Whether the tool is active (i.e. cutting) #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -52,13 +48,13 @@ pub struct Machine<'input> { impl<'input> Machine<'input> { /// Output gcode to turn the tool on. - pub fn tool_on<'a>(&'a mut self) -> Vec { + pub fn tool_on(&mut self) -> Vec> { if self.tool_state == Some(Tool::Off) || self.tool_state == None { self.tool_state = Some(Tool::On); self.tool_on_action .iter() .flat_map(|s| s.iter_fields()) - .map(|f: &Field| Token::from(f)) + .map(Token::from) .collect() } else { vec![] @@ -66,36 +62,36 @@ impl<'input> Machine<'input> { } /// Output gcode to turn the tool off. - pub fn tool_off<'a>(&'a mut self) -> Vec { + pub fn tool_off(&mut self) -> Vec> { if self.tool_state == Some(Tool::On) || self.tool_state == None { self.tool_state = Some(Tool::Off); self.tool_off_action .iter() .flat_map(|s| s.iter_fields()) - .map(|f: &Field| Token::from(f)) + .map(Token::from) .collect() } else { vec![] } } - pub fn program_begin<'a>(&'a self) -> Vec { + pub fn program_begin(&self) -> Vec> { self.program_begin_sequence .iter() .flat_map(|s| s.iter_fields()) - .map(|f: &Field| Token::from(f)) + .map(Token::from) .collect() } - pub fn program_end<'a>(&'a self) -> Vec { + pub fn program_end(&self) -> Vec> { self.program_end_sequence .iter() .flat_map(|s| s.iter_fields()) - .map(|f: &Field| Token::from(f)) + .map(Token::from) .collect() } /// Output absolute distance field if mode was relative or unknown. - pub fn absolute(&mut self) -> Vec { + pub fn absolute(&mut self) -> Vec> { if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None { self.distance_mode = Some(Distance::Absolute); command!(AbsoluteDistanceMode {}).into_token_vec() @@ -105,7 +101,7 @@ impl<'input> Machine<'input> { } /// Output relative distance field if mode was absolute or unknown. - pub fn relative(&mut self) -> Vec { + pub fn relative(&mut self) -> Vec> { if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None { self.distance_mode = Some(Distance::Relative); command!(RelativeDistanceMode {}).into_token_vec() diff --git a/src/main.rs b/src/main.rs index b3dec0b..387396d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::{self, Read}; use std::path::PathBuf; -use g_code::parse::{ast::Snippet, ParseError, snippet_parser}; +use g_code::parse::{ast::Snippet, snippet_parser, ParseError}; use structopt::StructOpt; /// Converts an SVG to GCode in an internal representation @@ -22,6 +22,7 @@ mod turtle; use converter::ProgramOptions; use machine::Machine; +use turtle::Turtle; #[derive(Debug, StructOpt)] #[structopt(name = "svg2gcode", author, about)] @@ -81,10 +82,11 @@ fn main() -> io::Result<()> { } }; - let mut options = ProgramOptions::default(); - options.tolerance = opt.tolerance; - options.feedrate = opt.feedrate; - options.dpi = opt.dpi; + let options = ProgramOptions { + tolerance: opt.tolerance, + feedrate: opt.feedrate, + dpi: opt.dpi, + }; let snippets = [ opt.tool_on_sequence.as_ref().map(parse_snippet).transpose(), @@ -139,7 +141,8 @@ fn main() -> io::Result<()> { let document = roxmltree::Document::parse(&input).expect("Invalid or unsupported SVG file"); - let mut program = converter::svg2program(&document, options, machine); + let mut turtle = Turtle::new(machine); + let mut program = converter::svg2program(&document, options, &mut turtle); let origin = opt .origin @@ -155,12 +158,12 @@ fn main() -> io::Result<()> { } } -fn parse_snippet<'input>(gcode: &'input String) -> Result, ParseError> { +fn parse_snippet(gcode: &'_ String) -> Result, ParseError> { snippet_parser(gcode) } fn tokens_into_gcode( - program: Vec, + program: Vec>, mut w: W, ) -> io::Result<()> { use g_code::emit::Token::*; @@ -169,8 +172,8 @@ fn tokens_into_gcode( match token { Field(f) => { if !preceded_by_newline { - if matches!(f.letters.as_str(), "G" | "M") { - writeln!(w, "")?; + if matches!(f.letters.as_ref(), "G" | "M") { + writeln!(w)?; } else { write!(w, " ")?; } @@ -197,7 +200,7 @@ fn tokens_into_gcode( } // Ensure presence of trailing newline if !preceded_by_newline { - writeln!(w, "")?; + writeln!(w)?; } Ok(()) } @@ -205,6 +208,7 @@ fn tokens_into_gcode( #[cfg(test)] mod test { use super::*; + use crate::turtle::Turtle; use pretty_assertions::assert_eq; fn get_actual(input: &str) -> String { @@ -219,7 +223,8 @@ mod test { }; let document = roxmltree::Document::parse(input).unwrap(); - let mut program = converter::svg2program(&document, options, machine); + let mut turtle = Turtle::new(machine); + let mut program = converter::svg2program(&document, options, &mut turtle); postprocess::set_origin(&mut program, lyon_geom::point(0., 0.)); let mut actual = vec![]; diff --git a/src/postprocess.rs b/src/postprocess.rs index cdd983a..a3e509b 100644 --- a/src/postprocess.rs +++ b/src/postprocess.rs @@ -7,15 +7,15 @@ use lyon_geom::{point, vector, Point}; type F64Point = Point; /// Moves all the commands so that they are beyond a specified position -pub fn set_origin(tokens: &mut [Token], origin: F64Point) { +pub fn set_origin(tokens: &mut [Token<'_>], origin: F64Point) { let offset = -get_bounding_box(tokens.iter()).min.to_vector() + origin.to_vector(); let mut is_relative = false; let mut current_position = point(0f64, 0f64); - let x = "X".to_string(); - let y = "Y".to_string(); - let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone()); - let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone()); + let x = "X"; + let y = "Y"; + let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD); + let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD); for token in tokens { match token { abs if *abs == abs_tok => is_relative = false, @@ -45,14 +45,14 @@ pub fn set_origin(tokens: &mut [Token], origin: F64Point) { } } -fn get_bounding_box<'a, I: Iterator>(tokens: I) -> Box2D { +fn get_bounding_box<'a, I: Iterator>>(tokens: I) -> Box2D { let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64)); let mut is_relative = false; let mut current_position = point(0f64, 0f64); - let x = "X".to_string(); - let y = "Y".to_string(); - let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone()); - let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone()); + let x = "X"; + let y = "Y"; + let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD); + let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD); for token in tokens { match token { abs if *abs == abs_tok => is_relative = false, diff --git a/src/turtle.rs b/src/turtle.rs index ca349be..4925c3b 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -6,6 +6,7 @@ use g_code::{ use lyon_geom::euclid::{default::Transform2D, Angle}; use lyon_geom::{point, vector, Point}; use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; +use std::borrow::Cow; type F64Point = Point; @@ -36,7 +37,7 @@ impl<'input> Turtle<'input> { /// Move the turtle to the given absolute/relative coordinates in the current transform /// https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands - pub fn move_to(&mut self, abs: bool, x: X, y: Y) -> Vec + pub fn move_to(&mut self, abs: bool, x: X, y: Y) -> Vec> where X: Into>, Y: Into>, @@ -84,17 +85,17 @@ impl<'input> Turtle<'input> { .collect() } - fn linear_interpolation(x: f64, y: f64, z: Option, f: Option) -> Vec { + fn linear_interpolation(x: f64, y: f64, z: Option, f: Option) -> Vec> { let mut linear_interpolation = command! {LinearInterpolation { X: x, Y: y, }}; if let Some(z) = z { linear_interpolation.push(Field { - letters: "Z".to_string(), + letters: Cow::Borrowed("Z"), value: Value::Float(z), }); } if let Some(f) = f { linear_interpolation.push(Field { - letters: "F".into(), + letters: Cow::Borrowed("F"), value: Value::Float(f), }); } @@ -103,7 +104,7 @@ impl<'input> Turtle<'input> { /// Close an SVG path, cutting back to its initial position /// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand - pub fn close(&mut self, z: Z, f: F) -> Vec + pub fn close(&mut self, z: Z, f: F) -> Vec> where Z: Into>, F: Into>, @@ -134,7 +135,7 @@ impl<'input> Turtle<'input> { /// Draw a line from the current position in the current transform to the specified position /// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands - pub fn line(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec + pub fn line(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec> where X: Into>, Y: Into>, @@ -186,7 +187,7 @@ impl<'input> Turtle<'input> { tolerance: f64, z: Z, f: F, - ) -> Vec { + ) -> Vec> { let z = z.into(); let f = f.into(); let last_point = std::cell::Cell::new(self.current_position); @@ -227,7 +228,7 @@ impl<'input> Turtle<'input> { tolerance: f64, z: Z, f: F, - ) -> Vec + ) -> Vec> where Z: Into>, F: Into>, @@ -269,7 +270,7 @@ impl<'input> Turtle<'input> { tolerance: f64, z: Z, f: F, - ) -> Vec + ) -> Vec> where Z: Into>, F: Into>, @@ -307,7 +308,7 @@ impl<'input> Turtle<'input> { tolerance: f64, z: Z, f: F, - ) -> Vec + ) -> Vec> where Z: Into>, F: Into>, @@ -339,7 +340,7 @@ impl<'input> Turtle<'input> { tolerance: f64, z: Z, f: F, - ) -> Vec + ) -> Vec> where Z: Into>, F: Into>, @@ -376,7 +377,7 @@ impl<'input> Turtle<'input> { z: Z, f: F, tolerance: f64, - ) -> Vec + ) -> Vec> where Z: Into>, F: Into>,