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