Skip to content

Add reuseable context to new expand_* functions - #582

Open
taj-p wants to merge 2 commits into
mainfrom
tajp/reuseableExpandCtx
Open

Add reuseable context to new expand_* functions#582
taj-p wants to merge 2 commits into
mainfrom
tajp/reuseableExpandCtx

Conversation

@taj-p

@taj-p taj-p commented May 8, 2026

Copy link
Copy Markdown
Contributor

While reviewing linebender/vello#1628 (comment), I noticed that the new expand methods allocate a new BezPath per call.

The proposed variant (exposing a ExpandCtx) parallels the existing StrokeCtx.

kurbo/kurbo/src/stroke.rs

Lines 200 to 220 in 6836244

/// A structure that is used for creating strokes.
///
/// See also [`stroke_with`].
#[derive(Default, Debug)]
pub struct StrokeCtx {
// As a possible future optimization, we might not need separate storage
// for forward and backward paths, we can add forward to the output in-place.
// However, this structure is clearer and the cost fairly modest.
output: BezPath,
forward_path: BezPath,
backward_path: BezPath,
result_path: BezPath,
start_pt: Point,
start_norm: Vec2,
start_tan: Vec2,
last_pt: Point,
last_tan: Vec2,
// Precomputation of the join threshold, to optimize per-join logic.
// If hypot < (hypot + dot) * join_thresh, omit join altogether.
join_thresh: f64,
}

Further, the proposed ExpandCtx provides the ability to better integrate with a BezPath free list. For example, by providing methods into ExpandCtx that allow a caller to "take" ownership of the underlying BezPath we can more easily move that allocation into a glyph cache (without cloning).

Happy to change any and all naming - not attached to anything here.

cc @jrmoulton

Comment thread kurbo/src/expand.rs Outdated
Comment on lines +77 to +81
/// Take the output path by value, leaving an empty `BezPath` in its place.
#[inline(always)]
pub fn take_output(&mut self) -> BezPath {
core::mem::take(&mut self.output)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I dropped this. Consumers can simply replace_output(BezPath::default()) for the same behaviour. But happy to add back if requested 🙏

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'd be in favour of adding this method. Seems like a common case, and there's little cost to having it.
(importance level: low)

@raphlinus raphlinus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a good idea and am ok with the PR landing as-is. One question I have is whether we need the API surface duplication and just have everything on ExpandCtx, get rid of the free functions.

Comment thread kurbo/src/expand.rs

/// The same as [`expand_path`], but using a caller-provided [`ExpandCtx`] to enable
/// allocation re-use.
pub fn expand_path_with(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It feels appropriate to have this (and the signed version) as a &mut self method on ExpandCtx, as opposed to taking a context argument.

Comment thread kurbo/src/expand.rs
self.tolerance = tolerance;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thinking about the ergonomics in the simple case, I'm wondering if it might be nice to have an into BezPath (ie From<ExpandCtx> for BezPath). Then the simple case looks like:

    let mut ctx = ExpandCtx::default();
    ctx.expand(...);
    let bezpath: BezPath = ctx.into();

Not quite as short as the free function, but I'm thinking this could be a reminder that there's an allocation and a nudge to maybe get rid of it.

@nicoburns nicoburns May 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't really like using Into for this. I feel like if we're going to do this it ought to just be a regular method (fn into_bez_path(self) or similar). A bonus of this is that you won't run into type inference issues.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair enough, the type inference concern is legit.

@waywardmonkeys waywardmonkeys left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I know it is obvious to us, but we should be a bit more clear in the API docs. They should explicitly say _with functions return output through ExpandCtx::output(), that the output is cleared/reused on the next call, and that callers must copy/consume it before reusing the context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants