-
Notifications
You must be signed in to change notification settings - Fork 618
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
[wpilibj, wpilibc] Fix LED patterns not offsetting reads #6948
Conversation
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
101fa9f
to
54a86d6
Compare
/format |
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
/format |
// data) const { | ||
// ApplyTo(data, [&](int index, Color color) { data[index].SetLED(color); }); | ||
// } | ||
|
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.
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()}, |
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 will copy the input span by value, but should be fine since that's only a pointer and length
writer((data.size() - 1) - i, color); | ||
}); | ||
self.ApplyTo( | ||
LEDPattern::LEDReader{[=](auto i) { return data[data.size() - 1 - i]; }, |
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 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?
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.
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) { |
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.
Is a std::move is necessary, or would a value capture in the lambda would be enough?
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.
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.
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.
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
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.
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) { |
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.
Ditto
|
||
writer(shiftedIndex, color); | ||
}); | ||
self.ApplyTo(LEDPattern::LEDReader{[=, d = std::move(data)](auto i) { |
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.
Ditto
@@ -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() { |
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.
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
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.
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?
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.
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
3c62119
to
768a054
Compare
Was causing bugs when combined with patterns that need to read back from the buffer (eg masks and overlays)
Todo:
Fixes #6824