1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 #ifndef JUCE_DUMP_LITTLEFOOT_HEAP_STATUS
23   #define JUCE_DUMP_LITTLEFOOT_HEAP_STATUS 0
24 #endif
25 
26 #if JUCE_DUMP_LITTLEFOOT_HEAP_STATUS
27  #define JUCE_LOG_LITTLEFOOT_HEAP(text) DBG(text)
28 #else
29  #define JUCE_LOG_LITTLEFOOT_HEAP(text)
30 #endif
31 
32 namespace littlefoot
33 {
34 
35 //==============================================================================
36 /**
37     This class manages the synchronisation of a remote block of heap memory used
38     by a littlefoot program running on a block.
39 
40     Data in the block can be changed by calling setByte, setBytes, setBits etc, and
41     these changes will be flushed to the device when sendChanges is called.
42 
43     @tags{Blocks}
44 */
45 template <typename ImplementationClass>
46 struct LittleFootRemoteHeap
47 {
LittleFootRemoteHeapLittleFootRemoteHeap48     LittleFootRemoteHeap (uint32 blockSizeToUse) noexcept  : blockSize (blockSizeToUse)
49     {
50         resetDeviceStateToUnknown();
51     }
52 
resetLittleFootRemoteHeap53     void reset()
54     {
55         JUCE_LOG_LITTLEFOOT_HEAP ("Resetting heap state");
56         clearTargetData();
57         resetDeviceStateToUnknown();
58         lastPacketIndexReceived = 0;
59     }
60 
clearTargetDataLittleFootRemoteHeap61     void clearTargetData() noexcept
62     {
63         JUCE_LOG_LITTLEFOOT_HEAP ("Clearing target heap data");
64         zeromem (targetData, sizeof (targetData));
65         needsSyncing = true;
66         programStateKnown = false;
67     }
68 
resetDeviceStateToUnknownLittleFootRemoteHeap69     void resetDeviceStateToUnknown()
70     {
71         JUCE_LOG_LITTLEFOOT_HEAP ("Resetting device state to unknown");
72         needsSyncing = true;
73         programStateKnown = false;
74         messagesSent.clear();
75         resetDataRangeToUnknown (0, ImplementationClass::maxBlockSize);
76     }
77 
resetDataRangeToUnknownLittleFootRemoteHeap78     void resetDataRangeToUnknown (size_t offset, size_t size) noexcept
79     {
80         auto* latestState = getLatestExpectedDataState();
81 
82         for (size_t i = 0; i < size; ++i)
83             latestState[offset + i] = unknownByte;
84     }
85 
setByteLittleFootRemoteHeap86     void setByte (size_t offset, uint8 value) noexcept
87     {
88         if (offset >= blockSize)
89         {
90             jassertfalse;
91             return;
92         }
93 
94         if (targetData[offset] != value)
95         {
96             targetData[offset] = value;
97             needsSyncing = true;
98 
99             if (offset < programSize)
100                 programStateKnown = false;
101         }
102     }
103 
setBytesLittleFootRemoteHeap104     void setBytes (size_t offset, const uint8* data, size_t num) noexcept
105     {
106         for (size_t i = 0; i < num; ++i)
107             setByte (offset + i, data[i]);
108     }
109 
setBitsLittleFootRemoteHeap110     void setBits (uint32 startBit, uint32 numBits, uint32 value) noexcept
111     {
112         if (startBit + numBits > 8 * blockSize)
113         {
114             jassertfalse;
115             return;
116         }
117 
118         if (readLittleEndianBitsInBuffer (targetData, startBit, numBits) != value)
119         {
120             JUCE_LOG_LITTLEFOOT_HEAP ("Set bits sync " << String (startBit) << " " << String (numBits) << String (value));
121 
122             writeLittleEndianBitsInBuffer (targetData, startBit, numBits, value);
123 
124             needsSyncing = true;
125 
126             if (startBit < programSize)
127                 programStateKnown = false;
128         }
129     }
130 
getByteLittleFootRemoteHeap131     uint8 getByte (size_t offset) noexcept
132     {
133         if (offset < blockSize)
134             return targetData [offset];
135 
136         jassertfalse;
137         return 0;
138     }
139 
isFullySyncedLittleFootRemoteHeap140     bool isFullySynced() const noexcept
141     {
142         return ! needsSyncing;
143     }
144 
isAllZeroLittleFootRemoteHeap145     static bool isAllZero (const uint8* data, size_t size) noexcept
146     {
147         for (size_t i = 0; i < size; ++i)
148             if (data[i] != 0)
149                 return false;
150 
151         return true;
152     }
153 
sendChangesLittleFootRemoteHeap154     void sendChanges (ImplementationClass& bi, bool forceSend)
155     {
156         if ((needsSyncing && messagesSent.isEmpty()) || forceSend)
157         {
158             for (int maxChanges = 30; --maxChanges >= 0;)
159             {
160                 if (isAllZero (targetData, blockSize))
161                     break;
162 
163                 uint16 data[ImplementationClass::maxBlockSize];
164                 auto* latestState = getLatestExpectedDataState();
165 
166                 for (uint32 i = 0; i < blockSize; ++i)
167                     data[i] = latestState[i];
168 
169                 uint32 packetIndex = messagesSent.isEmpty() ? lastPacketIndexReceived
170                                                             : messagesSent.getLast()->packetIndex;
171 
172                 packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter;
173 
174                 if (! Diff (data, targetData, blockSize).createChangeMessage (bi, data, messagesSent, packetIndex))
175                     break;
176 
177                 dumpStatus();
178             }
179         }
180 
181         for (auto* m : messagesSent)
182         {
183             if (m->dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250))
184                 break;
185 
186             m->dispatchTime = Time::getCurrentTime();
187             bi.sendMessageToDevice (m->packet);
188 
189             JUCE_LOG_LITTLEFOOT_HEAP ("Sending packet " << (int) m->packetIndex << " - " << m->packet.size() << " bytes, device " << bi.getDeviceIndex());
190 
191             if (getTotalSizeOfMessagesSent() > 200)
192                 break;
193         }
194     }
195 
handleACKFromDeviceLittleFootRemoteHeap196     void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept
197     {
198         if (packetIndex == lastPacketIndexReceived)
199             return;
200 
201         JUCE_LOG_LITTLEFOOT_HEAP ("ACK " << (int) packetIndex << "   device " << (int) bi.getDeviceIndex()
202                                   << ", last packet received " << String (lastPacketIndexReceived));
203 
204         lastPacketIndexReceived = packetIndex;
205 
206         for (int i = messagesSent.size(); --i >= 0;)
207         {
208             auto& m = *messagesSent.getUnchecked(i);
209 
210             if (m.packetIndex == packetIndex)
211             {
212                 for (uint32 j = 0; j < blockSize; ++j)
213                     deviceState[j] = m.resultDataState[j];
214 
215                 programStateKnown = false;
216                 messagesSent.removeRange (0, i + 1);
217                 dumpStatus();
218                 sendChanges (bi, false);
219 
220                 if (messagesSent.isEmpty())
221                 {
222                     JUCE_LOG_LITTLEFOOT_HEAP ("Heap fully synced");
223                     needsSyncing = false;
224                 }
225 
226                 return;
227             }
228         }
229 
230         resetDeviceStateToUnknown();
231     }
232 
isProgramLoadedLittleFootRemoteHeap233     bool isProgramLoaded() noexcept
234     {
235         if (! programStateKnown)
236         {
237             uint8 deviceMemory[ImplementationClass::maxBlockSize];
238 
239             for (size_t i = 0; i < blockSize; ++i)
240                 deviceMemory[i] = (uint8) deviceState[i];
241 
242             littlefoot::Program prog (deviceMemory, (uint32) blockSize);
243             programLoaded = prog.checksumMatches();
244             programSize = prog.getProgramSize();
245 
246             programStateKnown = true;
247         }
248 
249         return programLoaded;
250     }
251 
252     const size_t blockSize;
253 
254     static constexpr uint16 unknownByte = 0x100;
255 
256 private:
257     uint16 deviceState[ImplementationClass::maxBlockSize] = { 0 };
258     uint8 targetData[ImplementationClass::maxBlockSize] = { 0 };
259     uint32 programSize = 0;
260     bool needsSyncing = true, programStateKnown = true, programLoaded = false;
261 
getLatestExpectedDataStateLittleFootRemoteHeap262     uint16* getLatestExpectedDataState() noexcept
263     {
264         return messagesSent.isEmpty() ? deviceState
265                                       : messagesSent.getLast()->resultDataState;
266     }
267 
268     struct ChangeMessage
269     {
270         typename ImplementationClass::PacketBuilder packet;
271         Time dispatchTime;
272         uint32 packetIndex;
273         uint16 resultDataState[ImplementationClass::maxBlockSize];
274     };
275 
276     OwnedArray<ChangeMessage> messagesSent;
277     uint32 lastPacketIndexReceived = 0;
278 
getTotalSizeOfMessagesSentLittleFootRemoteHeap279     int getTotalSizeOfMessagesSent() const noexcept
280     {
281         int total = 0;
282 
283         for (auto* m : messagesSent)
284             if (m->dispatchTime != Time())
285                 total += m->packet.size();
286 
287         return total;
288     }
289 
dumpStatusLittleFootRemoteHeap290     void dumpStatus()
291     {
292        #if JUCE_DUMP_LITTLEFOOT_HEAP_STATUS
293         int differences = 0;
294         constexpr int diffLen = 50;
295         char areas[diffLen + 1] = { 0 };
296 
297         for (int i = 0; i < diffLen; ++i)
298             areas[i] = '.';
299 
300         for (int i = 0; i < (int) blockSize; ++i)
301         {
302             if (targetData[i] != deviceState[i])
303             {
304                 ++differences;
305                 areas[i * diffLen / (int) blockSize] = 'X';
306             }
307         }
308 
309         double proportionOK = ((int) blockSize - differences) / (double) blockSize;
310 
311         ignoreUnused (proportionOK);
312 
313         JUCE_LOG_LITTLEFOOT_HEAP ("Heap: " << areas << "  " << String (roundToInt (100 * proportionOK))
314                                      << "%  " << (isProgramLoaded() ? "Ready" : "Loading"));
315        #endif
316     }
317 
318     struct Diff
319     {
DiffLittleFootRemoteHeap::Diff320         Diff (uint16* current, const uint8* target, size_t blockSizeToUse)
321             : newData (target), blockSize (blockSizeToUse)
322         {
323             ranges.ensureStorageAllocated ((int) blockSize);
324 
325             for (int i = 0; i < (int) blockSize; ++i)
326                 ranges.add ({ i, 1, newData[i] == current[i], false });
327 
328             coalesceUniformRegions();
329             coalesceSequences();
330             trim();
331         }
332 
createChangeMessageLittleFootRemoteHeap::Diff333         bool createChangeMessage (const ImplementationClass& bi,
334                                   const uint16* currentState,
335                                   OwnedArray<ChangeMessage>& messagesCreated,
336                                   uint32 nextPacketIndex)
337         {
338             if (ranges.isEmpty())
339                 return false;
340 
341             auto deviceIndex = bi.getDeviceIndex();
342 
343             if (deviceIndex < 0)
344                 return false;
345 
346             auto& message = *messagesCreated.add (new ChangeMessage());
347 
348             message.packetIndex = nextPacketIndex;
349 
350             for (uint32 i = 0; i < blockSize; ++i)
351                 message.resultDataState[i] = currentState[i];
352 
353             auto& p = message.packet;
354             p.writePacketSysexHeaderBytes ((uint8) deviceIndex);
355             p.beginDataChanges (nextPacketIndex);
356 
357             uint8 lastValue = 0;
358             bool packetOverflow = false;
359 
360             for (auto& r : ranges)
361             {
362                 if (r.isSkipped)
363                 {
364                     packetOverflow = ! p.skipBytes (r.length);
365                 }
366                 else if (r.isMixed)
367                 {
368                     jassert (r.length > 1);
369                     packetOverflow = ! p.setMultipleBytes (newData + r.index, r.length);
370 
371                     if (! packetOverflow)
372                         lastValue = newData[r.index + r.length - 1];
373                 }
374                 else
375                 {
376                     auto value = newData[r.index];
377                     packetOverflow = ! p.setMultipleBytes (value, lastValue, r.length);
378 
379                     if (! packetOverflow)
380                         lastValue = value;
381                 }
382 
383                 if (packetOverflow)
384                     break;
385 
386                 if (! r.isSkipped)
387                     for (int i = r.index; i < r.index + r.length; ++i)
388                         message.resultDataState[i] = newData[i];
389             }
390 
391             p.endDataChanges (! packetOverflow);
392             p.writePacketSysexFooter();
393 
394             return packetOverflow;
395         }
396 
397     private:
398         struct ByteSequence
399         {
400             int index, length;
401             bool isSkipped, isMixed;
402         };
403 
404         const uint8* const newData;
405         const size_t blockSize;
406         Array<ByteSequence> ranges;
407 
coalesceUniformRegionsLittleFootRemoteHeap::Diff408         void coalesceUniformRegions()
409         {
410             for (int i = ranges.size(); --i > 0;)
411             {
412                 auto& r1 = ranges.getReference (i - 1);
413                 auto r2 = ranges.getReference (i);
414 
415                 if (r1.isSkipped == r2.isSkipped
416                      && (r1.isSkipped || newData[r1.index] == newData[r2.index]))
417                 {
418                     r1.length += r2.length;
419                     ranges.remove (i);
420                     i = jmin (ranges.size() - 1, i + 1);
421                 }
422             }
423         }
424 
coalesceSequencesLittleFootRemoteHeap::Diff425         void coalesceSequences()
426         {
427             for (int i = ranges.size(); --i > 0;)
428             {
429                 auto& r1 = ranges.getReference (i - 1);
430                 auto r2 = ranges.getReference (i);
431 
432                 if (! (r1.isSkipped || r2.isSkipped)
433                      && (r1.isMixed || r1.length == 1)
434                      && (r2.isMixed || r2.length == 1))
435                 {
436                     if (r1.length + r2.length < 32)
437                     {
438                         r1.length += r2.length;
439                         r1.isMixed = true;
440                         ranges.remove (i);
441                         i = jmin (ranges.size() - 1, i + 1);
442                     }
443                 }
444             }
445         }
446 
trimLittleFootRemoteHeap::Diff447         void trim()
448         {
449             while (ranges.size() > 0 && ranges.getLast().isSkipped)
450                 ranges.removeLast();
451         }
452     };
453 };
454 
455 }
456