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

Support user data with USINGZ #11

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 1 addition & 2 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ categories = ["algorithms"]
default = ["doc-images"]
doc-images = []
serde = ["dep:serde", "clipper2c-sys/serde"]
usingz = ["clipper2c-sys/usingz"]

[dependencies]
libc = "0.2"
Expand All @@ -31,3 +32,6 @@ serde_json = "1"
# docs.rs uses a nightly compiler, so by instructing it to use our `doc-images` feature we
# ensure that it will render any images that we may have in inner attribute documentation.
features = ["doc-images"]

[patch.crates-io]
clipper2c-sys = { rev = "210878394637f94bcd3a476bdf24ddf4ba8a39fd", git = "https://github.com/tirithen/clipper2c-sys" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: fix this before merging

144 changes: 142 additions & 2 deletions src/clipper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use clipper2c_sys::{
clipper_clipper64, clipper_clipper64_add_clip, clipper_clipper64_add_open_subject,
clipper_clipper64_add_subject, clipper_clipper64_execute, clipper_clipper64_size,
clipper_delete_clipper64, clipper_delete_paths64, ClipperClipper64,
clipper_delete_clipper64, clipper_delete_paths64, ClipperClipper64, ClipperPoint64,
};

use crate::{malloc, Centi, ClipType, FillRule, Paths, PointScaler};
use crate::{malloc, Centi, ClipType, FillRule, Paths, Point, PointScaler};

/// The state of the Clipper struct.
pub trait ClipperState {}
Expand All @@ -33,6 +33,10 @@
keep_ptr_on_drop: bool,
_marker: PhantomData<P>,
_state: S,

/// We only hold on to this in order to avoid leaking memory when Clipper is dropped
#[cfg(feature = "usingz")]
raw_z_callback: Option<*mut libc::c_void>,
}

impl<P: PointScaler> Clipper<NoSubjects, P> {
Expand All @@ -48,6 +52,9 @@
keep_ptr_on_drop: false,
_marker: PhantomData,
_state: NoSubjects {},

#[cfg(feature = "usingz")]
raw_z_callback: None,
}
}
}
Expand All @@ -72,6 +79,9 @@
keep_ptr_on_drop: false,
_marker: PhantomData,
_state: WithSubjects {},

#[cfg(feature = "usingz")]
raw_z_callback: None,
};

drop(self);
Expand All @@ -98,12 +108,88 @@
keep_ptr_on_drop: false,
_marker: PhantomData,
_state: WithSubjects {},

#[cfg(feature = "usingz")]
raw_z_callback: None,
};

drop(self);

clipper.add_open_subject(subject)
}

#[cfg(feature = "usingz")]
/// Allows specifying a callback that will be called each time a new vertex
/// is created by Clipper, in order to set user data on such points. New
/// points are created at the intersections between two edges, and the
/// callback will be called with the four neighboring points from those two
/// edges. The last argument is the new point itself.
///
/// # Examples
///
/// ```rust
/// use clipper2::*;
///
/// let mut clipper = Clipper::<NoSubjects, Centi>::new();
/// clipper.set_z_callback(|_: Point<_>, _: Point<_>, _: Point<_>, _: Point<_>, p: &mut Point<_>| {
/// p.set_z(1);
/// });
/// ```
pub fn set_z_callback(
&mut self,
callback: impl Fn(Point<P>, Point<P>, Point<P>, Point<P>, &mut Point<P>),
) {
// The closure will be represented by a trait object behind a fat
// pointer. Since fat pointers are larger than thin pointers, they
// cannot be passed through a thin-pointer c_void type. We must
// therefore wrap the fat pointer in another box, leading to this double
// indirection.
let cb: Box<Box<dyn Fn(Point<P>, Point<P>, Point<P>, Point<P>, &mut Point<P>)>> =
Box::new(Box::new(callback));
let raw_cb = Box::into_raw(cb) as *mut _;

// It there is an old callback stored, drop it before replacing it
if let Some(old_raw_cb) = self.raw_z_callback {
drop(unsafe { Box::from_raw(old_raw_cb as *mut _) });
}
self.raw_z_callback = Some(raw_cb);

unsafe {
clipper2c_sys::clipper_clipper64_set_z_callback(
self.ptr,
raw_cb,
Some(handle_set_z_callback::<P>),
);
}
}
}

extern "C" fn handle_set_z_callback<P: PointScaler>(

Check failure on line 167 in src/clipper.rs

View workflow job for this annotation

GitHub Actions / cargo test (ubuntu)

function `handle_set_z_callback` is never used
user_data: *mut ::std::os::raw::c_void,
e1bot: *const ClipperPoint64,
e1top: *const ClipperPoint64,
e2bot: *const ClipperPoint64,
e2top: *const ClipperPoint64,
pt: *mut ClipperPoint64,
) {
// SAFETY: user_data was set in set_z_callback, and is valid for as long as
// the Clipper2 instance exists.
let callback: &mut &mut dyn Fn(Point<P>, Point<P>, Point<P>, Point<P>, &mut Point<P>) =
unsafe { std::mem::transmute(user_data) };

// SAFETY: Clipper2 should produce valid pointers
let mut new_point = unsafe { Point::<P>::from(*pt) };
let e1bot = unsafe { Point::<P>::from(*e1bot) };
let e1top = unsafe { Point::<P>::from(*e1top) };
let e2bot = unsafe { Point::<P>::from(*e2bot) };
let e2top = unsafe { Point::<P>::from(*e2top) };

callback(e1bot, e1top, e2bot, e2top, &mut new_point);

// SAFETY: pt is a valid pointer and new_point is not null
unsafe {
*pt = *new_point.as_clipperpoint64();
}
}

impl<P: PointScaler> Clipper<WithSubjects, P> {
Expand Down Expand Up @@ -171,6 +257,9 @@
keep_ptr_on_drop: false,
_marker: PhantomData,
_state: WithClips {},

#[cfg(feature = "usingz")]
raw_z_callback: None,
};

drop(self);
Expand Down Expand Up @@ -321,6 +410,13 @@
fn drop(&mut self) {
if !self.keep_ptr_on_drop {
unsafe { clipper_delete_clipper64(self.ptr) }

#[cfg(feature = "usingz")]
{
if let Some(raw_cb) = self.raw_z_callback {
drop(unsafe { Box::from_raw(raw_cb as *mut _) });
}
}
}
}
}
Expand All @@ -332,3 +428,47 @@
#[error("Failed boolean operation")]
FailedBooleanOperation,
}

#[cfg(test)]
mod test {
use std::{cell::Cell, rc::Rc};

Check failure on line 434 in src/clipper.rs

View workflow job for this annotation

GitHub Actions / cargo test (ubuntu)

unused imports: `cell::Cell` and `rc::Rc`

use super::*;

Check failure on line 436 in src/clipper.rs

View workflow job for this annotation

GitHub Actions / cargo test (ubuntu)

unused import: `super::*`

#[cfg(feature = "usingz")]
#[test]
fn test_set_z_callback() {
let mut clipper = Clipper::<NoSubjects, Centi>::new();
let success = Rc::new(Cell::new(false));
{
let success = success.clone();
clipper.set_z_callback(
move |_e1bot: Point<_>,
_e1top: Point<_>,
_e2bot: Point<_>,
_e2top: Point<_>,
p: &mut Point<_>| {
p.set_z(1);
success.set(true);
},
);
}
let e1: Paths = vec![(0.0, 0.0), (2.0, 2.0), (0.0, 2.0)].into();
let e2: Paths = vec![(1.0, 0.0), (2.0, 0.0), (1.0, 2.0)].into();
let paths = clipper
.add_subject(e1)
.add_clip(e2)
.union(FillRule::default())
.unwrap();

assert_eq!(success.get(), true);

let n_intersecting = paths
.iter()
.flatten()
.into_iter()
.filter(|v| v.z() == 1)
.count();
assert_eq!(n_intersecting, 3);
}
}
1 change: 1 addition & 0 deletions src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ impl<P: PointScaler> Path<P> {
.map(|point: Point<P>| ClipperPoint64 {
x: point.x_scaled(),
y: point.y_scaled(),
..Default::default()
})
.collect::<Vec<_>>()
.as_mut_ptr(),
Expand Down
51 changes: 50 additions & 1 deletion src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,15 @@ pub struct Point<P: PointScaler = Centi>(
);

impl<P: PointScaler> Point<P> {
#[cfg(not(feature = "usingz"))]
/// The zero point.
pub const ZERO: Self = Self(ClipperPoint64 { x: 0, y: 0 }, PhantomData);

#[cfg(feature = "usingz")]
/// The zero point.
pub const ZERO: Self = Self(ClipperPoint64 { x: 0, y: 0, z: 0 }, PhantomData);

#[cfg(not(feature = "usingz"))]
/// The minimum value for a point.
pub const MIN: Self = Self(
ClipperPoint64 {
Expand All @@ -109,6 +115,18 @@ impl<P: PointScaler> Point<P> {
PhantomData,
);

#[cfg(feature = "usingz")]
/// The minimum value for a point.
pub const MIN: Self = Self(
ClipperPoint64 {
x: i64::MIN,
y: i64::MIN,
z: 0,
},
PhantomData,
);

#[cfg(not(feature = "usingz"))]
/// The maximum value for a point.
pub const MAX: Self = Self(
ClipperPoint64 {
Expand All @@ -118,12 +136,24 @@ impl<P: PointScaler> Point<P> {
PhantomData,
);

#[cfg(feature = "usingz")]
/// The maximum value for a point.
pub const MAX: Self = Self(
ClipperPoint64 {
x: i64::MAX,
y: i64::MAX,
z: 0,
},
PhantomData,
);

/// Create a new point.
pub fn new(x: f64, y: f64) -> Self {
Self(
ClipperPoint64 {
x: P::scale(x) as i64,
y: P::scale(y) as i64,
..Default::default()
},
PhantomData,
)
Expand All @@ -132,7 +162,14 @@ impl<P: PointScaler> Point<P> {
/// Create a new point from scaled values, this means that point is
/// constructed as is without applying the scaling multiplier.
pub fn from_scaled(x: i64, y: i64) -> Self {
Self(ClipperPoint64 { x, y }, PhantomData)
Self(
ClipperPoint64 {
x,
y,
..Default::default()
},
PhantomData,
)
}

/// Returns the x coordinate of the point.
Expand All @@ -158,6 +195,18 @@ impl<P: PointScaler> Point<P> {
pub(crate) fn as_clipperpoint64(&self) -> *const ClipperPoint64 {
&self.0
}

#[cfg(feature = "usingz")]
/// Returns the user data of the point.
pub fn z(&self) -> i64 {
self.0.z
}

#[cfg(feature = "usingz")]
/// Sets the user data of the point.
pub fn set_z(&mut self, z: i64) {
self.0.z = z;
}
}

impl<P: PointScaler> Default for Point<P> {
Expand Down
Loading