-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Add unit structs for each ease function #18739
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
Add unit structs for each ease function #18739
Conversation
…moothStep` directly instead of `EaseFunction::SmoothStep`.
…ingCurve`. Doesn't make sense for one to be broader. Also updated docs and added a TODO to the main `EasingCurve` docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty much exactly what I've been hoping for. Thanks for doing it!
As for the extra API surface, this is 100% worth it. It gives significant performance wins in my noise lib, basically for free. Also note that not all easing functions are zero-sized, differentiable, etc. We could build on this with enums like SmallEaseFunction
, DifferentiableEaseFunction
, and SmoothEaseFunction
(where the derivative is 0 at t=0 and t=1). That's what I'd use ultimately for my noise lib to allow users to change the ease at runtime. It would also open up the possibility for a Smooth
trait (WIP name).
So the API surface is not only worth it, but it's a blocker for these tools.
As for the docs, for now, I see no problem duplicating it. But if we add the other 3 enums above, this would become annoying. It doesn't have to be this pr, but I would either have the enum docs point back to the unit struct docs, or create a macro for each function that expands to the desired docs. Or just dump it all in module docs and refer to that. I've seen other crates do that sometimes.
Anyway, that's my thoughts. Excited to see this land!
}); | ||
} | ||
|
||
#[test] | ||
fn unit_structs_match_function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a bad thing to have, but all the functions just call each other, so I don't really see the need for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it to defend against copy and paste errors, although in retrospect the test was vulnerable to the same error.
I've updated with a macro based test that should be less vulnerable. Also open to removing it if there's a consensus that it's unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this :)
In general, I might prefer if the unit structs actually had Curve
appended to their names, but I could probably be convinced otherwise. (I recall trying to be pretty consistent about having Curve
in the name of curve types.)
In any case, thanks!
I'm open to adding My personal take is that I like the current solution's brevity and guess most people will recognise the names as curves. But adding |
Unfortunate that the compiler can't inline this... Does adding Could we make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer the FooCurve
names here. I'd also like to warn on the old struct / approach, and probably at the module docs level too. We need better guidance on when each approach should be preferred.
I don't think we should deprecated EaseFunction
, since it allows for runtime flexibility, but please correct me if I'm wrong.
…s_match_function` test - the macro magic no longer works well as the unit struct identifiers don't match `EaseFunction` identifiers.
|
Nope - I'm guessing the compiler stops as soon as it sees the size of
That's a good idea. I think it's worth exploring, but might also be a bit more controversial. So I'll leave it out of this PR unless other reviewers think it should be tried now. |
I've done a new version of the documentation that tries to be clearer about differences and user choices. The PR description might have been misleading - the intention is to offer more options, not replace I went with a more beginner-y focus (e.g. "zero to one" over "unit interval") as I guess easing functions are often used by people who are focused on visuals rather than math. Not sure if this was the right choice. Also added |
Can't you use macro magic to just append / strip |
I might be wrong as I'm still new to Rust, but as far I can tell that requires a proc macro or an external crate like EDIT: Should have said "requires a proc macro or external crate, which seems excessive for this case". |
Sounds good :) Not worth increasing complexity here. I haven't done a ton with macros myself, so I wasn't sure. |
Very nice docs, thank you! |
Objective
Allow users to directly call ease functions rather than going through the
EaseFunction
struct. This is less verbose and more efficient when the user doesn't need the data-driven aspects ofEaseFunction
.Background
EaseFunction
is a flexible and data-driven way to apply easing. But that has a price when a user just wants to call a specific ease function:This is a bit verbose, but also surprisingly inefficient. It calls the general
EaseFunction::eval
, which won't be inlined and adds an unnecessary branch. It can also increase code size since it pulls in all ease functions even though the user might only require one. As far as I can tell this is true even withopt-level = 3
andlto = "fat"
.Solution
This PR adds a struct for each ease function. Most are unit structs, but a couple have parameters:
The structs implement the
Curve<f32>
trait. This means they fit into the broaderCurve
system, and the user can choose betweensample
,sample_unchecked
, andsample_clamped
. The internals are a simple function call so the compiler can easily estimate the cost of inlining:In a microbenchmark this is around 4x faster. If inlining permits auto-vectorization then it's 20-50x faster, but that's a niche case.
Adding unit structs is also a small boost to discoverability - the unit struct can be found in VS Code via "Go To Symbol" -> "smoothstep", which doesn't find
EaseFunction::SmoothStep
.Concerns
Curve
trait.EasingCurve
, which requiresEaseFunction
.EasingCurve::new(a, b, SmoothStep)
.EasingCurve::new(a, b, EaseFunction::SmoothStep)
.EasingCurve
could be changed to support anyCurve<f32>
or a more limited trait.EaseFunction
.EaseFunction
.Testing