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
68 changes: 36 additions & 32 deletions wpilibc/src/main/native/cpp/LEDPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,46 @@

using namespace frc;

LEDPattern::LEDPattern(LEDPatternFn impl) : m_impl(std::move(impl)) {}
LEDPattern::LEDPattern(std::function<void(frc::LEDPattern::LEDReader,
std::function<void(int, frc::Color)>)>
impl)
: m_impl(std::move(impl)) {}

void LEDPattern::ApplyTo(LEDPattern::LEDReader reader,
std::function<void(int, frc::Color)> writer) const {
m_impl(reader, writer);
}

void LEDPattern::ApplyTo(std::span<AddressableLED::LEDData> data,
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

writer);
}

void LEDPattern::ApplyTo(std::span<AddressableLED::LEDData> data) const {
ApplyTo(data, [&](int index, Color color) { data[index].SetLED(color); });
}

LEDPattern LEDPattern::Reversed() {
return LEDPattern{[self = *this](auto data, auto writer) {
self.ApplyTo(data, [&](int i, Color color) {
writer((data.size() - 1) - i, color);
});
LEDPattern LEDPattern::MapIndex(
std::function<size_t(size_t, size_t)> indexMapper) {
return LEDPattern{[self = *this, indexMapper](auto data, auto writer) {
size_t bufLen = data.size();
self.ApplyTo(
LEDPattern::LEDReader{
[=](auto i) { return data[indexMapper(bufLen, i)]; }, bufLen},
[&](int i, Color color) { writer(indexMapper(bufLen, i), color); });
}};
}

LEDPattern LEDPattern::Reversed() {
return MapIndex([](size_t bufLen, size_t i) { return bufLen - 1 - i; });
}

LEDPattern LEDPattern::OffsetBy(int offset) {
return LEDPattern{[=, self = *this](auto data, auto writer) {
self.ApplyTo(data, [&data, &writer, offset](int i, Color color) {
int shiftedIndex =
frc::FloorMod(i + offset, static_cast<int>(data.size()));
writer(shiftedIndex, color);
});
}};
return MapIndex([offset](size_t bufLen, size_t i) {
return frc::FloorMod(static_cast<int>(i) + offset,
static_cast<int>(bufLen));
});
}

LEDPattern LEDPattern::ScrollAtRelativeSpeed(units::hertz_t velocity) {
Expand All @@ -53,21 +66,17 @@ LEDPattern LEDPattern::ScrollAtRelativeSpeed(units::hertz_t velocity) {
// Invert and multiply by 1,000,000 to get microseconds
double periodMicros = 1e6 / velocity.value();

return LEDPattern{[=, self = *this](auto data, auto writer) {
auto bufLen = data.size();
return MapIndex([=](size_t bufLen, size_t i) {
auto now = wpi::Now();

// index should move by (bufLen) / (period)
double t =
(now % static_cast<int64_t>(std::floor(periodMicros))) / periodMicros;
int offset = static_cast<int>(std::floor(t * bufLen));

self.ApplyTo(data, [=](int i, Color color) {
// floorMod so if the offset is negative, we still get positive outputs
int shiftedIndex = frc::FloorMod(i + offset, static_cast<int>(bufLen));
writer(shiftedIndex, color);
});
}};
return frc::FloorMod(static_cast<int>(i) + offset,
static_cast<int>(bufLen));
});
}

LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
Expand All @@ -77,8 +86,7 @@ LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
auto microsPerLed =
static_cast<int64_t>(std::floor((ledSpacing / velocity).value() * 1e6));

return LEDPattern{[=, self = *this](auto data, auto writer) {
auto bufLen = data.size();
return MapIndex([=](size_t bufLen, size_t i) {
auto now = wpi::Now();

// every step in time that's a multiple of microsPerLED will increment
Expand All @@ -87,13 +95,9 @@ LEDPattern LEDPattern::ScrollAtAbsoluteSpeed(
// offset values for negative velocities
auto offset = static_cast<int64_t>(now) / microsPerLed;

self.ApplyTo(data, [=, &writer](int i, Color color) {
// FloorMod so if the offset is negative, we still get positive outputs
int shiftedIndex = frc::FloorMod(i + offset, static_cast<int>(bufLen));

writer(shiftedIndex, color);
});
}};
return frc::FloorMod(static_cast<int>(i) + offset,
static_cast<int>(bufLen));
});
}

LEDPattern LEDPattern::Blink(units::second_t onTime, units::second_t offTime) {
Expand Down
56 changes: 41 additions & 15 deletions wpilibc/src/main/native/include/frc/LEDPattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,36 @@

namespace frc {

/**
* Sets the LED at the given index to the given color.
*/
using LEDWriterFn = std::function<void(int, frc::Color)>;

/**
* Accepts a data buffer (1st argument) and a callback (2nd argument) for
* writing data.
*/
using LEDPatternFn =
std::function<void(std::span<frc::AddressableLED::LEDData>, LEDWriterFn)>;

class LEDPattern {
public:
explicit LEDPattern(LEDPatternFn impl);
/**
* A wrapper around a length and an arbitrary reader function that accepts an
* LED index and returns data for the LED at that index. This configuration
* allows us to abstract over different container types without templating.
*/
class LEDReader {
public:
LEDReader(std::function<frc::AddressableLED::LEDData(int)> impl,
size_t size)
: m_impl{std::move(impl)}, m_size{size} {}

frc::AddressableLED::LEDData operator[](size_t index) const {
return m_impl(index);
}

size_t size() const { return m_size; }

private:
std::function<frc::AddressableLED::LEDData(int)> m_impl;
size_t m_size;
};

explicit LEDPattern(std::function<void(frc::LEDPattern::LEDReader,
std::function<void(int, frc::Color)>)>
impl);

void ApplyTo(LEDReader reader,
std::function<void(int, frc::Color)> writer) const;

/**
* Writes the pattern to an LED buffer. Dynamic animations should be called
Expand All @@ -48,7 +63,7 @@ class LEDPattern {
* @param writer data writer for setting new LED colors on the LED strip
*/
void ApplyTo(std::span<frc::AddressableLED::LEDData> data,
LEDWriterFn writer) const;
std::function<void(int, frc::Color)> writer) const;

/**
* Writes the pattern to an LED buffer. Dynamic animations should be called
Expand All @@ -64,6 +79,15 @@ class LEDPattern {
*/
void ApplyTo(std::span<frc::AddressableLED::LEDData> data) const;

/**
* Creates a pattern with remapped indices.
*
* @param indexMapper the index mapper
* @return the mapped pattern
*/
[[nodiscard]]
LEDPattern MapIndex(std::function<size_t(size_t, size_t)> indexMapper);

/**
* Creates a pattern that displays this one in reverse. Scrolling patterns
* will scroll in the opposite direction (but at the same speed). It will
Expand Down Expand Up @@ -373,6 +397,8 @@ class LEDPattern {
static LEDPattern Rainbow(int saturation, int value);

private:
LEDPatternFn m_impl;
std::function<void(frc::LEDPattern::LEDReader,
std::function<void(int, frc::Color)>)>
m_impl;
};
} // namespace frc
Loading
Loading