@@ -114,7 +114,8 @@ ModuleLiveScripts moduleLiveScripts = ModuleLiveScripts(&server, &esp32sveltekit
114114ModuleChannels moduleChannels = ModuleChannels(&server, &esp32sveltekit);
115115ModuleMoonLightInfo moduleMoonLightInfo = ModuleMoonLightInfo(&server, &esp32sveltekit);
116116
117- volatile xSemaphoreHandle lowLevelSemaphore = xSemaphoreCreateBinary();
117+ SemaphoreHandle_t swapMutex = NULL ;
118+ volatile bool newFrameReady = false ;
118119
119120TaskHandle_t effectTaskHandle = NULL ;
120121TaskHandle_t driverTaskHandle = NULL ;
@@ -123,24 +124,38 @@ void effectTask(void* pvParameters) {
123124 // 🌙
124125
125126 layerP.setup (); // setup virtual layers (no node setup here as done in addNode)
127+ static unsigned long last20ms = 0 ;
126128
127129 for (;;) {
128- esp32sveltekit.lps ++; // 🌙 todo: not moonlight specific?
130+ if (layerP.lights .useDoubleBuffer ) {
131+ // effectTask always writes to channelsBack, reads previous channelsBack
132+ layerP.lights .channels = layerP.lights .channelsBack ;
133+ layerP.loop (); // getRGB and setRGB both use channelsBack
129134
130- if (xSemaphoreTake (lowLevelSemaphore, pdMS_TO_TICKS (100 )) == pdFALSE) {
131- // EXT_LOGW(ML_TAG, "effectSemaphore wait too long"); //happens if no driver!, but let effects continue (for monitor) at 10 fps
132- // EXT_LOGW(ML_TAG, "effect: semaphore wait too long");
133- }
134- else {
135- layerP.loop (); // run all the effects of all virtual layers (currently only one layer)
135+ if (millis () - last20ms >= 20 ) {
136+ last20ms = millis ();
137+ layerP.loop20ms ();
138+ }
139+
140+ // Atomic swap channels
141+ xSemaphoreTake (swapMutex, portMAX_DELAY);
142+ uint8_t * temp = layerP.lights .channelsBack ;
143+ layerP.lights .channelsBack = layerP.lights .channels ;
144+ layerP.lights .channels = temp;
145+ newFrameReady = true ;
146+ xSemaphoreGive (swapMutex);
147+
148+ } else {
149+ // Single buffer mode
150+ xSemaphoreTake (swapMutex, portMAX_DELAY);
151+ layerP.loop ();
136152
137- static unsigned long last20ms = 0 ;
138153 if (millis () - last20ms >= 20 ) {
139154 last20ms = millis ();
140155 layerP.loop20ms ();
141156 }
142157
143- xSemaphoreGive (lowLevelSemaphore );
158+ xSemaphoreGive (swapMutex );
144159 }
145160
146161 vTaskDelay (1 ); // yield to other tasks, 1 tick (~1ms)
@@ -153,15 +168,25 @@ void driverTask(void* pvParameters) {
153168 // layerP.setup() done in effectTask
154169
155170 for (;;) {
156- if (xSemaphoreTake (lowLevelSemaphore, pdMS_TO_TICKS (100 )) == pdFALSE) {
157- // EXT_LOGW(ML_TAG, "driver: semaphore wait too long");
171+ xSemaphoreTake (swapMutex, portMAX_DELAY);
172+ esp32sveltekit.lps ++;
173+
174+ if (layerP.lights .useDoubleBuffer ) {
175+ if (newFrameReady) {
176+ newFrameReady = false ;
177+ // Double buffer: release lock, then send
178+ xSemaphoreGive (swapMutex);
179+ layerP.loopDrivers (); // ✅ No lock needed
180+ } else {
181+ xSemaphoreGive (swapMutex);
182+ }
183+ } else {
184+ // Single buffer: keep lock while sending
185+ layerP.loopDrivers (); // ✅ Protected by lock
186+ xSemaphoreGive (swapMutex);
158187 }
159- else {
160- layerP.loopDrivers ();
161188
162- xSemaphoreGive (lowLevelSemaphore);
163- }
164- vTaskDelay (1 ); // yield to other tasks, 1 tick (~1ms)
189+ vTaskDelay (1 );
165190 }
166191}
167192 #endif
@@ -222,39 +247,39 @@ void setup() {
222247 safeModeMB = true ;
223248 }
224249
225- // // check sizes ...
226- // sizeof(esp32sveltekit); // 4152
227- // sizeof(WiFiSettingsService); // 456
228- // sizeof(SystemStatus); // 16
229- // sizeof(UploadFirmwareService); // 32
230- // sizeof(HttpEndpoint<ModuleState>); // 152
231- // sizeof(EventEndpoint<ModuleState>); // 112
232- // sizeof(SharedEventEndpoint); // 8
233- // sizeof(WebSocketServer<ModuleState>); // 488
234- // sizeof(SharedWebSocketServer); // 352
235- // sizeof(FSPersistence<ModuleState>); // 128
236- // sizeof(PsychicHttpServer*); // 8
237- // sizeof(HttpEndpoint<APSettings>); // 152
238- // sizeof(SharedHttpEndpoint); // 16
239- // sizeof(FSPersistence<APSettings>); // 128
240- // sizeof(APSettingsService); // 600;
241- // sizeof(PsychicWebSocketHandler); // 336
242- // sizeof(fileManager); // 864
243- // sizeof(Module); // 1144 -> 472
244- // sizeof(moduleDevices); // 1320
245- // sizeof(moduleIO); // 1144
246- // #if FT_ENABLED(FT_MOONLIGHT)
247- // sizeof(moduleEffects); // 1208
248- // sizeof(moduleDrivers); // 1216
249- // sizeof(moduleLightsControl); // 1176
250- // #if FT_ENABLED(FT_LIVESCRIPT)
251- // sizeof(moduleLiveScripts); // 1176
252- // #endif
253- // sizeof(moduleChannels); // 1144
254- // sizeof(moduleMoonLightInfo); // 1144
255- // sizeof(layerP.lights); // 56
256- // sizeof(layerP.lights.header); // 40
257- // #endif
250+ // // check sizes ...
251+ // sizeof(esp32sveltekit); // 4152
252+ // sizeof(WiFiSettingsService); // 456
253+ // sizeof(SystemStatus); // 16
254+ // sizeof(UploadFirmwareService); // 32
255+ // sizeof(HttpEndpoint<ModuleState>); // 152
256+ // sizeof(EventEndpoint<ModuleState>); // 112
257+ // sizeof(SharedEventEndpoint); // 8
258+ // sizeof(WebSocketServer<ModuleState>); // 488
259+ // sizeof(SharedWebSocketServer); // 352
260+ // sizeof(FSPersistence<ModuleState>); // 128
261+ // sizeof(PsychicHttpServer*); // 8
262+ // sizeof(HttpEndpoint<APSettings>); // 152
263+ // sizeof(SharedHttpEndpoint); // 16
264+ // sizeof(FSPersistence<APSettings>); // 128
265+ // sizeof(APSettingsService); // 600;
266+ // sizeof(PsychicWebSocketHandler); // 336
267+ // sizeof(fileManager); // 864
268+ // sizeof(Module); // 1144 -> 472
269+ // sizeof(moduleDevices); // 1320
270+ // sizeof(moduleIO); // 1144
271+ // #if FT_ENABLED(FT_MOONLIGHT)
272+ // sizeof(moduleEffects); // 1208
273+ // sizeof(moduleDrivers); // 1216
274+ // sizeof(moduleLightsControl); // 1176
275+ // #if FT_ENABLED(FT_LIVESCRIPT)
276+ // sizeof(moduleLiveScripts); // 1176
277+ // #endif
278+ // sizeof(moduleChannels); // 1144
279+ // sizeof(moduleMoonLightInfo); // 1144
280+ // sizeof(layerP.lights); // 56
281+ // sizeof(layerP.lights.header); // 40
282+ // #endif
258283
259284 // start ESP32-SvelteKit
260285 esp32sveltekit.begin ();
@@ -328,16 +353,16 @@ void setup() {
328353 false );
329354 #endif
330355
331- xSemaphoreGive (lowLevelSemaphore );
356+ swapMutex = xSemaphoreCreateMutex ( );
332357
333358 // 🌙
334359 xTaskCreateUniversal (effectTask, // task function
335360 " AppEffectTask" , // name
336361 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
337362 NULL , // parameter
338- 3 , // priority (between 5 and 10: ASYNC_WORKER_TASK_PRIORITY and Restart/Sleep), don't set it higher then 10...
363+ 10 , // priority (between 5 and 10: ASYNC_WORKER_TASK_PRIORITY and Restart/Sleep), don't set it higher then 10...
339364 &effectTaskHandle, // task handle
340- 1 // core (0 or 1)
365+ 0 // core (0 or 1)
341366 );
342367
343368 xTaskCreateUniversal (driverTask, // task function
0 commit comments