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 23 namespace juce 24 { 25 26 //============================================================================== 27 /** This is the main singleton object that keeps track of connected blocks */ 28 struct Detector : public ReferenceCountedObject, 29 private Timer, 30 private AsyncUpdater 31 { 32 using BlockImpl = BlockImplementation<Detector>; 33 Detectorjuce::Detector34 Detector() : defaultDetector (new MIDIDeviceDetector()), deviceDetector (*defaultDetector) 35 { 36 startTimer (10); 37 } 38 Detectorjuce::Detector39 Detector (PhysicalTopologySource::DeviceDetector& dd) : deviceDetector (dd) 40 { 41 startTimer (10); 42 } 43 ~Detectorjuce::Detector44 ~Detector() override 45 { 46 jassert (activeTopologySources.isEmpty()); 47 } 48 49 using Ptr = ReferenceCountedObjectPtr<Detector>; 50 getDefaultDetectorjuce::Detector51 static Detector::Ptr getDefaultDetector() 52 { 53 auto& d = getDefaultDetectorPointer(); 54 55 if (d == nullptr) 56 d = new Detector(); 57 58 return d; 59 } 60 getDefaultDetectorPointerjuce::Detector61 static Detector::Ptr& getDefaultDetectorPointer() 62 { 63 static Detector::Ptr defaultDetector; 64 return defaultDetector; 65 } 66 detachjuce::Detector67 void detach (PhysicalTopologySource* pts) 68 { 69 activeTopologySources.removeAllInstancesOf (pts); 70 71 if (activeTopologySources.isEmpty()) 72 { 73 for (auto& b : currentTopology.blocks) 74 if (auto bi = BlockImpl::getFrom (b)) 75 bi->sendCommandMessage (BlocksProtocol::endAPIMode); 76 77 currentTopology = {}; 78 79 auto& d = getDefaultDetectorPointer(); 80 81 if (d != nullptr && d->getReferenceCount() == 2) 82 getDefaultDetectorPointer() = nullptr; 83 } 84 } 85 isConnectedjuce::Detector86 bool isConnected (Block::UID deviceID) const noexcept 87 { 88 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread! 89 90 for (auto&& b : currentTopology.blocks) 91 if (b->uid == deviceID) 92 return true; 93 94 return false; 95 } 96 isConnectedViaBluetoothjuce::Detector97 bool isConnectedViaBluetooth (const Block& block) const noexcept 98 { 99 if (const auto connection = getDeviceConnectionFor (block)) 100 if (const auto midiConnection = dynamic_cast<const MIDIDeviceConnection*> (connection)) 101 if (midiConnection->midiInput != nullptr) 102 return midiConnection->midiInput->getName().containsIgnoreCase ("bluetooth"); 103 104 return false; 105 } 106 handleDeviceAddedjuce::Detector107 void handleDeviceAdded (const DeviceInfo& info) 108 { 109 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 110 111 const auto blockWasRemoved = containsBlockWithUID (blocksToRemove, info.uid); 112 const auto knownBlock = std::find_if (previouslySeenBlocks.begin(), previouslySeenBlocks.end(), 113 [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); 114 115 Block::Ptr block; 116 117 if (knownBlock != previouslySeenBlocks.end()) 118 { 119 block = *knownBlock; 120 121 if (auto* blockImpl = BlockImpl::getFrom (*block)) 122 { 123 blockImpl->markReconnected (info); 124 previouslySeenBlocks.removeObject (block); 125 } 126 } 127 else 128 { 129 block = new BlockImpl (*this, info); 130 } 131 132 currentTopology.blocks.addIfNotAlreadyThere (block); 133 134 if (blockWasRemoved) 135 { 136 blocksToUpdate.addIfNotAlreadyThere (block); 137 blocksToAdd.removeObject (block); 138 } 139 else 140 { 141 blocksToAdd.addIfNotAlreadyThere (block); 142 blocksToUpdate.removeObject (block); 143 } 144 145 blocksToRemove.removeObject (block); 146 147 triggerAsyncUpdate(); 148 } 149 handleDeviceRemovedjuce::Detector150 void handleDeviceRemoved (const DeviceInfo& info) 151 { 152 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 153 154 const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), 155 [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); 156 157 if (blockIt != currentTopology.blocks.end()) 158 { 159 const Block::Ptr block { *blockIt }; 160 161 if (auto blockImpl = BlockImpl::getFrom (block.get())) 162 blockImpl->markDisconnected(); 163 164 currentTopology.blocks.removeObject (block); 165 previouslySeenBlocks.addIfNotAlreadyThere (block); 166 167 blocksToRemove.addIfNotAlreadyThere (block); 168 blocksToUpdate.removeObject (block); 169 blocksToAdd.removeObject (block); 170 triggerAsyncUpdate(); 171 } 172 } 173 handleConnectionsChangedjuce::Detector174 void handleConnectionsChanged() 175 { 176 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 177 triggerAsyncUpdate(); 178 } 179 handleDevicesUpdatedjuce::Detector180 void handleDevicesUpdated (const Array<DeviceInfo>& infos) 181 { 182 bool shouldTriggerUpdate { false }; 183 184 for (auto& info : infos) 185 { 186 if (containsBlockWithUID (blocksToRemove, info.uid)) 187 continue; 188 189 const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), 190 [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); 191 192 193 if (blockIt != currentTopology.blocks.end()) 194 { 195 const Block::Ptr block { *blockIt }; 196 197 if (auto blockImpl = BlockImpl::getFrom (block.get())) 198 blockImpl->updateDeviceInfo (info); 199 200 if (! containsBlockWithUID (blocksToAdd, info.uid)) 201 { 202 blocksToUpdate.addIfNotAlreadyThere (block); 203 shouldTriggerUpdate = true; 204 } 205 } 206 } 207 208 if (shouldTriggerUpdate) 209 triggerAsyncUpdate(); 210 } 211 handleDeviceUpdatedjuce::Detector212 void handleDeviceUpdated (const DeviceInfo& info) 213 { 214 handleDevicesUpdated ({ info }); 215 } 216 handleBatteryChargingChangedjuce::Detector217 void handleBatteryChargingChanged (Block::UID deviceID, const BlocksProtocol::BatteryCharging isCharging) 218 { 219 if (auto block = currentTopology.getBlockWithUID (deviceID)) 220 if (auto blockImpl = BlockImpl::getFrom (*block)) 221 blockImpl->batteryCharging = isCharging; 222 } 223 handleBatteryLevelChangedjuce::Detector224 void handleBatteryLevelChanged (Block::UID deviceID, const BlocksProtocol::BatteryLevel batteryLevel) 225 { 226 if (auto block = currentTopology.getBlockWithUID (deviceID)) 227 if (auto blockImpl = BlockImpl::getFrom (*block)) 228 blockImpl->batteryLevel = batteryLevel; 229 } 230 handleIndexChangedjuce::Detector231 void handleIndexChanged (Block::UID deviceID, const BlocksProtocol::TopologyIndex index) 232 { 233 if (auto block = currentTopology.getBlockWithUID (deviceID)) 234 if (auto blockImpl = BlockImpl::getFrom (*block)) 235 blockImpl->topologyIndex = index; 236 } 237 notifyBlockIsRestartingjuce::Detector238 void notifyBlockIsRestarting (Block::UID deviceID) 239 { 240 for (auto& group : connectedDeviceGroups) 241 group->handleBlockRestarting (deviceID); 242 } 243 getDnaDependentDeviceUIDsjuce::Detector244 Array<Block::UID> getDnaDependentDeviceUIDs (Block::UID uid) 245 { 246 JUCE_ASSERT_MESSAGE_THREAD 247 248 Array<Block::UID> dependentDeviceUIDs; 249 250 if (auto block = getBlockImplementationWithUID (uid)) 251 { 252 if (auto master = getBlockImplementationWithUID (block->masterUID)) 253 { 254 auto graph = BlockGraph (currentTopology, [uid] (Block::Ptr b) { return b->uid != uid; }); 255 const auto pathWithoutBlock = graph.getTraversalPathFromMaster (master); 256 257 for (const auto b : currentTopology.blocks) 258 { 259 if (b->uid != uid && ! pathWithoutBlock.contains (b)) 260 { 261 TOPOLOGY_LOG ( "Dependent device: " + b->name); 262 dependentDeviceUIDs.add (b->uid); 263 } 264 } 265 } 266 } 267 268 return dependentDeviceUIDs; 269 } 270 handleSharedDataACKjuce::Detector271 void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const 272 { 273 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 274 275 if (auto* bi = getBlockImplementationWithUID (deviceID)) 276 bi->handleSharedDataACK (packetCounter); 277 } 278 handleFirmwareUpdateACKjuce::Detector279 void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode, uint32 resultDetail) 280 { 281 if (auto* bi = getBlockImplementationWithUID (deviceID)) 282 bi->handleFirmwareUpdateACK (resultCode, resultDetail); 283 } 284 handleConfigUpdateMessagejuce::Detector285 void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max) 286 { 287 if (auto* bi = getBlockImplementationWithUID (deviceID)) 288 bi->handleConfigUpdateMessage (item, value, min, max); 289 } 290 notifyBlockOfConfigChangejuce::Detector291 void notifyBlockOfConfigChange (BlockImpl& bi, uint32 item) 292 { 293 if (item >= bi.getMaxConfigIndex()) 294 bi.handleConfigItemChanged ({ item }, item); 295 else 296 bi.handleConfigItemChanged (bi.getLocalConfigMetaData (item), item); 297 } 298 handleConfigSetMessagejuce::Detector299 void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value) 300 { 301 if (auto* bi = getBlockImplementationWithUID (deviceID)) 302 { 303 bi->handleConfigSetMessage (item, value); 304 notifyBlockOfConfigChange (*bi, uint32 (item)); 305 } 306 } 307 handleConfigFactorySyncEndMessagejuce::Detector308 void handleConfigFactorySyncEndMessage (Block::UID deviceID) 309 { 310 if (auto* bi = getBlockImplementationWithUID (deviceID)) 311 bi->handleConfigSyncEnded(); 312 } 313 handleConfigFactorySyncResetMessagejuce::Detector314 void handleConfigFactorySyncResetMessage (Block::UID deviceID) 315 { 316 if (auto* bi = getBlockImplementationWithUID (deviceID)) 317 bi->resetConfigListActiveStatus(); 318 } 319 handleLogMessagejuce::Detector320 void handleLogMessage (Block::UID deviceID, const String& message) const 321 { 322 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 323 324 if (auto* bi = getBlockImplementationWithUID (deviceID)) 325 bi->handleLogMessage (message); 326 } 327 handleButtonChangejuce::Detector328 void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const 329 { 330 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 331 332 if (auto* bi = getBlockImplementationWithUID (deviceID)) 333 { 334 bi->pingFromDevice(); 335 336 if (isPositiveAndBelow (buttonIndex, bi->getButtons().size())) 337 if (auto* cbi = dynamic_cast<BlockImpl::ControlButtonImplementation*> (bi->getButtons().getUnchecked (int (buttonIndex)))) 338 cbi->broadcastButtonChange (timestamp, bi->modelData.buttons[(int) buttonIndex].type, isDown); 339 } 340 } 341 handleTouchChangejuce::Detector342 void handleTouchChange (Block::UID deviceID, const TouchSurface::Touch& touchEvent) 343 { 344 JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 345 346 auto block = currentTopology.getBlockWithUID (deviceID); 347 if (block != nullptr) 348 { 349 if (auto* surface = dynamic_cast<BlockImpl::TouchSurfaceImplementation*> (block->getTouchSurface())) 350 { 351 TouchSurface::Touch scaledEvent (touchEvent); 352 353 scaledEvent.x *= (float) block->getWidth(); 354 scaledEvent.y *= (float) block->getHeight(); 355 scaledEvent.startX *= (float) block->getWidth(); 356 scaledEvent.startY *= (float) block->getHeight(); 357 358 surface->broadcastTouchChange (scaledEvent); 359 } 360 } 361 } 362 cancelAllActiveTouchesjuce::Detector363 void cancelAllActiveTouches() noexcept 364 { 365 for (auto& block : currentTopology.blocks) 366 if (auto* surface = block->getTouchSurface()) 367 surface->cancelAllActiveTouches(); 368 } 369 handleCustomMessagejuce::Detector370 void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data) 371 { 372 if (auto* bi = getBlockImplementationWithUID (deviceID)) 373 bi->handleCustomMessage (timestamp, data); 374 } 375 376 //============================================================================== 377 template <typename PacketBuilder> sendMessageToDevicejuce::Detector378 bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const 379 { 380 for (auto* c : connectedDeviceGroups) 381 if (c->contains (deviceID)) 382 return c->sendMessageToDevice (builder); 383 384 return false; 385 } 386 getFromjuce::Detector387 static Detector* getFrom (Block& b) noexcept 388 { 389 if (auto* bi = BlockImpl::getFrom (b)) 390 return (bi->detector); 391 392 jassertfalse; 393 return nullptr; 394 } 395 getDeviceConnectionForjuce::Detector396 PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b) 397 { 398 for (const auto& d : connectedDeviceGroups) 399 { 400 if (d->contains (b.uid)) 401 return d->getDeviceConnection(); 402 } 403 404 return nullptr; 405 } 406 getDeviceConnectionForjuce::Detector407 const PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b) const 408 { 409 for (const auto& d : connectedDeviceGroups) 410 { 411 if (d->contains (b.uid)) 412 return d->getDeviceConnection(); 413 } 414 415 return nullptr; 416 } 417 418 std::unique_ptr<MIDIDeviceDetector> defaultDetector; 419 PhysicalTopologySource::DeviceDetector& deviceDetector; 420 421 Array<PhysicalTopologySource*> activeTopologySources; 422 423 BlockTopology currentTopology; 424 425 private: 426 Block::Array previouslySeenBlocks, blocksToAdd, blocksToRemove, blocksToUpdate; 427 timerCallbackjuce::Detector428 void timerCallback() override 429 { 430 startTimer (1500); 431 432 auto detectedDevices = deviceDetector.scanForDevices(); 433 434 handleDevicesRemoved (detectedDevices); 435 handleDevicesAdded (detectedDevices); 436 } 437 containsBlockWithUIDjuce::Detector438 bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) 439 { 440 for (const auto block : blocks) 441 if (block->uid == uid) 442 return true; 443 444 return false; 445 } 446 handleDevicesRemovedjuce::Detector447 void handleDevicesRemoved (const StringArray& detectedDevices) 448 { 449 for (int i = connectedDeviceGroups.size(); --i >= 0;) 450 if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices)) 451 connectedDeviceGroups.remove (i); 452 } 453 handleDevicesAddedjuce::Detector454 void handleDevicesAdded (const StringArray& detectedDevices) 455 { 456 for (const auto& devName : detectedDevices) 457 { 458 if (! hasDeviceFor (devName)) 459 { 460 if (auto d = deviceDetector.openDevice (detectedDevices.indexOf (devName))) 461 { 462 connectedDeviceGroups.add (new ConnectedDeviceGroup<Detector> (*this, devName, d)); 463 } 464 } 465 } 466 } 467 hasDeviceForjuce::Detector468 bool hasDeviceFor (const String& devName) const 469 { 470 for (auto d : connectedDeviceGroups) 471 if (d->deviceName == devName) 472 return true; 473 474 return false; 475 } 476 getBlockImplementationWithUIDjuce::Detector477 BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept 478 { 479 if (auto block = currentTopology.getBlockWithUID (deviceID)) 480 return BlockImpl::getFrom (*block); 481 482 return nullptr; 483 } 484 485 OwnedArray<ConnectedDeviceGroup<Detector>> connectedDeviceGroups; 486 487 //============================================================================== 488 /** This is a friend of the BlocksImplementation that will scan and set the 489 physical positions of the blocks. 490 491 Returns an array of blocks that were updated. 492 */ 493 struct BlocksLayoutTraverser 494 { updateBlocksjuce::Detector::BlocksLayoutTraverser495 static Block::Array updateBlocks (const BlockTopology& topology) 496 { 497 Block::Array updated; 498 Array<Block::UID> visited; 499 500 for (auto& block : topology.blocks) 501 { 502 if (block->isMasterBlock() && ! visited.contains (block->uid)) 503 { 504 if (auto* bi = BlockImpl::getFrom (block)) 505 { 506 if (bi->rotation != 0 || bi->position.first != 0 || bi->position.second != 0) 507 { 508 bi->rotation = 0; 509 bi->position = {}; 510 updated.add (block); 511 } 512 } 513 514 layoutNeighbours (*block, topology, visited, updated); 515 } 516 } 517 518 return updated; 519 } 520 521 private: 522 // returns the distance from corner clockwise getUnitForIndexjuce::Detector::BlocksLayoutTraverser523 static int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index) 524 { 525 if (block->getType() == Block::seaboardBlock) 526 { 527 if (edge == Block::ConnectionPort::DeviceEdge::north) 528 { 529 if (index == 0) return 1; 530 if (index == 1) return 4; 531 } 532 else if (edge != Block::ConnectionPort::DeviceEdge::south) 533 { 534 return 1; 535 } 536 } 537 else if (block->getType() == Block::lumiKeysBlock) 538 { 539 if (edge == Block::ConnectionPort::DeviceEdge::north) 540 { 541 switch (index) 542 { 543 case 0 : return 0; 544 case 1 : return 2; 545 case 2 : return 3; 546 case 3 : return 5; 547 default : jassertfalse; 548 } 549 } 550 else if (edge == Block::ConnectionPort::DeviceEdge::south) 551 { 552 jassertfalse; 553 } 554 } 555 556 if (edge == Block::ConnectionPort::DeviceEdge::south) 557 return block->getWidth() - (index + 1); 558 559 if (edge == Block::ConnectionPort::DeviceEdge::west) 560 return block->getHeight() - (index + 1); 561 562 return index; 563 } 564 565 // returns how often north needs to rotate by 90 degrees getRotationForEdgejuce::Detector::BlocksLayoutTraverser566 static int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge) 567 { 568 switch (edge) 569 { 570 case Block::ConnectionPort::DeviceEdge::north: return 0; 571 case Block::ConnectionPort::DeviceEdge::east: return 1; 572 case Block::ConnectionPort::DeviceEdge::south: return 2; 573 case Block::ConnectionPort::DeviceEdge::west: return 3; 574 default: break; 575 } 576 577 jassertfalse; 578 return 0; 579 } 580 layoutNeighboursjuce::Detector::BlocksLayoutTraverser581 static void layoutNeighbours (const Block::Ptr block, 582 const BlockTopology& topology, 583 Array<Block::UID>& visited, 584 Block::Array& updated) 585 { 586 visited.add (block->uid); 587 588 for (auto& connection : topology.connections) 589 { 590 if ((connection.device1 == block->uid && ! visited.contains (connection.device2)) 591 || (connection.device2 == block->uid && ! visited.contains (connection.device1))) 592 { 593 const auto theirUid = connection.device1 == block->uid ? connection.device2 : connection.device1; 594 const auto neighbourPtr = topology.getBlockWithUID (theirUid); 595 596 if (auto* neighbour = dynamic_cast<BlockImpl*> (neighbourPtr.get())) 597 { 598 const auto myBounds = block->getBlockAreaWithinLayout(); 599 const auto& myPort = connection.device1 == block->uid ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2; 600 const auto& theirPort = connection.device1 == block->uid ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1; 601 const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index); 602 const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index); 603 604 { 605 const auto neighbourRotation = (2 + block->getRotation() 606 + getRotationForEdge (myPort.edge) 607 - getRotationForEdge (theirPort.edge)) % 4; 608 609 if (neighbour->rotation != neighbourRotation) 610 { 611 neighbour->rotation = neighbourRotation; 612 updated.addIfNotAlreadyThere (neighbourPtr); 613 } 614 } 615 616 std::pair<int, int> delta; 617 const auto theirBounds = neighbour->getBlockAreaWithinLayout(); 618 619 switch ((block->getRotation() + getRotationForEdge (myPort.edge)) % 4) 620 { 621 case 0: // over me 622 delta = { myOffset - (theirBounds.width - (theirOffset + 1)), -theirBounds.height }; 623 break; 624 case 1: // right of me 625 delta = { myBounds.width, myOffset - (theirBounds.height - (theirOffset + 1)) }; 626 break; 627 case 2: // under me 628 delta = { (myBounds.width - (myOffset + 1)) - theirOffset, myBounds.height }; 629 break; 630 case 3: // left of me 631 delta = { -theirBounds.width, (myBounds.height - (myOffset + 1)) - theirOffset }; 632 break; 633 default: 634 break; 635 } 636 637 { 638 const auto neighbourX = myBounds.x + delta.first; 639 const auto neighbourY = myBounds.y + delta.second; 640 641 if (neighbour->position.first != neighbourX 642 || neighbour->position.second != neighbourY) 643 { 644 neighbour->position.first = neighbourX; 645 neighbour->position.second = neighbourY; 646 647 updated.addIfNotAlreadyThere (neighbourPtr); 648 } 649 } 650 651 layoutNeighbours (neighbourPtr, topology, visited, updated); 652 } 653 } 654 } 655 } 656 }; 657 658 //============================================================================== 659 #if DUMP_TOPOLOGY idToSerialNumjuce::Detector660 static String idToSerialNum (const BlockTopology& topology, Block::UID uid) 661 { 662 for (auto* b : topology.blocks) 663 if (b->uid == uid) 664 return b->serialNumber; 665 666 return "???"; 667 } 668 portEdgeToStringjuce::Detector669 static String portEdgeToString (Block::ConnectionPort port) 670 { 671 switch (port.edge) 672 { 673 case Block::ConnectionPort::DeviceEdge::north: return "north"; 674 case Block::ConnectionPort::DeviceEdge::south: return "south"; 675 case Block::ConnectionPort::DeviceEdge::east: return "east"; 676 case Block::ConnectionPort::DeviceEdge::west: return "west"; 677 default: break; 678 } 679 680 return {}; 681 } 682 portToStringjuce::Detector683 static String portToString (Block::ConnectionPort port) 684 { 685 return portEdgeToString (port) + "_" + String (port.index); 686 } 687 dumpTopologyjuce::Detector688 static void dumpTopology (const BlockTopology& topology) 689 { 690 MemoryOutputStream m; 691 692 m << "=============================================================================" << newLine 693 << "Topology: " << topology.blocks.size() << " device(s)" << newLine 694 << newLine; 695 696 int index = 0; 697 698 for (auto block : topology.blocks) 699 { 700 m << "Device " << index++ << (block->isMasterBlock() ? ": (MASTER)" : ":") << newLine; 701 702 m << " Description: " << block->getDeviceDescription() << newLine 703 << " Serial: " << block->serialNumber << newLine; 704 705 if (auto bi = BlockImplementation<Detector>::getFrom (*block)) 706 m << " Short address: " << (int) bi->getDeviceIndex() << newLine; 707 708 m << " Battery level: " + String (roundToInt (100.0f * block->getBatteryLevel())) + "%" << newLine 709 << " Battery charging: " + String (block->isBatteryCharging() ? "y" : "n") << newLine 710 << " Width: " << block->getWidth() << newLine 711 << " Height: " << block->getHeight() << newLine 712 << " Millimeters per unit: " << block->getMillimetersPerUnit() << newLine 713 << newLine; 714 } 715 716 for (auto& connection : topology.connections) 717 { 718 m << idToSerialNum (topology, connection.device1) 719 << ":" << portToString (connection.connectionPortOnDevice1) 720 << " <-> " 721 << idToSerialNum (topology, connection.device2) 722 << ":" << portToString (connection.connectionPortOnDevice2) << newLine; 723 } 724 725 m << "=============================================================================" << newLine; 726 727 Logger::outputDebugString (m.toString()); 728 } 729 #endif 730 731 //============================================================================== updateBlockPositionsjuce::Detector732 void updateBlockPositions() 733 { 734 const auto updated = BlocksLayoutTraverser::updateBlocks (currentTopology); 735 736 for (const auto block : updated) 737 { 738 if (containsBlockWithUID (blocksToAdd, block->uid) || containsBlockWithUID (blocksToRemove, block->uid)) 739 continue; 740 741 blocksToUpdate.addIfNotAlreadyThere (block); 742 } 743 } 744 updateBlockConnectionsjuce::Detector745 void updateBlockConnections() 746 { 747 currentTopology.connections.clearQuick(); 748 749 for (auto d : connectedDeviceGroups) 750 currentTopology.connections.addArray (d->getCurrentDeviceConnections()); 751 } 752 handleAsyncUpdatejuce::Detector753 void handleAsyncUpdate() override 754 { 755 updateBlockConnections(); 756 updateBlockPositions(); 757 758 for (auto* d : activeTopologySources) 759 { 760 for (const auto block : blocksToAdd) 761 d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockAdded (block); }); 762 763 for (const auto block : blocksToRemove) 764 d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockRemoved (block); }); 765 766 for (const auto block : blocksToUpdate) 767 d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockUpdated (block); }); 768 } 769 770 const auto topologyChanged = blocksToAdd.size() > 0 || blocksToRemove.size() > 0 || blocksToUpdate.size() > 0; 771 772 if (topologyChanged) 773 { 774 #if DUMP_TOPOLOGY 775 dumpTopology (currentTopology); 776 #endif 777 778 for (auto* d : activeTopologySources) 779 d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); }); 780 } 781 782 blocksToUpdate.clear(); 783 blocksToAdd.clear(); 784 blocksToRemove.clear(); 785 786 static const int maxBlocksToSave = 100; 787 788 if (previouslySeenBlocks.size() > maxBlocksToSave) 789 previouslySeenBlocks.removeRange (0, 2 * (previouslySeenBlocks.size() - maxBlocksToSave)); 790 } 791 792 //============================================================================== 793 JUCE_DECLARE_WEAK_REFERENCEABLE (Detector) 794 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector) 795 }; 796 797 } // namespace juce 798