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

Convert static ChassisSpeeds factories to use instance methods and remove deprecation #7433

Closed

Conversation

mjansen4857
Copy link
Contributor

@mjansen4857 mjansen4857 commented Nov 25, 2024

This PR is basically identical to #7418, which allows chaining of the toFieldRelativeSpeeds, toRobotRelativeSpeeds, and discretize methods. I'm not exactly sure why that PR was closed, but I think the current implementation makes things a bit more verbose, especially when converting usage of the deprecated static methods to the new instance methods.

For example, a common usage of the static methods could look something like this:

swerve.driveRobotRelative(ChassisSpeeds.fromFieldRelativeSpeeds(
    someXValue,
    someYValue,
    someOmegaValue,
    robotRotation
));

The current implementation that returns void would require the above example to be converted like this:

ChassisSpeeds speeds = new ChassisSpeeds(someXValue, someYValue, someOmegaValue);
speeds.toRobotRelativeSpeeds(robotRotation);
swerve.driveRobotRelative(speeds);

Returning a self reference from these methods allows this conversion to be simplified back down to:

swerve.driveRobotRelative(new ChassisSpeeds(someXValue, someYValue, someOmegaValue)
    .toRobotRelativeSpeeds(robotRotation));

This also has the added benefit of allowing method chaining, but I'm mainly concerned about being able to directly pass the result of these methods to another method without having to create a variable and call a method as an intermediate step.

EDIT:
This PR has been reworked to instead remove the deprecation from the static factory methods, and remove their deprecation. This is a better solution overall than the previous iteration of this PR, as it allows the user to choose whether they want to create a new ChassisSpeeds object or modify the original.

@mjansen4857 mjansen4857 requested a review from a team as a code owner November 25, 2024 05:31
@mjansen4857
Copy link
Contributor Author

Honestly I think a better solution here is probably to just make these methods return a new ChassisSpeeds object instead of modifying the original, or un-deprecate the static methods so the user can choose if they want a new object or not. There are a lot of situations where the user may not want the original object modified because it gets used again further down in some method. This happens a bunch in PPLib, for example. The current implementation makes working with those situations pretty rough, since you need to create a copy of the original speeds which is very verbose in java:

ChassisSpeeds prevRobotSpeeds = new ChassisSpeeds(
    prevState.fieldSpeeds.vxMetersPerSecond, 
    prevState.fieldSpeeds.vyMetersPerSecond, 
    prevState.fieldSpeeds.omegaRadiansPerSecond);
prevRobotSpeeds.toRobotRelativeSpeeds(prevState.pose.getRotation());

instead of just:

ChassisSpeeds prevRobotSpeeds = prevState.fieldSpeeds.toRobotRelativeSpeeds(prevState.pose.getRotation());

@KangarooKoala
Copy link
Contributor

I'm not exactly sure why that PR was closed

Here's my understanding of the history:

I think a better solution here is probably to just make these methods return a new ChassisSpeeds object instead of modifying the original

That would be better than returning a self reference from the ChassisSpeeds modifiers. It comes at the expense of memory allocations, though, so it'd be up to a library maintainer (i.e., @calcmogul) whether the nicer API is worth the memory allocations.

un-deprecate the static methods so the user can choose if they want a new object or not

That's my personal preferred approach (especially since from shortly after the #7418 discussion the change will be reverted for 2027). However, it comes with the drawback of increased maintenance burden.

Thinking more about this, though, the static methods could be implemented in terms of the instance mutators (Just call the methods on a copy/new object), which would mean we would keep only one implementation of the logic. There would still be a lot more overloads, though.

@mjansen4857
Copy link
Contributor Author

That's my personal preferred approach (especially since from shortly after the #7418 discussion the change will be reverted for 2027). However, it comes with the drawback of increased maintenance burden.

Thinking more about this, though, the static methods could be implemented in terms of the instance mutators (Just call the methods on a copy/new object), which would mean we would keep only one implementation of the logic. There would still be a lot more overloads, though.

Yeah I think removing the deprecation from the factory methods and implementing them to use the instance methods is the best solution. I can rework this PR to do that if a maintainer is on board.

@narmstro2020
Copy link
Contributor

Since I had a hand or two on the previously mentioned PR's. I think these changes are totally fine. I missed this whole discussion due to both prepping for the next season and some medical issues.

@Daniel1464
Copy link
Contributor

Daniel1464 commented Nov 28, 2024

Wait, doesn't the current ChassisSpeeds.fromFieldRelativeSpeeds implementation need new allocations anyways?
If you're passing in a ChassisSpeeds supplier to your swerve drivetrain class, you have to make new allocations anyways.
I think if reducing allocations is the goal, it's better to have a builder alternative, i.e:

var newSpeeds = ChassisSpeeds.builder()
    .toFieldRelative(rotation)
    .discretized(0.02)
    .build(xVelocity, yVelocity, zVelocity);

With some optimizations, this would only create a single new ChassisSpeeds object while being more intuitive than ChassisSpeeds.fromFieldRelativeSpeeds and such.

@KangarooKoala
Copy link
Contributor

Wait, doesn't the current ChassisSpeeds.fromFieldRelativeSpeeds implementation need new allocations anyways?

Yes, and it's deprecated for 2025: ChassisSpeeds.fromFieldRelativeSpeeds(double,double,double,Rotation2d)

If you're passing in a ChassisSpeeds supplier to your swerve drivetrain class, you have to make new allocations anyways.

Correct. However, instead of every transformation making a new allocation, if the transformations modify the object in place, then we only make one ChassisSpeeds instance at the start.

I think if reducing allocations is the goal, it's better to have a builder alternative, i.e:

var newSpeeds = ChassisSpeeds.builder()
    .toFieldRelative(rotation)
    .discretized(0.02)
    .build(xVelocity, yVelocity, zVelocity);

With some optimizations, this would only create a single new ChassisSpeeds object while being more intuitive than ChassisSpeeds.fromFieldRelativeSpeeds and such.

Could you explain how this is better? From my second point, what's currently on main (transformations modify the object in place) only allocates once, while this would allocate twice (one for the builder, another for the ChassisSpeeds). I also would be hesitant to say a builder approach is more intuitive, because then you have an intermediate type and a final type, instead of always using the same type. Maybe you're talking about pass by reference causing modifications to show up far from the source?

@Daniel1464
Copy link
Contributor

Daniel1464 commented Nov 28, 2024

On second note, it might be better to have mutating versions of the instance methods while making the regular methods immutable. For instance:

var speeds = new ChassisSpeeds(0.0, 0.0, 0.0).toFieldRelative(Rotation2d.fromDegrees(0.0)).mut_discretize(0.02);

I was thinking about the builder as a lower-overhead variant of a mutating toFieldRelative that could be used in the internal library/teams who want lower allocations, but i realized that just having mut_ methods(in a similar vein to the units library) is the better option here.

@mjansen4857 mjansen4857 changed the title Return self reference from ChassisSpeeds modifiers Convert static ChassisSpeeds factories to use instance methods and remove deprecation Nov 29, 2024
@calcmogul
Copy link
Member

I don't like the idea of there being two parallel APIs. I also don't think there's a good API we can land on with the roboRIO's garbage collector being bad. I'm tempted to just leave this API as it is and fix it in 2027; on the MRC, we can just use immutable instance methods like the geometry API.

@Daniel1464
Copy link
Contributor

I don't like the idea of there being two parallel APIs. I also don't think there's a good API we can land on with the roboRIO's garbage collector being bad. I'm tempted to just leave this API as it is and fix it in 2027; on the MRC, we can just use immutable instance methods like the geometry API.

The problem w/ that is that people's code will (mostly) silently break without people knowing about it, since the return type is easily discarded. I think it's better to have a mut_toRobotRelative and a toRobotRelative side-by-side, then deprecate the mutable version.

@calcmogul
Copy link
Member

calcmogul commented Dec 9, 2024

By "as it is", I meant without the return value (i.e., what's on main). I agree that adding a return value turns this into a silent breaking change relative to the static API that'll be hard to diagnose. In particular, it makes the same API interaction have mutable semantics instead of immutable semantics.

@KangarooKoala
Copy link
Contributor

Given #7549, should this be closed?

@calcmogul
Copy link
Member

OBE by #7549. Also, we're not planning on making any changes to ChassisSpeeds until 2027.

@calcmogul calcmogul closed this Dec 14, 2024
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.

5 participants