The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

798 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. namespace juce
  18. {
  19. //==============================================================================
  20. /** This is the main singleton object that keeps track of connected blocks */
  21. struct Detector : public ReferenceCountedObject,
  22. private Timer,
  23. private AsyncUpdater
  24. {
  25. using BlockImpl = BlockImplementation<Detector>;
  26. Detector() : defaultDetector (new MIDIDeviceDetector()), deviceDetector (*defaultDetector)
  27. {
  28. startTimer (10);
  29. }
  30. Detector (PhysicalTopologySource::DeviceDetector& dd) : deviceDetector (dd)
  31. {
  32. startTimer (10);
  33. }
  34. ~Detector() override
  35. {
  36. jassert (activeTopologySources.isEmpty());
  37. }
  38. using Ptr = ReferenceCountedObjectPtr<Detector>;
  39. static Detector::Ptr getDefaultDetector()
  40. {
  41. auto& d = getDefaultDetectorPointer();
  42. if (d == nullptr)
  43. d = new Detector();
  44. return d;
  45. }
  46. static Detector::Ptr& getDefaultDetectorPointer()
  47. {
  48. static Detector::Ptr defaultDetector;
  49. return defaultDetector;
  50. }
  51. void detach (PhysicalTopologySource* pts)
  52. {
  53. activeTopologySources.removeAllInstancesOf (pts);
  54. if (activeTopologySources.isEmpty())
  55. {
  56. for (auto& b : currentTopology.blocks)
  57. if (auto bi = BlockImpl::getFrom (b))
  58. bi->sendCommandMessage (BlocksProtocol::endAPIMode);
  59. currentTopology = {};
  60. auto& d = getDefaultDetectorPointer();
  61. if (d != nullptr && d->getReferenceCount() == 2)
  62. getDefaultDetectorPointer() = nullptr;
  63. }
  64. }
  65. bool isConnected (Block::UID deviceID) const noexcept
  66. {
  67. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
  68. for (auto&& b : currentTopology.blocks)
  69. if (b->uid == deviceID)
  70. return true;
  71. return false;
  72. }
  73. bool isConnectedViaBluetooth (const Block& block) const noexcept
  74. {
  75. if (const auto connection = getDeviceConnectionFor (block))
  76. if (const auto midiConnection = dynamic_cast<const MIDIDeviceConnection*> (connection))
  77. if (midiConnection->midiInput != nullptr)
  78. return midiConnection->midiInput->getName().containsIgnoreCase ("bluetooth");
  79. return false;
  80. }
  81. void handleDeviceAdded (const DeviceInfo& info)
  82. {
  83. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  84. const auto blockWasRemoved = containsBlockWithUID (blocksToRemove, info.uid);
  85. const auto knownBlock = std::find_if (previouslySeenBlocks.begin(), previouslySeenBlocks.end(),
  86. [uid = info.uid] (Block::Ptr block) { return uid == block->uid; });
  87. Block::Ptr block;
  88. if (knownBlock != previouslySeenBlocks.end())
  89. {
  90. block = *knownBlock;
  91. if (auto* blockImpl = BlockImpl::getFrom (*block))
  92. {
  93. blockImpl->markReconnected (info);
  94. previouslySeenBlocks.removeObject (block);
  95. }
  96. }
  97. else
  98. {
  99. block = new BlockImpl (*this, info);
  100. }
  101. currentTopology.blocks.addIfNotAlreadyThere (block);
  102. if (blockWasRemoved)
  103. {
  104. blocksToUpdate.addIfNotAlreadyThere (block);
  105. blocksToAdd.removeObject (block);
  106. }
  107. else
  108. {
  109. blocksToAdd.addIfNotAlreadyThere (block);
  110. blocksToUpdate.removeObject (block);
  111. }
  112. blocksToRemove.removeObject (block);
  113. triggerAsyncUpdate();
  114. }
  115. void handleDeviceRemoved (const DeviceInfo& info)
  116. {
  117. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  118. const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(),
  119. [uid = info.uid] (Block::Ptr block) { return uid == block->uid; });
  120. if (blockIt != currentTopology.blocks.end())
  121. {
  122. const Block::Ptr block { *blockIt };
  123. if (auto blockImpl = BlockImpl::getFrom (block.get()))
  124. blockImpl->markDisconnected();
  125. currentTopology.blocks.removeObject (block);
  126. previouslySeenBlocks.addIfNotAlreadyThere (block);
  127. blocksToRemove.addIfNotAlreadyThere (block);
  128. blocksToUpdate.removeObject (block);
  129. blocksToAdd.removeObject (block);
  130. triggerAsyncUpdate();
  131. }
  132. }
  133. void handleConnectionsChanged()
  134. {
  135. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  136. triggerAsyncUpdate();
  137. }
  138. void handleDevicesUpdated (const Array<DeviceInfo>& infos)
  139. {
  140. bool shouldTriggerUpdate { false };
  141. for (auto& info : infos)
  142. {
  143. if (containsBlockWithUID (blocksToRemove, info.uid))
  144. continue;
  145. const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(),
  146. [uid = info.uid] (Block::Ptr block) { return uid == block->uid; });
  147. if (blockIt != currentTopology.blocks.end())
  148. {
  149. const Block::Ptr block { *blockIt };
  150. if (auto blockImpl = BlockImpl::getFrom (block.get()))
  151. blockImpl->updateDeviceInfo (info);
  152. if (! containsBlockWithUID (blocksToAdd, info.uid))
  153. {
  154. blocksToUpdate.addIfNotAlreadyThere (block);
  155. shouldTriggerUpdate = true;
  156. }
  157. }
  158. }
  159. if (shouldTriggerUpdate)
  160. triggerAsyncUpdate();
  161. }
  162. void handleDeviceUpdated (const DeviceInfo& info)
  163. {
  164. handleDevicesUpdated ({ info });
  165. }
  166. void handleBatteryChargingChanged (Block::UID deviceID, const BlocksProtocol::BatteryCharging isCharging)
  167. {
  168. if (auto block = currentTopology.getBlockWithUID (deviceID))
  169. if (auto blockImpl = BlockImpl::getFrom (*block))
  170. blockImpl->batteryCharging = isCharging;
  171. }
  172. void handleBatteryLevelChanged (Block::UID deviceID, const BlocksProtocol::BatteryLevel batteryLevel)
  173. {
  174. if (auto block = currentTopology.getBlockWithUID (deviceID))
  175. if (auto blockImpl = BlockImpl::getFrom (*block))
  176. blockImpl->batteryLevel = batteryLevel;
  177. }
  178. void handleIndexChanged (Block::UID deviceID, const BlocksProtocol::TopologyIndex index)
  179. {
  180. if (auto block = currentTopology.getBlockWithUID (deviceID))
  181. if (auto blockImpl = BlockImpl::getFrom (*block))
  182. blockImpl->topologyIndex = index;
  183. }
  184. void notifyBlockIsRestarting (Block::UID deviceID)
  185. {
  186. for (auto& group : connectedDeviceGroups)
  187. group->handleBlockRestarting (deviceID);
  188. }
  189. Array<Block::UID> getDnaDependentDeviceUIDs (Block::UID uid)
  190. {
  191. JUCE_ASSERT_MESSAGE_THREAD
  192. Array<Block::UID> dependentDeviceUIDs;
  193. if (auto block = getBlockImplementationWithUID (uid))
  194. {
  195. if (auto master = getBlockImplementationWithUID (block->masterUID))
  196. {
  197. auto graph = BlockGraph (currentTopology, [uid] (Block::Ptr b) { return b->uid != uid; });
  198. const auto pathWithoutBlock = graph.getTraversalPathFromMaster (master);
  199. for (const auto b : currentTopology.blocks)
  200. {
  201. if (b->uid != uid && ! pathWithoutBlock.contains (b))
  202. {
  203. TOPOLOGY_LOG ( "Dependent device: " + b->name);
  204. dependentDeviceUIDs.add (b->uid);
  205. }
  206. }
  207. }
  208. }
  209. return dependentDeviceUIDs;
  210. }
  211. void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const
  212. {
  213. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  214. if (auto* bi = getBlockImplementationWithUID (deviceID))
  215. bi->handleSharedDataACK (packetCounter);
  216. }
  217. void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode, uint32 resultDetail)
  218. {
  219. if (auto* bi = getBlockImplementationWithUID (deviceID))
  220. bi->handleFirmwareUpdateACK (resultCode, resultDetail);
  221. }
  222. void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max)
  223. {
  224. if (auto* bi = getBlockImplementationWithUID (deviceID))
  225. bi->handleConfigUpdateMessage (item, value, min, max);
  226. }
  227. void notifyBlockOfConfigChange (BlockImpl& bi, uint32 item)
  228. {
  229. if (item >= bi.getMaxConfigIndex())
  230. bi.handleConfigItemChanged ({ item }, item);
  231. else
  232. bi.handleConfigItemChanged (bi.getLocalConfigMetaData (item), item);
  233. }
  234. void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value)
  235. {
  236. if (auto* bi = getBlockImplementationWithUID (deviceID))
  237. {
  238. bi->handleConfigSetMessage (item, value);
  239. notifyBlockOfConfigChange (*bi, uint32 (item));
  240. }
  241. }
  242. void handleConfigFactorySyncEndMessage (Block::UID deviceID)
  243. {
  244. if (auto* bi = getBlockImplementationWithUID (deviceID))
  245. bi->handleConfigSyncEnded();
  246. }
  247. void handleConfigFactorySyncResetMessage (Block::UID deviceID)
  248. {
  249. if (auto* bi = getBlockImplementationWithUID (deviceID))
  250. bi->resetConfigListActiveStatus();
  251. }
  252. void handleLogMessage (Block::UID deviceID, const String& message) const
  253. {
  254. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  255. if (auto* bi = getBlockImplementationWithUID (deviceID))
  256. bi->handleLogMessage (message);
  257. }
  258. void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const
  259. {
  260. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  261. if (auto* bi = getBlockImplementationWithUID (deviceID))
  262. {
  263. bi->pingFromDevice();
  264. if (isPositiveAndBelow (buttonIndex, bi->getButtons().size()))
  265. if (auto* cbi = dynamic_cast<BlockImpl::ControlButtonImplementation*> (bi->getButtons().getUnchecked (int (buttonIndex))))
  266. cbi->broadcastButtonChange (timestamp, bi->modelData.buttons[(int) buttonIndex].type, isDown);
  267. }
  268. }
  269. void handleTouchChange (Block::UID deviceID, const TouchSurface::Touch& touchEvent)
  270. {
  271. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  272. auto block = currentTopology.getBlockWithUID (deviceID);
  273. if (block != nullptr)
  274. {
  275. if (auto* surface = dynamic_cast<BlockImpl::TouchSurfaceImplementation*> (block->getTouchSurface()))
  276. {
  277. TouchSurface::Touch scaledEvent (touchEvent);
  278. scaledEvent.x *= (float) block->getWidth();
  279. scaledEvent.y *= (float) block->getHeight();
  280. scaledEvent.startX *= (float) block->getWidth();
  281. scaledEvent.startY *= (float) block->getHeight();
  282. surface->broadcastTouchChange (scaledEvent);
  283. }
  284. }
  285. }
  286. void cancelAllActiveTouches() noexcept
  287. {
  288. for (auto& block : currentTopology.blocks)
  289. if (auto* surface = block->getTouchSurface())
  290. surface->cancelAllActiveTouches();
  291. }
  292. void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data)
  293. {
  294. if (auto* bi = getBlockImplementationWithUID (deviceID))
  295. bi->handleCustomMessage (timestamp, data);
  296. }
  297. //==============================================================================
  298. template <typename PacketBuilder>
  299. bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const
  300. {
  301. for (auto* c : connectedDeviceGroups)
  302. if (c->contains (deviceID))
  303. return c->sendMessageToDevice (builder);
  304. return false;
  305. }
  306. static Detector* getFrom (Block& b) noexcept
  307. {
  308. if (auto* bi = BlockImpl::getFrom (b))
  309. return (bi->detector);
  310. jassertfalse;
  311. return nullptr;
  312. }
  313. PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b)
  314. {
  315. for (const auto& d : connectedDeviceGroups)
  316. {
  317. if (d->contains (b.uid))
  318. return d->getDeviceConnection();
  319. }
  320. return nullptr;
  321. }
  322. const PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b) const
  323. {
  324. for (const auto& d : connectedDeviceGroups)
  325. {
  326. if (d->contains (b.uid))
  327. return d->getDeviceConnection();
  328. }
  329. return nullptr;
  330. }
  331. std::unique_ptr<MIDIDeviceDetector> defaultDetector;
  332. PhysicalTopologySource::DeviceDetector& deviceDetector;
  333. Array<PhysicalTopologySource*> activeTopologySources;
  334. BlockTopology currentTopology;
  335. private:
  336. Block::Array previouslySeenBlocks, blocksToAdd, blocksToRemove, blocksToUpdate;
  337. void timerCallback() override
  338. {
  339. startTimer (1500);
  340. auto detectedDevices = deviceDetector.scanForDevices();
  341. handleDevicesRemoved (detectedDevices);
  342. handleDevicesAdded (detectedDevices);
  343. }
  344. bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid)
  345. {
  346. for (const auto block : blocks)
  347. if (block->uid == uid)
  348. return true;
  349. return false;
  350. }
  351. void handleDevicesRemoved (const StringArray& detectedDevices)
  352. {
  353. for (int i = connectedDeviceGroups.size(); --i >= 0;)
  354. if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices))
  355. connectedDeviceGroups.remove (i);
  356. }
  357. void handleDevicesAdded (const StringArray& detectedDevices)
  358. {
  359. for (const auto& devName : detectedDevices)
  360. {
  361. if (! hasDeviceFor (devName))
  362. {
  363. if (auto d = deviceDetector.openDevice (detectedDevices.indexOf (devName)))
  364. {
  365. connectedDeviceGroups.add (new ConnectedDeviceGroup<Detector> (*this, devName, d));
  366. }
  367. }
  368. }
  369. }
  370. bool hasDeviceFor (const String& devName) const
  371. {
  372. for (auto d : connectedDeviceGroups)
  373. if (d->deviceName == devName)
  374. return true;
  375. return false;
  376. }
  377. BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept
  378. {
  379. if (auto block = currentTopology.getBlockWithUID (deviceID))
  380. return BlockImpl::getFrom (*block);
  381. return nullptr;
  382. }
  383. OwnedArray<ConnectedDeviceGroup<Detector>> connectedDeviceGroups;
  384. //==============================================================================
  385. /** This is a friend of the BlocksImplementation that will scan and set the
  386. physical positions of the blocks.
  387. Returns an array of blocks that were updated.
  388. */
  389. struct BlocksLayoutTraverser
  390. {
  391. static Block::Array updateBlocks (const BlockTopology& topology)
  392. {
  393. Block::Array updated;
  394. Array<Block::UID> visited;
  395. for (auto& block : topology.blocks)
  396. {
  397. if (block->isMasterBlock() && ! visited.contains (block->uid))
  398. {
  399. if (auto* bi = BlockImpl::getFrom (block))
  400. {
  401. if (bi->rotation != 0 || bi->position.first != 0 || bi->position.second != 0)
  402. {
  403. bi->rotation = 0;
  404. bi->position = {};
  405. updated.add (block);
  406. }
  407. }
  408. layoutNeighbours (*block, topology, visited, updated);
  409. }
  410. }
  411. return updated;
  412. }
  413. private:
  414. // returns the distance from corner clockwise
  415. static int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index)
  416. {
  417. if (block->getType() == Block::seaboardBlock)
  418. {
  419. if (edge == Block::ConnectionPort::DeviceEdge::north)
  420. {
  421. if (index == 0) return 1;
  422. if (index == 1) return 4;
  423. }
  424. else if (edge != Block::ConnectionPort::DeviceEdge::south)
  425. {
  426. return 1;
  427. }
  428. }
  429. else if (block->getType() == Block::lumiKeysBlock)
  430. {
  431. if (edge == Block::ConnectionPort::DeviceEdge::north)
  432. {
  433. switch (index)
  434. {
  435. case 0 : return 0;
  436. case 1 : return 2;
  437. case 2 : return 3;
  438. case 3 : return 5;
  439. default : jassertfalse;
  440. }
  441. }
  442. else if (edge == Block::ConnectionPort::DeviceEdge::south)
  443. {
  444. jassertfalse;
  445. }
  446. }
  447. if (edge == Block::ConnectionPort::DeviceEdge::south)
  448. return block->getWidth() - (index + 1);
  449. if (edge == Block::ConnectionPort::DeviceEdge::west)
  450. return block->getHeight() - (index + 1);
  451. return index;
  452. }
  453. // returns how often north needs to rotate by 90 degrees
  454. static int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge)
  455. {
  456. switch (edge)
  457. {
  458. case Block::ConnectionPort::DeviceEdge::north: return 0;
  459. case Block::ConnectionPort::DeviceEdge::east: return 1;
  460. case Block::ConnectionPort::DeviceEdge::south: return 2;
  461. case Block::ConnectionPort::DeviceEdge::west: return 3;
  462. default: break;
  463. }
  464. jassertfalse;
  465. return 0;
  466. }
  467. static void layoutNeighbours (const Block::Ptr block,
  468. const BlockTopology& topology,
  469. Array<Block::UID>& visited,
  470. Block::Array& updated)
  471. {
  472. visited.add (block->uid);
  473. for (auto& connection : topology.connections)
  474. {
  475. if ((connection.device1 == block->uid && ! visited.contains (connection.device2))
  476. || (connection.device2 == block->uid && ! visited.contains (connection.device1)))
  477. {
  478. const auto theirUid = connection.device1 == block->uid ? connection.device2 : connection.device1;
  479. const auto neighbourPtr = topology.getBlockWithUID (theirUid);
  480. if (auto* neighbour = dynamic_cast<BlockImpl*> (neighbourPtr.get()))
  481. {
  482. const auto myBounds = block->getBlockAreaWithinLayout();
  483. const auto& myPort = connection.device1 == block->uid ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
  484. const auto& theirPort = connection.device1 == block->uid ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
  485. const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index);
  486. const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index);
  487. {
  488. const auto neighbourRotation = (2 + block->getRotation()
  489. + getRotationForEdge (myPort.edge)
  490. - getRotationForEdge (theirPort.edge)) % 4;
  491. if (neighbour->rotation != neighbourRotation)
  492. {
  493. neighbour->rotation = neighbourRotation;
  494. updated.addIfNotAlreadyThere (neighbourPtr);
  495. }
  496. }
  497. std::pair<int, int> delta;
  498. const auto theirBounds = neighbour->getBlockAreaWithinLayout();
  499. switch ((block->getRotation() + getRotationForEdge (myPort.edge)) % 4)
  500. {
  501. case 0: // over me
  502. delta = { myOffset - (theirBounds.width - (theirOffset + 1)), -theirBounds.height };
  503. break;
  504. case 1: // right of me
  505. delta = { myBounds.width, myOffset - (theirBounds.height - (theirOffset + 1)) };
  506. break;
  507. case 2: // under me
  508. delta = { (myBounds.width - (myOffset + 1)) - theirOffset, myBounds.height };
  509. break;
  510. case 3: // left of me
  511. delta = { -theirBounds.width, (myBounds.height - (myOffset + 1)) - theirOffset };
  512. break;
  513. default:
  514. break;
  515. }
  516. {
  517. const auto neighbourX = myBounds.x + delta.first;
  518. const auto neighbourY = myBounds.y + delta.second;
  519. if (neighbour->position.first != neighbourX
  520. || neighbour->position.second != neighbourY)
  521. {
  522. neighbour->position.first = neighbourX;
  523. neighbour->position.second = neighbourY;
  524. updated.addIfNotAlreadyThere (neighbourPtr);
  525. }
  526. }
  527. layoutNeighbours (neighbourPtr, topology, visited, updated);
  528. }
  529. }
  530. }
  531. }
  532. };
  533. //==============================================================================
  534. #if DUMP_TOPOLOGY
  535. static String idToSerialNum (const BlockTopology& topology, Block::UID uid)
  536. {
  537. for (auto* b : topology.blocks)
  538. if (b->uid == uid)
  539. return b->serialNumber;
  540. return "???";
  541. }
  542. static String portEdgeToString (Block::ConnectionPort port)
  543. {
  544. switch (port.edge)
  545. {
  546. case Block::ConnectionPort::DeviceEdge::north: return "north";
  547. case Block::ConnectionPort::DeviceEdge::south: return "south";
  548. case Block::ConnectionPort::DeviceEdge::east: return "east";
  549. case Block::ConnectionPort::DeviceEdge::west: return "west";
  550. default: break;
  551. }
  552. return {};
  553. }
  554. static String portToString (Block::ConnectionPort port)
  555. {
  556. return portEdgeToString (port) + "_" + String (port.index);
  557. }
  558. static void dumpTopology (const BlockTopology& topology)
  559. {
  560. MemoryOutputStream m;
  561. m << "=============================================================================" << newLine
  562. << "Topology: " << topology.blocks.size() << " device(s)" << newLine
  563. << newLine;
  564. int index = 0;
  565. for (auto block : topology.blocks)
  566. {
  567. m << "Device " << index++ << (block->isMasterBlock() ? ": (MASTER)" : ":") << newLine;
  568. m << " Description: " << block->getDeviceDescription() << newLine
  569. << " Serial: " << block->serialNumber << newLine;
  570. if (auto bi = BlockImplementation<Detector>::getFrom (*block))
  571. m << " Short address: " << (int) bi->getDeviceIndex() << newLine;
  572. m << " Battery level: " + String (roundToInt (100.0f * block->getBatteryLevel())) + "%" << newLine
  573. << " Battery charging: " + String (block->isBatteryCharging() ? "y" : "n") << newLine
  574. << " Width: " << block->getWidth() << newLine
  575. << " Height: " << block->getHeight() << newLine
  576. << " Millimeters per unit: " << block->getMillimetersPerUnit() << newLine
  577. << newLine;
  578. }
  579. for (auto& connection : topology.connections)
  580. {
  581. m << idToSerialNum (topology, connection.device1)
  582. << ":" << portToString (connection.connectionPortOnDevice1)
  583. << " <-> "
  584. << idToSerialNum (topology, connection.device2)
  585. << ":" << portToString (connection.connectionPortOnDevice2) << newLine;
  586. }
  587. m << "=============================================================================" << newLine;
  588. Logger::outputDebugString (m.toString());
  589. }
  590. #endif
  591. //==============================================================================
  592. void updateBlockPositions()
  593. {
  594. const auto updated = BlocksLayoutTraverser::updateBlocks (currentTopology);
  595. for (const auto block : updated)
  596. {
  597. if (containsBlockWithUID (blocksToAdd, block->uid) || containsBlockWithUID (blocksToRemove, block->uid))
  598. continue;
  599. blocksToUpdate.addIfNotAlreadyThere (block);
  600. }
  601. }
  602. void updateBlockConnections()
  603. {
  604. currentTopology.connections.clearQuick();
  605. for (auto d : connectedDeviceGroups)
  606. currentTopology.connections.addArray (d->getCurrentDeviceConnections());
  607. }
  608. void handleAsyncUpdate() override
  609. {
  610. updateBlockConnections();
  611. updateBlockPositions();
  612. for (auto* d : activeTopologySources)
  613. {
  614. for (const auto block : blocksToAdd)
  615. d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockAdded (block); });
  616. for (const auto block : blocksToRemove)
  617. d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockRemoved (block); });
  618. for (const auto block : blocksToUpdate)
  619. d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockUpdated (block); });
  620. }
  621. const auto topologyChanged = blocksToAdd.size() > 0 || blocksToRemove.size() > 0 || blocksToUpdate.size() > 0;
  622. if (topologyChanged)
  623. {
  624. #if DUMP_TOPOLOGY
  625. dumpTopology (currentTopology);
  626. #endif
  627. for (auto* d : activeTopologySources)
  628. d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); });
  629. }
  630. blocksToUpdate.clear();
  631. blocksToAdd.clear();
  632. blocksToRemove.clear();
  633. static const int maxBlocksToSave = 100;
  634. if (previouslySeenBlocks.size() > maxBlocksToSave)
  635. previouslySeenBlocks.removeRange (0, 2 * (previouslySeenBlocks.size() - maxBlocksToSave));
  636. }
  637. //==============================================================================
  638. JUCE_DECLARE_WEAK_REFERENCEABLE (Detector)
  639. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector)
  640. };
  641. } // namespace juce