If the code hasn't been touched in this long, its probably release-worthy.

This commit is contained in:
2022-06-11 11:02:27 +02:00
parent 0c9eb831dd
commit d14fa7fde1
59 changed files with 1610 additions and 842 deletions

View File

@ -1,7 +1,6 @@
#pragma once
#include <FastLED.h>
#include "./Figment.h"
#include <vector>
class Display;
@ -24,6 +23,18 @@ struct AnimatedNumber {
}
}
void update(uint8_t speed) {
if (255 - speed >= m_idx) {
m_idx += speed;
} else {
m_idx = 255;
}
}
bool isFinished() const {
return m_idx == 255;
}
AnimatedNumber() {}
AnimatedNumber(uint8_t v) : m_end(v) {}

View File

@ -15,11 +15,11 @@ struct LinearCoordinateMapping: CoordinateMapping {
unsigned int startPixel = 0;
LinearCoordinateMapping() {}
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const {
return VirtualCoordinates{(uint8_t)((localCoords.x) / pixelCount), 0};
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
return VirtualCoordinates{map8(localCoords.x, 0, pixelCount), 0};
}
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
}
@ -27,7 +27,7 @@ struct LinearCoordinateMapping: CoordinateMapping {
return localCoords.x + startPixel;
}
unsigned int physicalPixelCount() const {
unsigned int physicalPixelCount() const override {
return pixelCount;
}
};

View File

@ -22,15 +22,15 @@ struct Task : public virtual Loopable {
};
Task() {}
Task(State initialState) : Task(0, initialState) {}
Task(const char* name) : Task(name, Running) {}
explicit Task(State initialState) : Task(0, initialState) {}
explicit Task(const char* name) : Task(name, Running) {}
Task(const char* name, State initialState) : name(name), state(initialState) {}
void start() { state = Running; onStart(); }
void stop() { onStop(); state = Stopped; }
virtual bool isFigment() const { return false; }
const char* name = 0;
const char* name = "";
State state = Running;
};
@ -42,8 +42,8 @@ struct TaskFunc: public Task {
struct Figment: public Task {
Figment() : Task() {}
Figment(State initialState) : Task(initialState) {}
Figment(const char* name) : Task(name) {}
explicit Figment(State initialState) : Task(initialState) {}
explicit Figment(const char* name) : Task(name) {}
Figment(const char* name, State initialState) : Task(name, initialState) {}
virtual void render(Display* dpy) const = 0;
bool isFigment() const override { return true; }

View File

@ -8,8 +8,8 @@ template<typename T> struct Coordinates {
T y;
};
struct VirtualCoordinates: Coordinates<uint16_t> {
VirtualCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
struct VirtualCoordinates: Coordinates<uint8_t> {
VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {}
};
struct PhysicalCoordinates: Coordinates<uint16_t> {

View File

@ -20,10 +20,63 @@ Variant::asInt() const
return m_value.asInt;
}
bool
Variant::asBool() const
{
return (bool)m_value.asInt;
}
void
InputSource::init()
{
#ifdef CONFIG_THREADED_INPUTS
m_queue = xQueueCreate(32, sizeof(InputEvent));
#endif
}
#ifdef CONFIG_THREADED_INPUTS
void
InputSource::readThread(void* data)
{
InputSource* self = static_cast<InputSource*>(data);
while(true) {
InputEvent evt = self->read();
if (evt.intent != InputEvent::None) {
xQueueSend(m_queue, &evt, 0)
}
taskYIELD();
}
}
#endif
void
InputSource::onStart()
{
#ifdef CONFIG_THREADED_INPUTS
m_threadLoop = MainLoop::instance();
xTaskCreate(
&InputSource::readThread,
name,
1000,
this,
1,
NULL
);
#endif
}
void
InputSource::loop()
{
#ifndef CONFIG_THREADED_INPUTS
MainLoop::instance()->dispatch(read());
#else
InputEvent evt;
xQueueReceive(m_queue, &evt, 0);
if (evt.intent != InputEvent::None) {
MainLoop::instance()->dispatch(evt);
}
#endif
}
InputEvent

View File

@ -32,6 +32,7 @@ struct Variant {
const char* asString() const;
CRGB asRGB() const;
int asInt() const;
bool asBool() const;
private:
union {
@ -74,6 +75,8 @@ struct InputEvent: public Variant {
// Timekeeping
ScheduleChange,
Beat,
BeatDetect,
// Task management
StartThing,
@ -87,6 +90,8 @@ struct InputEvent: public Variant {
// Firmware events
FirmwareUpdate,
ReadyToRoll,
};
template<typename Value>
@ -102,14 +107,26 @@ struct InputEvent: public Variant {
Intent intent;
};
struct MainLoop;
class InputSource: public Task {
public:
InputSource() : Task() {}
InputSource(const char* name) : Task(name) {}
InputSource(Task::State initialState) : Task(initialState) {}
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {}
InputSource() : Task() {init();}
explicit InputSource(const char* name) : Task(name) {init();}
explicit InputSource(Task::State initialState) : Task(initialState) {init();}
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {init();}
void loop() override;
void onStart() override;
virtual InputEvent read() = 0;
private:
void init();
#ifdef CONFIG_THREADED_INPUTS
static void readThread(void* data);
MainLoop* m_threadLoop;
static uint8_t m_threadBuf[sizeof(InputEvent) * 32];
QueueHandle_t m_queue;
StaticQueue_t m_threadQueue;
#endif
};
class InputFunc : public InputSource {
@ -128,7 +145,6 @@ private:
class BufferedInputSource: public InputSource {
public:
BufferedInputSource() : InputSource() {}
BufferedInputSource(const char* name) : InputSource(name) {}
InputEvent read() override;

View File

@ -16,6 +16,7 @@ MainLoop::dispatch(const InputEvent& evt)
void
MainLoop::loop()
{
s_instance = this;
InputEvent evt;
while (m_eventBuf.take(evt)) {
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
@ -23,10 +24,10 @@ MainLoop::loop()
for(auto figmentJob: scheduler.tasks) {
if (!strcmp(figmentJob->name, evt.asString())) {
if (jobState) {
//Log.notice("Starting %s", figmentJob->name);
Log.trace("Starting task %s", figmentJob->name);
figmentJob->start();
} else {
//Log.notice("Stopping %s", figmentJob->name);
Log.trace("Stopping task %s", figmentJob->name);
figmentJob->stop();
}
}
@ -34,6 +35,9 @@ MainLoop::loop()
}
for(Task* task : scheduler) {
if (evt.intent == InputEvent::SetPower) {
Log.notice("Event %s", task->name);
}
task->handleEvent(evt);
}
}
@ -44,10 +48,18 @@ MainLoop::loop()
Task* slowestTask = NULL;
for(Task* task : scheduler) {
//unsigned int start = millis();
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
unsigned int start = ESP.getCycleCount();
#else
unsigned int start = millis();
#endif
Log.verbose("Running %s", task->name);
task->loop();
//unsigned int runtime = millis() - start;
unsigned int runtime = ESP.getCycleCount() - start;
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
#else
unsigned int runtime = millis() - start;
#endif
frameSpeed += runtime;
taskCount++;
if (runtime > slowest) {
@ -57,19 +69,22 @@ MainLoop::loop()
}
frameSpeed = millis() - frameStart;
if (frameSpeed >= 23) {
Log.notice("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest/160000);
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest);
}
}
void
MainLoop::start()
{
s_instance = this;
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
Serial.flush();
for(auto task: scheduler) {
Log.notice("** Starting %s", task->name);
task->start();
}
dispatch(InputEvent::ReadyToRoll);
}
MainLoop* MainLoop::s_instance;

View File

@ -55,7 +55,7 @@ struct MainLoop {
Scheduler scheduler;
MainLoop(std::vector<Task*> &&tasks)
: scheduler(std::move(tasks)) {s_instance = this;}
: scheduler(std::move(tasks)) {}
void start();
void loop();
@ -63,7 +63,7 @@ struct MainLoop {
static MainLoop* instance() { return s_instance; }
private:
Ringbuf<InputEvent, 10> m_eventBuf;
Ringbuf<InputEvent, 32> m_eventBuf;
static MainLoop* s_instance;
};

View File

@ -0,0 +1,12 @@
#pragma once
#include <ArduinoLog.h>
struct PerfCounter {
PerfCounter(const char* name) {}
~PerfCounter() {}
/*PerfCounter(const char* name) : start(millis()), name(name) {}
~PerfCounter() {Log.notice("%s: %d", name, millis() - start);}*/
uint16_t start;
const char* name;
};

View File

@ -9,11 +9,18 @@ Renderer::loop()
for(Display* dpy : m_displays) {
for(Figment* figment : m_figments) {
if (figment->state == Task::Running) {
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
unsigned int frameStart = ESP.getCycleCount();
#endif
Log.verbose("Render %s", figment->name);
figment->render(dpy);
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
#else
unsigned int runtime = 0;
#endif
if (runtime >= 8) {
Log.notice("SLOW RENDER: %s took %dms!", figment->name, runtime);
Log.warning("SLOW RENDER: %s took %dms!", figment->name, runtime);
}
}
};

View File

@ -5,7 +5,7 @@ class Display;
struct Renderer: public Task {
public:
Renderer(std::vector<Display*> displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {}
Renderer(std::vector<Display*>&& displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(std::move(displays)) {}
void loop() override;
void onStart() override;

View File

@ -10,6 +10,11 @@ struct Ringbuf {
m_tail = 0;
}
T peek(int offset) const {
const int nextHead = (m_head + offset) % Size;
return m_items[nextHead];
}
bool take(T& dest) {
if (m_head == m_tail) {
return false;
@ -41,6 +46,22 @@ struct Ringbuf {
}
return ret;
}
size_t write(Print& stream) {
T val;
size_t ret = 0;
while(take(val)) {
stream.write(val);
}
return ret;
}
size_t size() {
if (m_tail > m_head) {
return m_tail - m_head;
}
return m_tail + (Size - m_head);
}
private:
int m_head = 0;
int m_tail = 0;

View File

@ -1,12 +1,29 @@
#include "./Surface.h"
#include "./Display.h"
#include <ArduinoLog.h>
#include "Perfcounter.h"
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
virtStart(start),
virtEnd(end),
m_display(dpy)
{
//assert(start.x <= end.x);
//assert(start.y <= end.y);
}
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
virtStart(start),
virtEnd(end),
m_display(dpy),
m_rotation(rotation)
{
//assert(start.x <= end.x);
//assert(start.y <= end.y);
}
Surface&
@ -30,11 +47,27 @@ Surface::operator+=(const CRGB& color)
void
Surface::paintWith(std::function<void(CRGB&)> func)
{
//Log.verbose("Painting startx=%d endx=%d starty=%d endy=%d", start.x, end.x, start.y, end.y);
for(auto x = start.x; x <= end.x; x++) {
for(auto y = start.y; y <= end.y; y++) {
//Log.verbose("x=%d y=%d", x, y);
func(m_display->pixelAt(PhysicalCoordinates{x, y}));
paintShader([=](CRGB& pixel, const VirtualCoordinates&, const PhysicalCoordinates&, const VirtualCoordinates&){ func(pixel); });
}
void
Surface::paintShader(Surface::Shader shader)
{
PerfCounter _("paintShader");
const uint16_t width = end.x - start.x + 1;
const uint16_t height = end.y - start.y + 1;
const uint8_t xMod = 255 / width;
const uint8_t yMod = 255 / height;
for(auto x = 0; x < width; x++) {
for(auto y = 0; y < height; y++) {
PhysicalCoordinates coords{x + start.x, y + start.y};
VirtualCoordinates virtCoords{m_display->coordinateMapping()->physicalToVirtualCoords(coords)};
VirtualCoordinates surfaceCoords{xMod * x, yMod * y};
//Log.notice("width=%d height=%d vx=%d vy=%d sx=%d sy=%d x=%d y=%d px=%d py=%d", width, height, start.x, start.y, x, y, coords.x, coords.y);
// 256 = 1.0
// 128 = 0.0
// 0 = 1.0
shader(m_display->pixelAt(coords), virtCoords, coords, surfaceCoords);
}
}
}

View File

@ -1,3 +1,5 @@
#pragma once
#include <FastLED.h>
#include "./Geometry.h"
#include <functional>
@ -7,15 +9,30 @@ class Display;
class Surface {
public:
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation);
Surface& operator=(const CRGB& color);
Surface& operator+=(const CRGB& color);
template<typename T>
Surface& operator|=(const T& val) {
paintWith([&](CRGB& pixel) {
pixel |= val;
});
return *this;
}
void paintWith(std::function<void(CRGB&)> func);
using Shader = std::function<void(CRGB&, const VirtualCoordinates& virtPos, const PhysicalCoordinates& realPos, const VirtualCoordinates& surfacePos)>;
using BrushFunc = std::function<void(CRGB&)>;
void paintWith(BrushFunc func);
void paintShader(Shader shader);
const PhysicalCoordinates start;
const PhysicalCoordinates end;
const VirtualCoordinates virtStart;
const VirtualCoordinates virtEnd;
private:
Display* m_display;
uint8_t m_rotation = 0;
};

View File

@ -0,0 +1,8 @@
{
"name": "Figments",
"version": "0.3.0",
"description": "An embedded graphics rendering engine",
"keywords": ["FastLED", "esp32", "esp8266"],
"frameworks": ["arduino"],
"platforms": ["espressif32", "espressif8266"]
}