@@ -45,18 +45,16 @@ sequenceDiagram
4545 participant EffectTask
4646 participant DriverTask
4747 participant LEDs
48- participant FileSystem
4948
5049 Note over EffectTask,DriverTask: Both tasks synchronized via mutex
5150
5251 User->>WebUI: Adjust effect parameter
5352 WebUI->>SvelteKit: WebSocket message
5453 SvelteKit->>SvelteKit: Update in-memory state
55- SvelteKit->>SvelteKit: Queue deferred write
5654
5755 Note over EffectTask: Core 0 (PRO_CPU)
5856 EffectTask->>EffectTask: Take mutex (10µs)
59- EffectTask->>EffectTask: memcpy front→back buffer
57+ EffectTask->>EffectTask: memcpy channelsD → channelsE
6058 EffectTask->>EffectTask: Release mutex
6159 EffectTask->>EffectTask: Compute effects (5-15ms)
6260 EffectTask->>EffectTask: Take mutex (10µs)
@@ -69,11 +67,6 @@ sequenceDiagram
6967 DriverTask->>DriverTask: Release mutex
7068 DriverTask->>DriverTask: Send via DMA (1-5ms)
7169 DriverTask->>LEDs: Pixel data
72-
73- User->>WebUI: Click "Save Config"
74- WebUI->>SvelteKit: POST /rest/saveConfig
75- SvelteKit->>FileSystem: Execute all deferred writes
76- FileSystem-->>SvelteKit: Write complete (10-50ms)
7770```
7871
7972## Core Assignments
@@ -139,19 +132,19 @@ Buffer Architecture (PSRAM Only)
139132``` mermaid
140133graph LR
141134 subgraph MemoryBuffers["Memory Buffers"]
142- Front[Front Buffer<br/>channels *]
143- Back[Back Buffer<br/>channelsBack *]
135+ Effects[Effects Buffer<br/>channelsE *]
136+ Drivers[Drivers Buffer<br/>channelsD *]
144137 end
145138
146- EffectTask[Effect Task<br/>Core 0] -.->|1. memcpy| Back
147- EffectTask -.->|2. Compute effects| Back
148- EffectTask -.->|3. Swap pointers<br/>MUTEX 10µs| Front
139+ EffectTask[Effect Task<br/>Core 0] -.->|1. memcpy| Drivers
140+ EffectTask -.->|2. Compute effects| Drivers
141+ EffectTask -.->|3. Swap pointers<br/>MUTEX 10µs| Effects
149142
150- DriverTask[Driver Task<br/>Core 1] -->|4. Read pixels| Front
143+ DriverTask[Driver Task<br/>Core 1] -->|4. Read pixels| Effects
151144 DriverTask -->|5. Send via DMA| LEDs[LEDs]
152145
153- style Front fill:#898f89
154- style Back fill:#898c8f
146+ style Effects fill:#898f89
147+ style Drivers fill:#898c8f
155148```
156149
157150Synchronization Flow
@@ -161,14 +154,50 @@ Synchronization Flow
161154
162155void effectTask (void* param) {
163156 while (true) {
164- // tbd ...
165- vTaskDelay(1);
157+ uint8_t isPositions = layerP.lights.header.isPositions;
158+ bool canProduce = !newFrameReady;
159+
160+ if (isPositions == 0) { // driver task can change this
161+ if (layerP.lights.useDoubleBuffer) {
162+
163+ if (canProduce) {
164+ // Copy previous frame (channelsD) to working buffer (channelsE)
165+ memcpy(layerP.lights.channelsE, layerP.lights.channelsD, layerP.lights.header.nrOfChannels);
166+
167+ layerP.loop();
168+
169+ // Atomic swap channels
170+ uint8_t* temp = layerP.lights.channelsD;
171+ layerP.lights.channelsD = layerP.lights.channelsE;
172+ layerP.lights.channelsE = temp;
173+ newFrameReady = true;
174+ }
175+
176+ } else {
177+ // Single buffer mode
178+ layerP.loop();
179+ }
180+ vTaskDelay(1);
166181 }
167182}
168183
169184void driverTask(void* param) {
170185 while (true) {
171- // tbd ...
186+ if (isPositions == 0) {
187+ if (layerP.lights.useDoubleBuffer) {
188+ if (newFrameReady) {
189+ newFrameReady = false;
190+ // Double buffer: release lock, then send
191+
192+ esp32sveltekit.lps++;
193+ layerP.loopDrivers(); // ✅ No lock needed
194+ }
195+ } else {
196+ // Single buffer: keep lock while sending
197+ esp32sveltekit.lps++;
198+ layerP.loopDrivers(); // ✅ Protected by lock
199+ }
200+ }
172201 vTaskDelay(1);
173202 }
174203}
@@ -187,79 +216,6 @@ Performance Impact
187216
188217**Conclusion**: Double buffering overhead is negligible (<1% for typical setups).
189218
190- ## State Persistence & Deferred Writes
191-
192- Why Deferred Writes?
193-
194- Flash write operations (LittleFS) **block all CPU cores** for 10-50ms, causing:
195-
196- - ❌ Dropped frames (2-6 frames at 60fps)
197- - ❌ Visible LED stutter
198- - ❌ Poor user experience during settings changes
199-
200- Solution: Deferred Write Queue
201-
202- ```mermaid
203- sequenceDiagram
204- participant User
205- participant UI
206- participant Module
207- participant WriteQueue
208- participant FileSystem
209-
210- User->>UI: Move slider
211- UI->>Module: Update state (in-memory)
212- Module->>WriteQueue: Queue write operation
213- Note over WriteQueue: Changes accumulate<br/>in memory
214-
215- User->>UI: Move slider again
216- UI->>Module: Update state (in-memory)
217- Note over WriteQueue: Previous write replaced<br/>No flash access yet
218-
219- User->>UI: Click "Save Config"
220- UI->>WriteQueue: Execute all queued writes
221- WriteQueue->>FileSystem: Write all changes (10-50ms)
222- Note over FileSystem: Single flash write<br/>for all changes
223- FileSystem-->>UI: Complete
224- ```
225-
226- Implementation
227-
228- ** When UI updates state:**
229- ``` cpp
230- // File: SharedFSPersistence.h
231- void writeToFS (const String& moduleName) {
232- if (delayedWriting) {
233- // Add to global queue (no flash write yet)
234- sharedDelayedWrites.push_back([ this, module] (char writeOrCancel) {
235- if (writeOrCancel == 'W') {
236- this->writeToFSNow(moduleName); // Actual flash write
237- }
238- });
239- }
240- }
241- ```
242-
243- **When user clicks "Save Config":**
244- ```cpp
245- // File: FileManager.cpp
246- _server->on("/rest/saveConfig", HTTP_POST, [](PsychicRequest* request) {
247- // Execute all queued writes in a single batch
248- FSPersistence<int>::writeToFSDelayed('W');
249- return ESP_OK;
250- });
251- ```
252-
253- Benefits
254-
255- | Aspect | Without Deferred Writes | With Deferred Writes |
256- | --------| -------------------------| ----------------------|
257- | ** Flash writes per slider move** | 1 (10-50ms) | 0 |
258- | ** LED stutter during UI use** | Constant | None |
259- | ** Flash writes per session** | 100+ | 1 |
260- | ** User experience** | Laggy, stuttering | Smooth |
261- | ** Flash wear** | High | Minimal |
262-
263219## Performance Budget at 60fps
264220
265221Per-Frame Time Budget (16.66ms)
@@ -315,15 +271,13 @@ Overhead Analysis
315271| SvelteKit | 0.5-2ms (on Core 1) | 2-3ms (on Core 1) | 5ms |
316272| Double buffer memcpy | 0.1ms (0.6%) | 0.1ms (0.6%) | 0.1ms |
317273| Mutex locks | 0.02ms (0.1%) | 0.02ms (0.1%) | 0.02ms |
318- | Flash writes | ** 0ms** (deferred) | ** 0ms** (deferred) | 10-50ms (on save) |
319274| ** Total** | ** 1-3ms (6-18%)** | ** 4-8ms (24-48%)** | ** Flash: user-triggered** |
320275
321276** Result** :
322277
323278- ✅ 60fps sustained during normal operation
324279- ✅ 52-60fps during heavy WiFi/UI activity
325- - ✅ No stutter during UI interaction (deferred writes)
326- - ✅ Only brief stutter when user explicitly saves config (acceptable)
280+ - ✅ No stutter during UI interaction
327281
328282## Configuration
329283
@@ -389,6 +343,5 @@ This architecture achieves optimal performance through:
3893432 . **Priority Hierarchy**: Driver > SvelteKit ensures LED timing is never compromised
3903443 . **Minimal Locking**: 10 µs mutex locks enable 99 % parallel execution
3913454 . **Double Buffering**: Eliminates tearing with <1 % overhead
392- 5 . **Deferred Writes**: Eliminates UI stutter by batching flash operations
393346
394347**Result**: Smooth 60fps LED effects with responsive UI and stable networking. 🚀
0 commit comments