Path: use a more compact representation

This turns Path from

    pub struct Path {
        path_commands: Box<[PathCommand]>,
    }

into this:

    pub struct Path {
        commands: Box<[PackedCommand]>,
        coords: Box<[f64]>,
    }

Each PackedCommand is a u8-sized enum, which encodes the type of
command plus the flags inside EllipticalArc.

The coords array is a linear list of the coordinates for all commands.

PathBuilder.into_path() performs the encoding from &[PathCommand] into
Path, and PathIter.next() does the decoding.

This reduces memory consumption in the huge map file in issue #574 by
quite a lot.

Before, (352,523,448 + 50,990,328) = 403,513,776 bytes of path data:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 33 24,139,598,653    1,416,831,176    1,329,943,212    86,887,964            0
->24.88% (352,523,448B) 0x4A2727E: alloc (alloc.rs:84)
| ->24.88% (352,523,448B) 0x4A2727E: alloc (alloc.rs:172)
|   ->24.88% (352,523,448B) 0x4A2727E: allocate_in<rsvg_internals::path_builder::PathCommand,alloc::alloc::Global> (raw_vec.rs:98)
|     ->24.88% (352,523,448B) 0x4A2727E: with_capacity<rsvg_internals::path_builder::PathCommand> (raw_vec.rs:167)
|       ->24.88% (352,523,448B) 0x4A2727E: with_capacity<rsvg_internals::path_builder::PathCommand> (vec.rs:358)
|         ->24.88% (352,523,448B) 0x4A2727E: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T,I>>::from_iter (vec.rs:1992)
|           ->24.88% (352,523,448B) 0x49D212C: from_iter<rsvg_internals::path_builder::PathCommand,smallvec::IntoIter<[rsvg_internals::path_builder::PathCommand; 32]>> (vec.rs:1901)
|             ->24.88% (352,523,448B) 0x49D212C: collect<smallvec::IntoIter<[rsvg_internals::path_builder::PathCommand; 32]>,alloc::vec::Vec<rsvg_internals::path_builder::PathCommand>> (iterator.rs:1493)
|               ->24.88% (352,523,448B) 0x49D212C: into_vec<[rsvg_internals::path_builder::PathCommand; 32]> (lib.rs:893)
|                 ->24.88% (352,523,448B) 0x49D212C: smallvec::SmallVec<A>::into_boxed_slice (lib.rs:902)
|                   ->24.88% (352,523,016B) 0x4A0394C: into_path (path_builder.rs:320)
|
->03.60% (50,990,328B) 0x4A242F0: realloc (alloc.rs:128)
| ->03.60% (50,990,328B) 0x4A242F0: realloc (alloc.rs:187)
|   ->03.60% (50,990,328B) 0x4A242F0: shrink_to_fit<rsvg_internals::path_builder::PathCommand,alloc::alloc::Global> (raw_vec.rs:633)
|     ->03.60% (50,990,328B) 0x4A242F0: shrink_to_fit<rsvg_internals::path_builder::PathCommand> (vec.rs:623)
|       ->03.60% (50,990,328B) 0x4A242F0: alloc::vec::Vec<T>::into_boxed_slice (vec.rs:679)
|         ->03.60% (50,990,328B) 0x49D2136: smallvec::SmallVec<A>::into_boxed_slice (lib.rs:902)
|           ->03.60% (50,990,328B) 0x4A0394C: into_path (path_builder.rs:320)

After, 81,525,328 bytes of path data:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 28 26,611,886,993    1,093,747,888    1,023,147,907    70,599,981            0
->07.45% (81,525,328B) 0x4A34C6F: alloc (alloc.rs:84)
| ->07.45% (81,525,328B) 0x4A34C6F: alloc (alloc.rs:172)
|   ->07.45% (81,525,328B) 0x4A34C6F: allocate_in<f64,alloc::alloc::Global> (raw_vec.rs:98)
|     ->07.45% (81,525,328B) 0x4A34C6F: with_capacity<f64> (raw_vec.rs:167)
|       ->07.45% (81,525,328B) 0x4A34C6F: with_capacity<f64> (vec.rs:358)
|         ->07.45% (81,525,328B) 0x4A34C6F: rsvg_internals::path_builder::PathBuilder::into_path (path_builder.rs:486)
parent cb4cde71
......@@ -33,6 +33,24 @@ impl CubicBezierCurve {
let Self { pt1, pt2, to } = self;
cr.curve_to(pt1.0, pt1.1, pt2.0, pt2.1, to.0, to.1);
}
fn from_coords(coords: &[f64]) -> CubicBezierCurve {
let pt1 = (coords[0], coords[1]);
let pt2 = (coords[2], coords[3]);
let to = (coords[4], coords[5]);
CubicBezierCurve { pt1, pt2, to }
}
fn to_packed_and_coords(&self, coords: &mut [f64]) -> PackedCommand {
coords[0] = self.pt1.0;
coords[1] = self.pt1.1;
coords[2] = self.pt2.0;
coords[3] = self.pt2.1;
coords[4] = self.to.0;
coords[5] = self.to.1;
PackedCommand::CurveTo
}
}
/// When attempting to compute the center parameterization of the arc,
......@@ -226,6 +244,39 @@ impl EllipticalArc {
ArcParameterization::Omit => {}
}
}
fn from_coords(large_arc: LargeArc, sweep: Sweep, coords: &[f64]) -> EllipticalArc {
let r = (coords[0], coords[1]);
let x_axis_rotation = coords[2];
let from = (coords[3], coords[4]);
let to = (coords[5], coords[6]);
EllipticalArc {
r,
x_axis_rotation,
large_arc,
sweep,
from,
to,
}
}
fn to_packed_and_coords(&self, coords: &mut [f64]) -> PackedCommand {
coords[0] = self.r.0;
coords[1] = self.r.1;
coords[2] = self.x_axis_rotation;
coords[3] = self.from.0;
coords[4] = self.from.1;
coords[5] = self.to.0;
coords[6] = self.to.1;
match (self.large_arc, self.sweep) {
(LargeArc(false), Sweep::Negative) => PackedCommand::ArcSmallNegative,
(LargeArc(false), Sweep::Positive) => PackedCommand::ArcSmallPositive,
(LargeArc(true), Sweep::Negative) => PackedCommand::ArcLargeNegative,
(LargeArc(true), Sweep::Positive) => PackedCommand::ArcLargePositive,
}
}
}
/// Turns an arc segment into a cubic bezier curve.
......@@ -291,9 +342,86 @@ impl PathCommand {
PathCommand::ClosePath => cr.close_path(),
}
}
// Returns the number of coordinate values that this command will generate in a `Path`.
fn num_coordinates(&self) -> usize {
match *self {
PathCommand::MoveTo(..) => 2,
PathCommand::LineTo(..) => 2,
PathCommand::CurveTo(_) => 6,
PathCommand::Arc(_) => 7,
PathCommand::ClosePath => 0,
}
}
fn to_packed(&self, coords: &mut [f64]) -> PackedCommand {
match *self {
PathCommand::MoveTo(x, y) => {
coords[0] = x;
coords[1] = y;
PackedCommand::MoveTo
}
PathCommand::LineTo(x, y) => {
coords[0] = x;
coords[1] = y;
PackedCommand::LineTo
}
PathCommand::CurveTo(ref c) => c.to_packed_and_coords(coords),
PathCommand::Arc(ref a) => a.to_packed_and_coords(coords),
PathCommand::ClosePath => PackedCommand::ClosePath,
}
}
fn from_packed(packed: &PackedCommand, coords: &[f64]) -> PathCommand {
match *packed {
PackedCommand::MoveTo => {
let x = coords[0];
let y = coords[1];
PathCommand::MoveTo(x, y)
}
PackedCommand::LineTo => {
let x = coords[0];
let y = coords[1];
PathCommand::LineTo(x, y)
}
PackedCommand::CurveTo => PathCommand::CurveTo(CubicBezierCurve::from_coords(coords)),
PackedCommand::ClosePath => PathCommand::ClosePath,
PackedCommand::ArcSmallNegative => PathCommand::Arc(EllipticalArc::from_coords(
LargeArc(false),
Sweep::Negative,
coords,
)),
PackedCommand::ArcSmallPositive => PathCommand::Arc(EllipticalArc::from_coords(
LargeArc(false),
Sweep::Positive,
coords,
)),
PackedCommand::ArcLargeNegative => PathCommand::Arc(EllipticalArc::from_coords(
LargeArc(true),
Sweep::Negative,
coords,
)),
PackedCommand::ArcLargePositive => PathCommand::Arc(EllipticalArc::from_coords(
LargeArc(true),
Sweep::Positive,
coords,
)),
}
}
}
/// Constructs a path out of commands
/// Constructs a path out of commands.
///
/// When you are finished constructing a path builder, turn it into
/// a `Path` with `into_path`.
......@@ -302,16 +430,42 @@ pub struct PathBuilder {
path_commands: SmallVec<[PathCommand; 32]>,
}
/// An immutable path
/// An immutable path with a compact representation.
///
/// This is constructed from a `PathBuilder` once it is finished. You
/// can get an iterator for the path's commands with the `iter`
/// function.
///
/// This is constructed from a `PathBuilder` once it is finished.
/// Most `PathCommand` variants only have a few coordinates, but `PathCommand::Arc`
/// has two extra booleans. We separate the commands from their coordinates so
/// we can have two dense arrays: one with a compact representation of commands,
/// and another with a linear list of the coordinates for each command.
///
/// Each `PathCommand` knows how many coordinates it ought to produce, with
/// its `num_coordinates` method.
pub struct Path {
path_commands: Box<[PathCommand]>,
commands: Box<[PackedCommand]>,
coords: Box<[f64]>,
}
/// Iterator over a path's commands
/// Iterator over a `Path`'s commands, from `Path::iter`.
pub struct PathIter<'a> {
commands: slice::Iter<'a, PathCommand>,
commands: slice::Iter<'a, PackedCommand>,
coords: &'a [f64],
}
/// Packed version of a `PathCommand`, used in `Path`.
#[repr(u8)]
#[derive(Debug)]
enum PackedCommand {
MoveTo,
LineTo,
CurveTo,
ArcSmallNegative,
ArcSmallPositive,
ArcLargeNegative,
ArcLargePositive,
ClosePath,
}
impl PathBuilder {
......@@ -322,8 +476,26 @@ impl PathBuilder {
}
pub fn into_path(self) -> Path {
let num_commands = self.path_commands.len();
let num_coords = self
.path_commands
.iter()
.fold(0, |acc, cmd| acc + cmd.num_coordinates());
let mut packed_commands = Vec::with_capacity(num_commands);
let mut coords = vec![0.0; num_coords];
let mut coords_slice = coords.as_mut_slice();
for c in self.path_commands {
let n = c.num_coordinates();
packed_commands.push(c.to_packed(coords_slice.get_mut(0..n).unwrap()));
coords_slice = &mut coords_slice[n..];
}
Path {
path_commands: self.path_commands.into_boxed_slice(),
commands: packed_commands.into_boxed_slice(),
coords: coords.into_boxed_slice(),
}
}
......@@ -375,12 +547,13 @@ impl PathBuilder {
impl Path {
pub fn iter(&self) -> PathIter {
PathIter {
commands: self.path_commands.iter(),
commands: self.commands.iter(),
coords: &self.coords[..],
}
}
pub fn is_empty(&self) -> bool {
self.iter().nth(0).is_none()
self.commands.is_empty()
}
pub fn to_cairo(&self, cr: &cairo::Context) -> Result<(), cairo::Status> {
......@@ -412,7 +585,14 @@ impl<'a> Iterator for PathIter<'a> {
type Item = PathCommand;
fn next(&mut self) -> Option<Self::Item> {
self.commands.next().map(Clone::clone)
if let Some(cmd) = self.commands.next() {
let cmd = PathCommand::from_packed(cmd, self.coords);
let num_coords = cmd.num_coordinates();
self.coords = &self.coords[num_coords..];
Some(cmd)
} else {
None
}
}
}
......@@ -428,38 +608,23 @@ mod tests {
assert_eq!(path.iter().count(), 0);
}
#[test]
fn arc() {
let mut builder = PathBuilder::new();
builder.arc(42.0, 43.0, 44.0, 45.0, 46.0, LargeArc(true), Sweep::Positive, 47.0, 48.0);
let path = builder.into_path();
assert!(path.iter().eq(vec![PathCommand::Arc(
EllipticalArc {
from: (42.0, 43.0),
r: (44.0, 45.0),
to: (47.0, 48.0),
x_axis_rotation: 46.0,
large_arc: LargeArc(true),
sweep: Sweep::Positive,
}
)]));
}
#[test]
fn close_path() {
let mut builder = PathBuilder::new();
builder.close_path();
let path = builder.into_path();
assert!(path.iter().eq(vec![PathCommand::ClosePath]));
}
#[test]
fn all_commands() {
let mut builder = PathBuilder::new();
builder.move_to(42.0, 43.0);
builder.line_to(42.0, 43.0);
builder.curve_to(42.0, 43.0, 44.0, 45.0, 46.0, 47.0);
builder.arc(42.0, 43.0, 44.0, 45.0, 46.0, LargeArc(true), Sweep::Positive, 47.0, 48.0);
builder.arc(
42.0,
43.0,
44.0,
45.0,
46.0,
LargeArc(true),
Sweep::Positive,
47.0,
48.0,
);
builder.close_path();
let path = builder.into_path();
assert!(path.iter().eq(vec![
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment