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