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

[wpilibj, wpilibc] Fix LED patterns not offsetting reads #6948

Merged
merged 13 commits into from
Nov 29, 2024

Conversation

SamCarlberg
Copy link
Member

@SamCarlberg SamCarlberg commented Aug 11, 2024

Was causing bugs when combined with patterns that need to read back from the buffer (eg masks and overlays)

Todo:

  • Java implementation
  • C++ implementation (needs a custom span type for reading with a modulo offset)

Fixes #6824

@SamCarlberg SamCarlberg added the help: needs C++ Java exists, needs C++ port label Sep 20, 2024
Was causing bugs when combined with patterns that need to read back from the buffer (eg masks and overlays)
Didn't actually cause issues due to the small buffer size, but it's good to be consistent
@SamCarlberg SamCarlberg force-pushed the fix/led-pattern-offsets branch from 101fa9f to 54a86d6 Compare October 23, 2024 03:27
@SamCarlberg
Copy link
Member Author

/format

github-actions bot and others added 2 commits October 23, 2024 03:32
For wrapping different types of containers (std::span, wpi::rotated_span, etc) with a single type and without templating the LEDPattern class

Probably still scuffed
@SamCarlberg
Copy link
Member Author

/format

@SamCarlberg SamCarlberg marked this pull request as ready for review October 24, 2024 04:17
@SamCarlberg SamCarlberg requested a review from a team as a code owner October 24, 2024 04:17
@SamCarlberg SamCarlberg removed the help: needs C++ Java exists, needs C++ port label Nov 3, 2024
PeterJohnson
PeterJohnson previously approved these changes Nov 3, 2024
// data) const {
// ApplyTo(data, [&](int index, Color color) { data[index].SetLED(color); });
// }

Copy link
Member

Choose a reason for hiding this comment

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

Do you want to leave in this commented block?

LEDWriterFn writer) const {
m_impl(data, writer);
std::function<void(int, frc::Color)> writer) const {
ApplyTo(LEDPattern::LEDReader{[=](size_t i) { return data[i]; }, data.size()},
Copy link
Member Author

Choose a reason for hiding this comment

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

This will copy the input span by value, but should be fine since that's only a pointer and length

wpilibc/src/main/native/cpp/LEDPattern.cpp Outdated Show resolved Hide resolved
writer((data.size() - 1) - i, color);
});
self.ApplyTo(
LEDPattern::LEDReader{[=](auto i) { return data[data.size() - 1 - i]; },
Copy link
Member Author

Choose a reason for hiding this comment

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

I think a copy-by-value is actually necessary here. Patterns are usually built by chaining up from a base pattern (eg LEDPattern::Solid(...).ScrollAtRelativeSpeed(...).Mask(...).Reversed(), and all those intermediate patterns would get freed soon after. As I understand it, the final object that sticks around needs to have ownership of its own copy to avoid use-after-free

@PeterJohnson does that sound correct to you?

Copy link
Contributor

Choose a reason for hiding this comment

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

My two cents after rereading the method- We shouldn't need to copy data, since the LEDReader lambda's lifetime is confined to the LEDPattern lambda's lifetime (The LEDReader to passes to ApplyTo and then discarded), and data is guaranteed to be valid for at least that long.
I wouldn't be opposed to keeping the copy-by-value to play it safe, though.

writer(shiftedIndex, color);
});
return LEDPattern{[=, self = *this](LEDPattern::LEDReader data, auto writer) {
self.ApplyTo(LEDPattern::LEDReader{[=, d = std::move(data)](auto i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Is a std::move is necessary, or would a value capture in the lambda would be enough?

Copy link
Contributor

Choose a reason for hiding this comment

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

Based on my reading of https://en.cppreference.com/w/cpp/language/lambda, using a value capture would copy LEDReader, which would copy the std::function member, whereas using the std::move() moves the (by-value) parameter, which I think is slightly preferable. I'll let others weigh in on whether this is correct and if the difference is significant.

Copy link
Member Author

Choose a reason for hiding this comment

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

As I understand it, std::move could make the original owner invalid, which would be fine if the original owners are short lived intermediate objects. But I don't think that's actually the case here (or at least isn't guaranteed). It's entirely possible to do LEDPattern::Gradient(...).Reversed(), where the gradient is really a member of the reversed pattern, or auto gradient = LEDPattern::Gradient(...); auto reversedGradient = gradient.Reversed(), where the original gradient is still referenced and presumably used

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, std::move() can make the original owner invalid. However, as I understand it, since LEDPattern::LEDReader data is pass by copy, the "original owner" is our copy of the argument, not what was passed in.

That said, I just noticed that we do reference data elsewhere in the outer lambda bodies, so I think it does need to be a by-copy capture.

Is a std::move is necessary, or would a value capture in the lambda would be enough?

After re-reading this, I have a question- What concerns did you have about a value capture?

int shiftedIndex = frc::FloorMod(i + offset, static_cast<int>(bufLen));
writer(shiftedIndex, color);
});
self.ApplyTo(LEDPattern::LEDReader{[=, d = std::move(data)](auto i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Ditto


writer(shiftedIndex, color);
});
self.ApplyTo(LEDPattern::LEDReader{[=, d = std::move(data)](auto i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Ditto

wpilibc/src/main/native/include/frc/LEDPattern.h Outdated Show resolved Hide resolved
@@ -145,7 +145,29 @@ default <T extends LEDReader & LEDWriter> void applyTo(T readWriter) {
default LEDPattern reversed() {
return (reader, writer) -> {
int bufLen = reader.getLength();
applyTo(reader, (i, r, g, b) -> writer.setRGB((bufLen - 1) - i, r, g, b));
applyTo(
new LEDReader() {
Copy link
Member Author

Choose a reason for hiding this comment

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

A more generic LEDReader implementation that accepts an arbitrary remapping function may be better than a single-purpose class for offsets and this anonymous class

Copy link
Contributor

Choose a reason for hiding this comment

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

Going a step further, would it be possible to have an overload that accepts a remapping function that is applied to both the reader and writer?

Copy link
Contributor

Choose a reason for hiding this comment

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

Made a PR that adds a mapIndex() decorator that applies the mapping function to both the reader and writer: SamCarlberg#2

Now operates on arbitrary remapping functions, not just a fixed offset
@SamCarlberg SamCarlberg force-pushed the fix/led-pattern-offsets branch from 3c62119 to 768a054 Compare November 8, 2024 03:24
@PeterJohnson PeterJohnson merged commit 5e1c6a8 into wpilibsuite:main Nov 29, 2024
44 checks passed
@SamCarlberg SamCarlberg deleted the fix/led-pattern-offsets branch November 29, 2024 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Offset LED patterns only offset writes, not reads
3 participants