Skip to content

Commit 77cc503

Browse files
committed
Refactor task parallelism (monitorMutex, isPositions, memcpy)
back end ======= - main: add monitorMutex, use isPositions, add effect loop memcpy - node manager: delay(100) before deleting a node so the loop can finish - Physical layer, ArtnetOut, PLEDDriver: remove isPositions from loop and loopDriver (done in main) - Lights control: add monitorMutex and monitorMillis
1 parent 28eceab commit 77cc503

File tree

8 files changed

+87
-121
lines changed

8 files changed

+87
-121
lines changed

docs/develop/architecture.md

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -161,44 +161,14 @@ Synchronization Flow
161161

162162
void effectTask(void* param) {
163163
while (true) {
164-
if (layerP.lights.useDoubleBuffer) {
165-
166-
layerP.loop(); // getRGB and setRGB both use channelsBack
167-
168-
// Atomic swap channels
169-
xSemaphoreTake(swapMutex, portMAX_DELAY);
170-
uint8_t* temp = layerP.lights.channelsD;
171-
layerP.lights.channelsD = layerP.lights.channelsE;
172-
layerP.lights.channelsE = temp;
173-
newFrameReady = true;
174-
xSemaphoreGive(swapMutex);
175-
176-
} else {
177-
xSemaphoreTake(swapMutex, portMAX_DELAY);
178-
layerP.loop();
179-
xSemaphoreGive(swapMutex);
180-
}
164+
// tbd ...
181165
vTaskDelay(1);
182166
}
183167
}
184168

185169
void driverTask(void* param) {
186170
while (true) {
187-
xSemaphoreTake(swapMutex, portMAX_DELAY);
188-
esp32sveltekit.lps++;
189-
190-
if (layerP.lights.useDoubleBuffer) {
191-
if (newFrameReady) {
192-
newFrameReady = false;
193-
xSemaphoreGive(swapMutex);
194-
layerP.loopDrivers(); // ✅ No lock needed
195-
} else {
196-
xSemaphoreGive(swapMutex);
197-
}
198-
} else {
199-
layerP.loopDrivers(); // ✅ Protected by lock
200-
xSemaphoreGive(swapMutex);
201-
}
171+
// tbd ...
202172
vTaskDelay(1);
203173
}
204174
}

src/MoonBase/NodeManager.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ class NodeManager : public Module {
220220
}
221221

222222
oldNode->requestMappings();
223-
223+
224+
delay(100); // to allow the node to finish its last loop
224225
EXT_LOGD(ML_TAG, "remove oldNode: %d p:%p", nodes->size(), oldNode);
225226
// delete node; //causing assert failed: multi_heap_free multi_heap_poisoning.c:259 (head != NULL) ATM
226227
// EXT_LOGD(MB_TAG, "destructing object (inPR:%d)", isInPSRAM(node));

src/MoonLight/Layers/PhysicalLayer.cpp

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ void PhysicalLayer::setup() {
4242
lights.maxChannels = MIN(ESP.getPsramSize() / 4, 61440 * 3); // fill halve with channels, max 120 pins * 512 LEDs, still addressable with uint16_t
4343
lights.useDoubleBuffer = true; // Enable double buffering
4444
} else {
45-
lights.maxChannels = 4096 * 3; // esp32-d0: max 1024->2048->4096 Leds ATM
45+
lights.maxChannels = 4096 * 3; // esp32-d0: max 1024->2048->4096 Leds ATM
4646
lights.useDoubleBuffer = false; // Single buffer mode
4747
}
4848

@@ -71,12 +71,9 @@ void PhysicalLayer::setup() {
7171
}
7272

7373
void PhysicalLayer::loop() {
74-
if (lights.header.isPositions == 0 || lights.header.isPositions == 3) { // otherwise lights is used for positions etc.
75-
76-
// runs the loop of all effects / nodes in the layer
77-
for (VirtualLayer* layer : layers) {
78-
if (layer) layer->loop(); // if (layer) needed when deleting rows ...
79-
}
74+
// runs the loop of all effects / nodes in the layer
75+
for (VirtualLayer* layer : layers) {
76+
if (layer) layer->loop(); // if (layer) needed when deleting rows ...
8077
}
8178
}
8279

@@ -108,19 +105,14 @@ void PhysicalLayer::loopDrivers() {
108105
requestMapVirtual = false;
109106
}
110107

111-
if (lights.header.isPositions == 3) {
112-
EXT_LOGD(ML_TAG, "positions done (3 -> 0)");
113-
lights.header.isPositions = 0; // now driver can show again
114-
}
108+
if (prevSize != lights.header.size) EXT_LOGD(ML_TAG, "onSizeChanged P %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, lights.header.size.x, lights.header.size.y, lights.header.size.z);
115109

116-
if (lights.header.isPositions == 0) { // otherwise lights is used for positions etc.
117-
if (prevSize != lights.header.size) EXT_LOGD(ML_TAG, "onSizeChanged P %d,%d,%d -> %d,%d,%d", prevSize.x, prevSize.y, prevSize.z, lights.header.size.x, lights.header.size.y, lights.header.size.z);
118-
for (Node* node : nodes) {
119-
if (prevSize != lights.header.size) node->onSizeChanged(prevSize);
120-
if (node->on) node->loop();
121-
}
122-
prevSize = lights.header.size;
110+
for (Node* node : nodes) {
111+
if (prevSize != lights.header.size) node->onSizeChanged(prevSize);
112+
if (node->on) node->loop();
123113
}
114+
115+
prevSize = lights.header.size;
124116
}
125117

126118
void PhysicalLayer::mapLayout() {

src/MoonLight/Modules/ModuleDrivers.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,6 @@ class ModuleDrivers : public NodeManager {
159159
bool initPins = false;
160160

161161
void loop() override {
162-
// if (layerP.lights.header.isPositions == 0) //otherwise lights is used for positions etc.
163-
// layerP.loop(); //run all the effects of all virtual layers (currently only one)
164-
165162
NodeManager::loop();
166163

167164
if (!initPins) {

src/MoonLight/Modules/ModuleLightsControl.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -352,31 +352,30 @@ class ModuleLightsControl : public Module {
352352
}
353353

354354
#if FT_ENABLED(FT_MONITOR)
355+
extern SemaphoreHandle_t monitorMutex; // defined in main
355356
if (layerP.lights.header.isPositions == 2) { // send to UI
356357
read([&](ModuleState& _state) {
357358
if (_socket->getConnectedClients() && _state.data["monitorOn"]) {
358-
_socket->emitEvent("monitor", (char*)&layerP.lights.header, 37); // sizeof(LightsHeader)); //sizeof(LightsHeader), nearest prime nr above 32 to avoid monitor data to be seen as header
359+
_socket->emitEvent("monitor", (char*)&layerP.lights.header, 37); // sizeof(LightsHeader)); //sizeof(LightsHeader), nearest prime nr above 32 to avoid monitor data to be seen as header
359360
_socket->emitEvent("monitor", (char*)layerP.lights.channelsE, MIN(layerP.lights.header.nrOfLights * 3, layerP.lights.maxChannels)); //*3 is for 3 bytes position
360361
}
361362
memset(layerP.lights.channelsE, 0, layerP.lights.maxChannels); // set all the channels to 0 //cleaning the positions
362363
EXT_LOGD(ML_TAG, "positions sent to monitor (2 -> 3, #L:%d maxC:%d)", layerP.lights.header.nrOfLights, layerP.lights.maxChannels);
363364
layerP.lights.header.isPositions = 3;
364365
});
365366
} else if (layerP.lights.header.isPositions == 0 && layerP.lights.header.nrOfLights) { // send to UI
366-
EVERY_N_MILLIS(layerP.lights.header.nrOfLights / 12) {
367-
367+
static unsigned long monitorMillis = 0;
368+
if (millis() - monitorMillis >= layerP.lights.header.nrOfLights / 12) {
369+
monitorMillis = millis();
370+
368371
read([&](ModuleState& _state) {
369372
if (_socket->getConnectedClients() && _state.data["monitorOn"]) {
370-
371-
//protect emit by swapMutex, see main.cpp
372-
extern SemaphoreHandle_t swapMutex;
373-
374-
xSemaphoreTake(swapMutex, portMAX_DELAY);
373+
// protect emit by monitorMutex, see main.cpp
374+
xSemaphoreTake(monitorMutex, portMAX_DELAY);
375375
_socket->emitEvent("monitor", (char*)layerP.lights.channelsE, MIN(layerP.lights.header.nrOfChannels, layerP.lights.maxChannels));
376-
xSemaphoreGive(swapMutex);
376+
xSemaphoreGive(monitorMutex);
377377
}
378378
});
379-
380379
}
381380
}
382381
#endif

src/MoonLight/Nodes/Drivers/D_ArtnetOut.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class ArtNetOutDriver : public DriverNode {
106106

107107
LightsHeader* header = &layerP.lights.header;
108108

109-
if (header->isPositions != 0 || nrOfIPAddresses == 0) return; // don't sent if positions are sent or no IP addresses found (to do broadcast if no addresses specified...!)
109+
if (nrOfIPAddresses == 0) return; // don't sent if no IP addresses found (to do broadcast if no addresses specified...!)
110110

111111
// continue with Art-Net code
112112
uint8_t actualIPIndex = 0;

src/MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,22 @@ class ParallelLEDDriver : public DriverNode {
5151
#if HP_ALL_DRIVERS
5252
if (!initDone) return;
5353

54-
if (layerP.lights.header.isPositions == 0) {
55-
DriverNode::loop(); // This populates the LUT tables!
54+
DriverNode::loop(); // This populates the LUT tables!
5655

5756
#ifndef CONFIG_IDF_TARGET_ESP32P4
58-
if (ledsDriver.total_leds > 0) ledsDriver.showPixels(WAIT);
57+
if (ledsDriver.total_leds > 0) ledsDriver.showPixels(WAIT);
5958
#else
60-
uint8_t nrOfPins = min(layerP.nrOfLedPins, layerP.nrOfAssignedPins);
61-
// LUTs are accessed directly within show_parlio via extern ledsDriver
62-
// No brightness parameter needed
63-
show_parlio(pins, layerP.lights.header.nrOfLights, layerP.lights.channelsD, layerP.lights.header.channelsPerLight == 4, nrOfPins, layerP.ledsPerPin[0], layerP.lights.header.offsetRed, layerP.lights.header.offsetGreen, layerP.lights.header.offsetBlue);
59+
uint8_t nrOfPins = min(layerP.nrOfLedPins, layerP.nrOfAssignedPins);
60+
// LUTs are accessed directly within show_parlio via extern ledsDriver
61+
// No brightness parameter needed
62+
show_parlio(pins, layerP.lights.header.nrOfLights, layerP.lights.channelsD, layerP.lights.header.channelsPerLight == 4, nrOfPins, layerP.ledsPerPin[0], layerP.lights.header.offsetRed, layerP.lights.header.offsetGreen, layerP.lights.header.offsetBlue);
6463
#endif
65-
}
6664
#else // ESP32_LEDSDRIVER
6765
if (!ledsDriver.initLedsDone) return;
6866

69-
if (layerP.lights.header.isPositions == 0) {
70-
DriverNode::loop();
67+
DriverNode::loop();
7168

72-
ledsDriver.show();
73-
}
69+
ledsDriver.show();
7470
#endif
7571
}
7672

src/main.cpp

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ ModuleLiveScripts moduleLiveScripts = ModuleLiveScripts(&server, &esp32sveltekit
114114
ModuleChannels moduleChannels = ModuleChannels(&server, &esp32sveltekit);
115115
ModuleMoonLightInfo moduleMoonLightInfo = ModuleMoonLightInfo(&server, &esp32sveltekit);
116116

117-
SemaphoreHandle_t swapMutex = NULL;
117+
SemaphoreHandle_t swapMutex = xSemaphoreCreateMutex();
118+
SemaphoreHandle_t monitorMutex = xSemaphoreCreateMutex();
118119
volatile bool newFrameReady = false;
119120

120121
TaskHandle_t effectTaskHandle = NULL;
@@ -127,40 +128,46 @@ void effectTask(void* pvParameters) {
127128
static unsigned long last20ms = 0;
128129

129130
while (true) {
130-
if (layerP.lights.useDoubleBuffer) {
131-
xSemaphoreTake(swapMutex, portMAX_DELAY);
132-
bool canProduce = !newFrameReady;
133-
xSemaphoreGive(swapMutex);
131+
if (layerP.lights.header.isPositions == 0) { // driver task can change this
132+
if (layerP.lights.useDoubleBuffer) {
133+
xSemaphoreTake(swapMutex, portMAX_DELAY);
134+
bool canProduce = !newFrameReady;
135+
xSemaphoreGive(swapMutex);
136+
137+
if (canProduce) {
138+
// Copy previous frame (channelsD) to working buffer (channelsE)
139+
memcpy(layerP.lights.channelsE, layerP.lights.channelsD, layerP.lights.header.nrOfChannels);
134140

135-
if (canProduce) {
136-
// effectTask always writes to channelsBack, reads previous channelsBack
137-
layerP.loop(); // getRGB and setRGB both use channelsBack
141+
layerP.loop();
142+
143+
if (millis() - last20ms >= 20) {
144+
last20ms = millis();
145+
layerP.loop20ms();
146+
}
147+
148+
// Atomic swap channels
149+
xSemaphoreTake(swapMutex, portMAX_DELAY);
150+
xSemaphoreTake(monitorMutex, portMAX_DELAY);
151+
uint8_t* temp = layerP.lights.channelsD;
152+
layerP.lights.channelsD = layerP.lights.channelsE;
153+
layerP.lights.channelsE = temp;
154+
newFrameReady = true;
155+
xSemaphoreGive(monitorMutex);
156+
xSemaphoreGive(swapMutex);
157+
}
158+
159+
} else {
160+
// Single buffer mode
161+
xSemaphoreTake(swapMutex, portMAX_DELAY);
162+
layerP.loop();
138163

139164
if (millis() - last20ms >= 20) {
140165
last20ms = millis();
141166
layerP.loop20ms();
142167
}
143168

144-
// Atomic swap channels
145-
xSemaphoreTake(swapMutex, portMAX_DELAY);
146-
uint8_t* temp = layerP.lights.channelsD;
147-
layerP.lights.channelsD = layerP.lights.channelsE;
148-
layerP.lights.channelsE = temp;
149-
newFrameReady = true;
150169
xSemaphoreGive(swapMutex);
151170
}
152-
153-
} else {
154-
// Single buffer mode
155-
xSemaphoreTake(swapMutex, portMAX_DELAY);
156-
layerP.loop();
157-
158-
if (millis() - last20ms >= 20) {
159-
last20ms = millis();
160-
layerP.loop20ms();
161-
}
162-
163-
xSemaphoreGive(swapMutex);
164171
}
165172

166173
vTaskDelay(1); // yield to other tasks, 1 tick (~1ms)
@@ -173,24 +180,30 @@ void driverTask(void* pvParameters) {
173180
// layerP.setup() done in effectTask
174181

175182
while (true) {
176-
esp32sveltekit.lps++;
177-
178-
xSemaphoreTake(swapMutex, portMAX_DELAY);
179-
if (layerP.lights.useDoubleBuffer) {
180-
if (newFrameReady) {
181-
newFrameReady = false;
182-
// Double buffer: release lock, then send
183-
xSemaphoreGive(swapMutex);
184-
layerP.loopDrivers(); // ✅ No lock needed
183+
if (layerP.lights.header.isPositions == 3) {
184+
EXT_LOGD(ML_TAG, "positions done (3 -> 0)");
185+
layerP.lights.header.isPositions = 0; // now driver can show again
186+
}
187+
188+
if (layerP.lights.header.isPositions == 0) {
189+
xSemaphoreTake(swapMutex, portMAX_DELAY);
190+
if (layerP.lights.useDoubleBuffer) {
191+
if (newFrameReady) {
192+
newFrameReady = false;
193+
esp32sveltekit.lps++;
194+
// Double buffer: release lock, then send
195+
xSemaphoreGive(swapMutex);
196+
197+
layerP.loopDrivers(); // ✅ No lock needed
198+
} else {
199+
xSemaphoreGive(swapMutex);
200+
}
185201
} else {
202+
// Single buffer: keep lock while sending
203+
layerP.loopDrivers(); // ✅ Protected by lock
186204
xSemaphoreGive(swapMutex);
187205
}
188-
} else {
189-
// Single buffer: keep lock while sending
190-
layerP.loopDrivers(); // ✅ Protected by lock
191-
xSemaphoreGive(swapMutex);
192206
}
193-
194207
vTaskDelay(1);
195208
}
196209
}
@@ -358,16 +371,14 @@ void setup() {
358371
false);
359372
#endif
360373

361-
swapMutex = xSemaphoreCreateMutex();
362-
363374
// 🌙
364375
xTaskCreateUniversal(effectTask, // task function
365376
"AppEffectTask", // name
366377
psramFound() ? 4 * 1024 : 3 * 1024, // d0-tuning... stack size (without livescripts we can do with 12...). updated from 4 to 6 to support preset loop
367378
NULL, // parameter
368379
10, // priority (between 5 and 10: ASYNC_WORKER_TASK_PRIORITY and Restart/Sleep), don't set it higher then 10...
369380
&effectTaskHandle, // task handle
370-
0 // core (0 or 1)
381+
0 // core
371382
);
372383

373384
xTaskCreateUniversal(driverTask, // task function
@@ -376,7 +387,7 @@ void setup() {
376387
NULL, // parameter
377388
3, // priority (between 5 and 10: ASYNC_WORKER_TASK_PRIORITY and Restart/Sleep), don't set it higher then 10...
378389
&driverTaskHandle, // task handle
379-
1 // core (0 or 1)
390+
1 // core
380391
);
381392
#endif
382393

0 commit comments

Comments
 (0)