|
71 | 71 | let copyColor = '#000'; |
72 | 72 | let ws = null; |
73 | 73 | let maxCol; // max colors to send out in one chunk, ESP8266 is limited to ~50 (500 bytes), ESP32 can do ~128 (1340 bytes) |
| 74 | + let _applySeq = 0; // incremented each time applyLED fires; used to cancel stale in-flight previews |
| 75 | + let _httpQueue = [], _httpRun = 0; |
74 | 76 |
|
75 | 77 | // load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded |
76 | 78 | (function loadFiles() { |
|
621 | 623 |
|
622 | 624 | async function requestJson(cmd) |
623 | 625 | { |
624 | | - if (ws && ws.readyState == 1) { |
| 626 | + if (ws && ws.readyState == 1 && ws.bufferedAmount < 32768) { |
625 | 627 | try { |
626 | 628 | ws.send(JSON.stringify(cmd)); |
| 629 | + await new Promise(r => setTimeout(r, 15)); // short delay to give ESP time to process (fewer packets dropped) |
627 | 630 | return 1; |
628 | 631 | } catch (e) {} |
629 | 632 | } |
630 | 633 |
|
631 | | - if (!window._httpQueue) { |
632 | | - window._httpQueue = []; |
633 | | - window._httpRun = 0; |
634 | | - } |
635 | | - if (_httpQueue.length >= 5) { |
636 | | - return Promise.resolve(-1); // reject if too many queued requests |
637 | | - } |
638 | | - |
| 634 | + // HTTP fallback |
| 635 | + if (_httpQueue.length >= 5) return -1; // queue full; applyLED cancels stale queues before sending |
639 | 636 | return new Promise(resolve => { |
640 | 637 | _httpQueue.push({ cmd, resolve }); |
641 | 638 | (async function run() { |
|
650 | 647 | cache: 'no-store' |
651 | 648 | }); |
652 | 649 | } catch (e) {} |
653 | | - await new Promise(r => setTimeout(r, 120)); |
| 650 | + await new Promise(r => setTimeout(r, 120)); // delay between requests (go slow, this is the http fallback if WS fails) |
654 | 651 | q.resolve(0); |
655 | 652 | } |
656 | 653 | _httpRun = 0; |
|
662 | 659 | async function applyLED() |
663 | 660 | { |
664 | 661 | if (!palCache.length) return; |
| 662 | + const seq = ++_applySeq; |
| 663 | + // discard pending HTTP chunks from any previous preview so stale data doesn't drain slowly |
| 664 | + while (_httpQueue.length) _httpQueue.shift().resolve(-1); // resolve dropped entries so their awaiters can observe the seq change and exit |
665 | 665 | try { |
666 | 666 | let st = await (await fetch(getURL('/json/state'), { cache: 'no-store' })).json(); |
| 667 | + if (seq !== _applySeq) return; // superseded by a newer preview request |
667 | 668 | if (!st.seg || !st.seg.length) return; |
668 | 669 |
|
669 | 670 | // get selected segments, use main segment if none selected |
|
680 | 681 | arr.push(palCache[len > 1 ? Math.round(i * 255 / (len - 1)) : 0]); |
681 | 682 | // send colors in chunks |
682 | 683 | for (let j = 0; j < arr.length; j += maxCol) { |
| 684 | + if (seq !== _applySeq) return; // superseded mid-send |
683 | 685 | let chunk = [s.start + j, ...arr.slice(j, j + maxCol)]; |
684 | 686 | await requestJson({ seg: { id: s.id, i: chunk } }); |
685 | 687 | } |
|
0 commit comments