From 7cf52297ee175481b9e11cfdba1ce4de88c22065 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 18 Feb 2019 15:03:42 +0000 Subject: [PATCH] BLOCKS: Added callbacks from the topology for device added, removed and updated --- .../juce_blocks_basics/blocks/juce_Block.cpp | 8 +- .../juce_blocks_basics/blocks/juce_Block.h | 7 + .../protocol/juce_BlocksProtocolDefinitions.h | 84 ++- .../protocol/juce_HostPacketDecoder.h | 7 +- .../internal/juce_BlockImplementation.cpp | 73 ++- .../internal/juce_ConnectedDeviceGroup.cpp | 576 +++++++++--------- .../juce_DepreciatedVersionReader.cpp | 21 +- .../topology/internal/juce_Detector.cpp | 407 +++++++------ .../topology/internal/juce_DeviceInfo.cpp | 13 +- .../topology/juce_PhysicalTopologySource.cpp | 11 - .../topology/juce_TopologySource.h | 15 +- 11 files changed, 642 insertions(+), 580 deletions(-) diff --git a/modules/juce_blocks_basics/blocks/juce_Block.cpp b/modules/juce_blocks_basics/blocks/juce_Block.cpp index 144cc183e0..034e708782 100644 --- a/modules/juce_blocks_basics/blocks/juce_Block.cpp +++ b/modules/juce_blocks_basics/blocks/juce_Block.cpp @@ -27,7 +27,7 @@ static Block::UID getBlockUIDFromSerialNumber (const uint8* serial) noexcept { Block::UID n = {}; - for (int i = 0; i < (int) sizeof (BlocksProtocol::BlockSerialNumber); ++i) + for (int i = 0; i < int (BlocksProtocol::BlockSerialNumber::maxLength); ++i) n += n * 127 + serial[i]; return n; @@ -35,15 +35,15 @@ static Block::UID getBlockUIDFromSerialNumber (const uint8* serial) noexcept static Block::UID getBlockUIDFromSerialNumber (const BlocksProtocol::BlockSerialNumber& serial) noexcept { - return getBlockUIDFromSerialNumber (serial.serial); + return getBlockUIDFromSerialNumber (serial.data); } static Block::UID getBlockUIDFromSerialNumber (const juce::String& serial) noexcept { - if (serial.length() < (int) sizeof (BlocksProtocol::BlockSerialNumber)) + if (serial.length() < int (BlocksProtocol::BlockSerialNumber::maxLength)) { jassertfalse; - return getBlockUIDFromSerialNumber (serial.paddedRight ('0', sizeof (BlocksProtocol::BlockSerialNumber))); + return getBlockUIDFromSerialNumber (serial.paddedRight ('0', BlocksProtocol::BlockSerialNumber::maxLength)); } return getBlockUIDFromSerialNumber ((const uint8*) serial.toRawUTF8()); diff --git a/modules/juce_blocks_basics/blocks/juce_Block.h b/modules/juce_blocks_basics/blocks/juce_Block.h index 9b6fb71f4e..63da0c51f6 100644 --- a/modules/juce_blocks_basics/blocks/juce_Block.h +++ b/modules/juce_blocks_basics/blocks/juce_Block.h @@ -107,6 +107,13 @@ public: /** Returns true if this block is connected and active. */ virtual bool isConnected() const = 0; + /** Returns the time this block object was connected to the topology. + Only valid when isConnected == true. + + @see isConnected + */ + virtual Time getConnectionTime() const = 0; + /** Returns true if this block is directly connected to the application, as opposed to only being connected to a different block via a connection port. diff --git a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h index 25a05e1e54..4bc47f6e07 100644 --- a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h +++ b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h @@ -123,36 +123,6 @@ using BatteryCharging = IntegerWithBitSize<1>; */ using ConnectorPort = IntegerWithBitSize<5>; -//============================================================================== -/** Structure describing a block's serial number - - @tags{Blocks} -*/ -struct BlockSerialNumber -{ - uint8 serial[16]; - - bool isValid() const noexcept - { - for (auto c : serial) - if (c == 0) - return false; - - return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); - } - - bool isPadBlock() const noexcept { return hasPrefix ("LPB") || hasPrefix ("LPM"); } - bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } - bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } - bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } - bool isTouchBlock() const noexcept { return hasPrefix ("TCB"); } - bool isSeaboardBlock() const noexcept { return hasPrefix ("SBB"); } - - bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); } - - bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; } -}; - //============================================================================== /** Structure for generic block data @@ -171,6 +141,11 @@ struct BlockStringData return length > 0; } + juce::String asString() const + { + return juce::String ((const char*) data, length); + } + bool operator== (const BlockStringData& other) const { if (length != other.length) @@ -192,6 +167,34 @@ struct BlockStringData using VersionNumber = BlockStringData<21>; using BlockName = BlockStringData<33>; +//============================================================================== +/** Structure describing a block's serial number + + @tags{Blocks} +*/ +struct BlockSerialNumber : public BlockStringData<16> +{ + bool isValid() const noexcept + { + for (auto c : data) + if (c == 0) + return false; + + return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); + } + + bool isPadBlock() const noexcept { return hasPrefix ("LPB") || hasPrefix ("LPM"); } + bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } + bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } + bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } + bool isTouchBlock() const noexcept { return hasPrefix ("TCB"); } + bool isSeaboardBlock() const noexcept { return hasPrefix ("SBB"); } + + bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); } + + bool hasPrefix (const char* prefix) const noexcept { return memcmp (data, prefix, 3) == 0; } +}; + //============================================================================== /** Structure for the device status @@ -214,6 +217,25 @@ struct DeviceConnection { TopologyIndex device1, device2; ConnectorPort port1, port2; + + bool operator== (const DeviceConnection& other) const + { + return isEqual (other); + } + + bool operator!= (const DeviceConnection& other) const + { + return ! isEqual (other); + } + +private: + bool isEqual (const DeviceConnection& other) const + { + return device1 == other.device1 + && device2 == other.device2 + && port1 == other.port1 + && port2 == other.port2; + } }; //============================================================================== @@ -445,7 +467,7 @@ static constexpr uint32 controlBlockStackSize = 800; enum BitSizes { topologyMessageHeader = MessageType::bits + ProtocolVersion::bits + DeviceCount::bits + ConnectionCount::bits, - topologyDeviceInfo = sizeof (BlockSerialNumber) * 7 + BatteryLevel::bits + BatteryCharging::bits, + topologyDeviceInfo = BlockSerialNumber::maxLength * 7 + BatteryLevel::bits + BatteryCharging::bits, topologyConnectionInfo = topologyIndexBits + ConnectorPort::bits + topologyIndexBits + ConnectorPort::bits, typeDeviceAndTime = MessageType::bits + PacketTimestampOffset::bits, diff --git a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h index 97cc2ab667..7db57e2f85 100644 --- a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h +++ b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h @@ -164,8 +164,11 @@ struct HostPacketDecoder { DeviceStatus status; - for (uint32 i = 0; i < sizeof (BlockSerialNumber); ++i) - status.serialNumber.serial[i] = (uint8) reader.readBits (7); + for (uint32 i = 0; i < BlockSerialNumber::maxLength; ++i) + { + status.serialNumber.data[i] = (uint8) reader.readBits (7); + ++status.serialNumber.length; + } status.index = (TopologyIndex) reader.readBits (topologyIndexBits); status.batteryLevel = reader.read(); diff --git a/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp b/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp index 6ccacaa24c..2599be24e2 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp @@ -34,19 +34,16 @@ public: struct LEDGridImplementation; struct LEDRowImplementation; - BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, - Detector& detectorToUse, - BlocksProtocol::VersionNumber version, - BlocksProtocol::BlockName blockName, - bool isMasterBlock) - : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), - juce::String ((const char*) version.data, version.length), - juce::String ((const char*) blockName.data, blockName.length)), - modelData (serial), + BlockImplementation (Detector& detectorToUse, const DeviceInfo& deviceInfo) + : Block (deviceInfo.serial.asString(), + deviceInfo.version.asString(), + deviceInfo.name.asString()), + modelData (deviceInfo.serial), remoteHeap (modelData.programAndHeapSize), - detector (&detectorToUse), - isMaster (isMasterBlock) + detector (&detectorToUse) { + markReconnected (deviceInfo); + if (modelData.hasTouchSurface) touchSurface.reset (new TouchSurfaceImplementation (*this)); @@ -77,13 +74,25 @@ public: { if (auto surface = dynamic_cast (touchSurface.get())) surface->disableTouchSurface(); + + connectionTime = Time(); } void markReconnected (const DeviceInfo& deviceInfo) { - versionNumber = asString (deviceInfo.version); - name = asString (deviceInfo.name); + if (wasPowerCycled()) + resetPowerCycleFlag(); + + if (connectionTime == Time()) + connectionTime = Time::getCurrentTime(); + + versionNumber = deviceInfo.version.asString(); + name = deviceInfo.name.asString(); isMaster = deviceInfo.isMaster; + masterUID = deviceInfo.masterUid; + batteryCharging = deviceInfo.batteryCharging; + batteryLevel = deviceInfo.batteryLevel; + topologyIndex = deviceInfo.index; setProgram (nullptr); remoteHeap.resetDeviceStateToUnknown(); @@ -120,6 +129,7 @@ public: bool isHardwareBlock() const override { return true; } juce::Array getPorts() const override { return modelData.ports; } bool isConnected() const override { return detector && detector->isConnected (uid); } + juce::Time getConnectionTime() const override { return connectionTime; } bool isMasterBlock() const override { return isMaster; } Block::UID getConnectedMasterUID() const override { return masterUID; } int getRotation() const override { return rotation; } @@ -159,24 +169,12 @@ public: float getBatteryLevel() const override { - if (detector == nullptr) - return 0.0f; - - if (auto status = detector->getLastStatus (uid)) - return status->batteryLevel.toUnipolarFloat(); - - return 0.0f; + return batteryLevel.toUnipolarFloat(); } bool isBatteryCharging() const override { - if (detector == nullptr) - return false; - - if (auto status = detector->getLastStatus (uid)) - return status->batteryCharging.get() != 0; - - return false; + return batteryCharging.get() > 0; } bool supportsGraphics() const override @@ -186,10 +184,7 @@ public: int getDeviceIndex() const noexcept { - if (detector == nullptr) - return -1; - - return isConnected() ? detector->getIndexFromDeviceID (uid) : -1; + return isConnected() ? topologyIndex : -1; } template @@ -220,10 +215,15 @@ public: programEventListeners.call ([&] (ProgramEventListener& l) { l.handleProgramEvent (*this, m); }); } + static BlockImplementation* getFrom (Block* b) noexcept + { + jassert (dynamic_cast (b) != nullptr); + return dynamic_cast (b); + } + static BlockImplementation* getFrom (Block& b) noexcept { - jassert (dynamic_cast (&b) != nullptr); - return dynamic_cast (&b); + return getFrom (&b); } //============================================================================== @@ -584,6 +584,13 @@ private: bool isMaster = false; Block::UID masterUID = {}; + BlocksProtocol::BatteryLevel batteryLevel {}; + BlocksProtocol::BatteryCharging batteryCharging {}; + + BlocksProtocol::TopologyIndex topologyIndex {}; + + Time connectionTime {}; + std::pair position; int rotation = 0; friend Detector; diff --git a/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp b/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp index b67af622a0..5dbbf2817b 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp @@ -29,55 +29,6 @@ namespace { return static_cast (timestamp); } - - template - static int removeUnusedBlocksFromMap (std::map& mapToClean, const juce::Array& currentDevices) - { - int numRemoved = 0; - - for (auto iter = mapToClean.begin(); iter != mapToClean.end();) - { - bool found = false; - - for (auto& info : currentDevices) - { - if (info.uid == iter->first) - { - found = true; - break; - } - } - - if (found) - { - ++iter; - } - else - { - mapToClean.erase (iter++); - ++numRemoved; - } - } - - return numRemoved; - } - - /* Returns true new element added to map or value of existing key changed */ - template - static bool insertOrAssign (std::map& map, const Block::UID& key, const V& value) - { - const auto result = map.emplace (std::make_pair (key, value)); - - if (! result.second) - { - if (result.first->second == value) - return false; - - result.first->second = value; - } - - return true; - } } template @@ -88,8 +39,6 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, ConnectedDeviceGroup (Detector& d, const juce::String& name, PhysicalTopologySource::DeviceConnection* connection) : detector (d), deviceName (name), deviceConnection (connection) { - - if (auto midiDeviceConnection = static_cast (deviceConnection.get())) { depreciatedVersionReader = std::make_unique (*midiDeviceConnection); @@ -106,30 +55,23 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, sendTopologyRequest(); } - bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept + ~ConnectedDeviceGroup() { - return detectedDevices.contains (deviceName) && ! failedToGetTopology(); + for (const auto& device : currentDeviceInfo) + detector.handleDeviceRemoved (device); } - int getIndexFromDeviceID (Block::UID uid) const noexcept + bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept { - for (auto& d : currentDeviceInfo) - if (d.uid == uid) - return d.index; - - return -1; + return detectedDevices.contains (deviceName) && ! failedToGetTopology(); } - const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept + bool contains (Block::UID uid) { - for (auto&& status : currentTopologyDevices) - if (getBlockUIDFromSerialNumber (status.serialNumber) == deviceID) - return &status; - - return nullptr; + return getIndexFromDeviceID (uid) >= 0; } - void notifyBlockIsRestarting (Block::UID deviceID) + void handleBlockRestarting (Block::UID deviceID) { forceApiDisconnected (deviceID); } @@ -164,63 +106,52 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, { lastTopologyReceiveTime = juce::Time::getCurrentTime(); - if (incomingTopologyDevices.isEmpty()) + if (incomingTopologyDevices.isEmpty() + || incomingTopologyConnections.size() < incomingTopologyDevices.size() - 1) { - jassertfalse; + LOG_CONNECTIVITY ("Invalid topology or device list received."); + LOG_CONNECTIVITY ("Device size : " << incomingTopologyDevices.size()); + LOG_CONNECTIVITY ("Connections size : " << incomingTopologyConnections.size()); + scheduleNewTopologyRequest(); return; } - currentTopologyDevices.swapWith (incomingTopologyDevices); - currentTopologyConnections.swapWith (incomingTopologyConnections); - - incomingTopologyDevices.clearQuick(); - incomingTopologyConnections.clearQuick(); - - refreshCurrentDeviceInfo(); - refreshCurrentDeviceConnections(); - - removeUnusedBlocksFromMap (versionNumbers, currentDeviceInfo); - removeUnusedBlocksFromMap (blockNames, currentDeviceInfo); + LOG_CONNECTIVITY ("Valid topology received"); - removePingForDisconnectedBlocks(); - - detector.handleTopologyChange(); + updateCurrentDeviceList(); + updateCurrentDeviceConnections(); } void handleVersion (BlocksProtocol::DeviceVersion version) { - const auto uid = getDeviceIDFromIndex (version.index); - - if (uid == Block::UID() || version.version.length <= 1) - return; - - setVersion (uid, version.version); + setVersion (version.index, version.version); } void handleName (BlocksProtocol::DeviceName name) { - const auto uid = getDeviceIDFromIndex (name.index); - - if (uid == Block::UID() || name.name.length <= 1) + if (name.name.length <= 1) return; - if (insertOrAssign (blockNames, uid, name.name)) + if (const auto info = getDeviceInfoFromIndex (name.index)) { - refreshCurrentDeviceInfo(); - detector.handleTopologyChange(); + if (info->name == name.name) + return; + + info->name = name.name; + detector.handleDeviceUpdated (*info); } } void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, BlocksProtocol::ControlButtonID buttonID, bool isDown) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); } void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data); } @@ -231,7 +162,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, BlocksProtocol::TouchVelocity velocity, bool isStart, bool isEnd) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { TouchSurface::Touch touch; @@ -267,7 +198,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::PacketCounter counter) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { detector.handleSharedDataACK (deviceID, counter); updateApiPing (deviceID); @@ -278,7 +209,7 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, BlocksProtocol::FirmwareUpdateACKCode resultCode, BlocksProtocol::FirmwareUpdateACKDetail resultDetail) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) { detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get()); updateApiPing (deviceID); @@ -288,32 +219,32 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value, int32 min, int32 max) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigUpdateMessage (deviceID, item, value, min, max); } void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigSetMessage (deviceID, item, value); } void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigFactorySyncEndMessage (deviceID); } void handleConfigFactorySyncResetMessage (BlocksProtocol::TopologyIndex deviceIndex) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleConfigFactorySyncResetMessage (deviceID); } void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) { - if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) + if (auto deviceID = getDeviceIDFromIndex (deviceIndex)) detector.handleLogMessage (deviceID, message); } @@ -337,17 +268,14 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, return deviceConnection.get(); } - juce::Array getCurrentDeviceInfo() - { - auto blocks = currentDeviceInfo; - blocks.removeIf ([this] (DeviceInfo& info) { return ! isApiConnected (info.uid); }); - return blocks; - } - juce::Array getCurrentDeviceConnections() { - auto connections = currentDeviceConnections; - connections.removeIf ([this] (BlockDeviceConnection& c) { return ! isApiConnected (c.device1) || ! isApiConnected (c.device2); }); + juce::Array connections; + + for (const auto& connection : currentDeviceConnections) + if (isApiConnected (getDeviceIDFromIndex (connection.device1)) && isApiConnected (getDeviceIDFromIndex (connection.device2))) + connections.add (getBlockDeviceConnection (connection)); + return connections; } @@ -359,18 +287,14 @@ struct ConnectedDeviceGroup : private juce::AsyncUpdater, private: //============================================================================== juce::Array currentDeviceInfo; - juce::Array currentDeviceConnections; - std::unique_ptr deviceConnection; + juce::Array incomingTopologyDevices; + juce::Array incomingTopologyConnections, currentDeviceConnections; - juce::Array incomingTopologyDevices, currentTopologyDevices; - juce::Array incomingTopologyConnections, currentTopologyConnections; + std::unique_ptr deviceConnection; juce::CriticalSection incomingPacketLock; juce::Array incomingPackets; - std::map versionNumbers; - std::map blockNames; - std::unique_ptr depreciatedVersionReader; struct TouchStart { float x, y; }; @@ -378,6 +302,21 @@ private: Block::UID masterBlock = 0; + //============================================================================== + void timerCallback() override + { + const auto now = juce::Time::getCurrentTime(); + + if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0)) + && now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0) + && numTopologyRequestsSent < 4) + sendTopologyRequest(); + + checkApiTimeouts (now); + startApiModeOnConnectedBlocks(); + requestMasterBlockVersionIfNeeded(); + } + //============================================================================== void setMidiMessageCallback() { @@ -387,12 +326,57 @@ private: }; } + void handleIncomingMessage (const void* data, size_t dataSize) + { + juce::MemoryBlock mb (data, dataSize); + + { + const juce::ScopedLock sl (incomingPacketLock); + incomingPackets.add (std::move (mb)); + } + + triggerAsyncUpdate(); + + #if DUMP_BANDWIDTH_STATS + registerBytesIn ((int) dataSize); + #endif + } + + void handleAsyncUpdate() override + { + juce::Array packets; + packets.ensureStorageAllocated (32); + + { + const juce::ScopedLock sl (incomingPacketLock); + incomingPackets.swapWith (packets); + } + + for (auto& packet : packets) + { + auto data = static_cast (packet.getData()); + + BlocksProtocol::HostPacketDecoder + ::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1); + } + } + + bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const + { + BlocksProtocol::HostPacketBuilder<64> p; + p.writePacketSysexHeaderBytes (deviceIndex); + p.deviceControlMessage (commandID); + p.writePacketSysexFooter(); + return sendMessageToDevice (p); + } + //============================================================================== juce::Time lastTopologyRequestTime, lastTopologyReceiveTime; int numTopologyRequestsSent = 0; void scheduleNewTopologyRequest() { + LOG_CONNECTIVITY ("Topology Request Scheduled"); numTopologyRequestsSent = 0; lastTopologyReceiveTime = juce::Time(); lastTopologyRequestTime = juce::Time::getCurrentTime(); @@ -405,34 +389,11 @@ private: sendCommandMessage (0, BlocksProtocol::requestTopologyMessage); } - void timerCallback() override - { - const auto now = juce::Time::getCurrentTime(); - - if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0)) - && now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0) - && numTopologyRequestsSent < 4) - sendTopologyRequest(); - - checkApiTimeouts (now); - startApiModeOnConnectedBlocks(); - requestMasterBlockVersionIfNeeded(); - } - bool failedToGetTopology() const noexcept { return numTopologyRequestsSent >= 4 && lastTopologyReceiveTime == juce::Time(); } - bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const - { - BlocksProtocol::HostPacketBuilder<64> p; - p.writePacketSysexHeaderBytes (deviceIndex); - p.deviceControlMessage (commandID); - p.writePacketSysexFooter(); - return sendMessageToDevice (p); - } - //============================================================================== void requestMasterBlockVersionIfNeeded() { @@ -442,18 +403,31 @@ private: const auto masterVersion = depreciatedVersionReader->getVersionNumber(); if (masterVersion.isNotEmpty()) - setVersion (masterBlock, masterVersion); + { + const auto masterIndex = getIndexFromDeviceID (masterBlock); + + if (masterIndex >= 0) + setVersion (BlocksProtocol::TopologyIndex (masterIndex), masterVersion); + else + jassertfalse; + } } - void setVersion (const Block::UID uid, const BlocksProtocol::VersionNumber versionNumber) + void setVersion (const BlocksProtocol::TopologyIndex index, const BlocksProtocol::VersionNumber versionNumber) { - if (uid == masterBlock) - depreciatedVersionReader.reset(); + if (versionNumber.length <= 1) + return; - if (insertOrAssign (versionNumbers, uid, versionNumber)) + if (const auto info = getDeviceInfoFromIndex (index)) { - refreshCurrentDeviceInfo(); - detector.handleTopologyChange(); + if (info->version == versionNumber) + return; + + if (info->uid == masterBlock) + depreciatedVersionReader.reset(); + + info->version = versionNumber; + detector.handleDeviceUpdated (*info); } } @@ -467,6 +441,31 @@ private: juce::Array blockPings; + BlockPingTime* getPing (Block::UID uid) + { + for (auto& ping : blockPings) + if (uid == ping.blockUID) + return &ping; + + return nullptr; + } + + void removePing (Block::UID uid) + { + const auto remove = [uid] (const BlockPingTime& ping) + { + if (uid == ping.blockUID) + { + LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); + return true; + } + + return false; + }; + + blockPings.removeIf (remove); + } + void updateApiPing (Block::UID uid) { const auto now = juce::Time::getCurrentTime(); @@ -480,22 +479,10 @@ private: { LOG_CONNECTIVITY ("API Connected " << uid); blockPings.add ({ uid, now, now }); - detector.handleTopologyChange(); - } - } - - BlockPingTime* getPing (Block::UID uid) - { - for (auto& ping : blockPings) - if (uid == ping.blockUID) - return &ping; - return nullptr; - } - - void removeDeviceInfo (Block::UID uid) - { - currentDeviceInfo.removeIf ([uid] (DeviceInfo& info) { return uid == info.uid; }); + if (const auto info = getDeviceInfoFromUID (uid)) + detector.handleDeviceAdded (*info); + } } bool isApiConnected (Block::UID uid) @@ -503,55 +490,35 @@ private: return getPing (uid) != nullptr; } - void forceApiDisconnected (Block::UID uid) + void forceApiDisconnected (Block::UID /*uid*/) { - if (isApiConnected (uid)) - { - // Clear all known API connections and broadcast an empty topology, - // as DNA blocks connected to the restarting block may be offline. - LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology"); - currentDeviceInfo.clearQuick(); - blockPings.clearQuick(); - blockNames.clear(); - versionNumbers.clear(); - detector.handleTopologyChange(); - scheduleNewTopologyRequest(); - } - } + Array toRemove; - void checkApiTimeouts (juce::Time now) - { - const auto timedOut = [this, now] (BlockPingTime& ping) - { - if (ping.lastPing >= now - juce::RelativeTime::seconds (pingTimeoutSeconds)) - return false; + for (const auto& ping : blockPings) + toRemove.add (ping.blockUID); - LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID); - removeDeviceInfo (ping.blockUID); - return true; - }; + for (const auto& uid : toRemove) + removeDevice (uid); - if (blockPings.removeIf (timedOut) > 0) - { - scheduleNewTopologyRequest(); - detector.handleTopologyChange(); - } + scheduleNewTopologyRequest(); } - /* Returns true is ping was removed */ - void removePingForDisconnectedBlocks() + void checkApiTimeouts (juce::Time now) { - const auto removed = [this] (auto& ping) - { - for (auto& info : currentDeviceInfo) - if (info.uid == ping.blockUID) - return false; + Array toRemove; - LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID); - return true; - }; + for (const auto& ping : blockPings) + { + if (ping.lastPing < now - juce::RelativeTime::seconds (pingTimeoutSeconds)) + { + LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID); + toRemove.add (ping.blockUID); + scheduleNewTopologyRequest(); + } + } - blockPings.removeIf (removed); + for (const auto& uid : toRemove) + removeDevice (uid); } void startApiModeOnConnectedBlocks() @@ -568,151 +535,160 @@ private: } //============================================================================== - void checkVersionNumberTimeouts() + Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) noexcept { for (const auto& device : currentDeviceInfo) - { - const auto version = versionNumbers.find (device.uid); + if (device.index == index) + return device.uid; - if (version == versionNumbers.end()) - { - auto* ping = getPing (device.uid); + scheduleNewTopologyRequest(); + return {}; + } - } - } + int getIndexFromDeviceID (Block::UID uid) const noexcept + { + for (auto& d : currentDeviceInfo) + if (d.uid == uid) + return d.index; + + return -1; } - //============================================================================== - Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept + DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept { for (auto& d : currentDeviceInfo) - if (d.index == index) - return d.uid; + if (d.uid == uid) + return &d; - return {}; + return nullptr; } - Block::UID getDeviceIDFromMessageIndex (BlocksProtocol::TopologyIndex index) noexcept + DeviceInfo* getDeviceInfoFromIndex (BlocksProtocol::TopologyIndex index) const noexcept { - const auto uid = getDeviceIDFromIndex (index); + for (auto& d : currentDeviceInfo) + if (d.index == index) + return &d; - // re-request topology if we get an event from an unknown block - if (uid == Block::UID()) - scheduleNewTopologyRequest(); + return nullptr; + } - return uid; + void removeDeviceInfo (Block::UID uid) + { + currentDeviceInfo.removeIf ([uid] (const DeviceInfo& info) { return info.uid == uid; }); } - //============================================================================== - void handleIncomingMessage (const void* data, size_t dataSize) + const DeviceStatus* getIncomingDeviceStatus (BlockSerialNumber serialNumber) const { - juce::MemoryBlock mb (data, dataSize); + for (auto& device : incomingTopologyDevices) + if (device.serialNumber == serialNumber) + return &device; - { - const juce::ScopedLock sl (incomingPacketLock); - incomingPackets.add (std::move (mb)); - } + return nullptr; + } - triggerAsyncUpdate(); + //============================================================================== + void removeDevice (Block::UID uid) + { + if (const auto info = getDeviceInfoFromUID (uid)) + detector.handleDeviceRemoved (*info); - #if DUMP_BANDWIDTH_STATS - registerBytesIn ((int) dataSize); - #endif + removeDeviceInfo (uid); + removePing (uid); } - void handleAsyncUpdate() override + void updateCurrentDeviceList() { - juce::Array packets; - packets.ensureStorageAllocated (32); + Array toRemove; + //Update known devices + for (int i = currentDeviceInfo.size(); --i >= 0; ) { - const juce::ScopedLock sl (incomingPacketLock); - incomingPackets.swapWith (packets); - } + auto& currentDevice = currentDeviceInfo.getReference (i); - for (auto& packet : packets) - { - auto data = static_cast (packet.getData()); + if (const auto newStatus = getIncomingDeviceStatus (currentDevice.serial)) + { + if (currentDevice.index != newStatus->index) + { + currentDevice.index = newStatus->index; + detector.handleIndexChanged (currentDevice.uid, currentDevice.index); + } - BlocksProtocol::HostPacketDecoder - ::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1); - } - } + if (currentDevice.batteryCharging != newStatus->batteryCharging) + { + currentDevice.batteryCharging = newStatus->batteryCharging; + detector.handleBatteryChargingChanged (currentDevice.uid, currentDevice.batteryCharging); + } - //============================================================================== - BlocksProtocol::VersionNumber getVersionNumber (Block::UID uid) - { - const auto version = versionNumbers.find (uid); - return version == versionNumbers.end() ? BlocksProtocol::VersionNumber() : version->second; - } + if (currentDevice.batteryLevel != newStatus->batteryLevel) + { + currentDevice.batteryLevel = newStatus->batteryLevel; + detector.handleBatteryLevelChanged (currentDevice.uid, currentDevice.batteryLevel); + } + } + else + { + toRemove.add (currentDevice.uid); + } + } - BlocksProtocol::BlockName getName (Block::UID uid) - { - const auto name = blockNames.find (uid); - return name == blockNames.end() ? BlocksProtocol::BlockName() : name->second; - } + for (const auto& uid : toRemove) + removeDevice (uid); - //============================================================================== - const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept - { - for (auto& d : currentDeviceInfo) - if (d.uid == uid) - return &d; + //Add new devices + for (const auto& device : incomingTopologyDevices) + { + const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); - return nullptr; + if (! getDeviceInfoFromUID (uid)) + { + // For backwards compatibility we assume the first device we see in a group is the master and won't change + if (masterBlock == 0) + masterBlock = uid; + + currentDeviceInfo.add ({ uid, + device.index, + device.serialNumber, + BlocksProtocol::VersionNumber(), + BlocksProtocol::BlockName(), + device.batteryLevel, + device.batteryCharging, + masterBlock }); + } + } } + //============================================================================== Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept { if (auto* info = getDeviceInfoFromUID (uid)) return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p); - jassertfalse; + jassertfalse; return { Block::ConnectionPort::DeviceEdge::north, 0 }; } - //============================================================================== - void refreshCurrentDeviceInfo() + BlockDeviceConnection getBlockDeviceConnection (const BlocksProtocol::DeviceConnection& connection) { - currentDeviceInfo.clearQuick(); + BlockDeviceConnection dc; - for (auto& device : currentTopologyDevices) - { - const auto uid = getBlockUIDFromSerialNumber (device.serialNumber); - const auto version = getVersionNumber (uid); - const auto name = getName (uid); + dc.device1 = getDeviceIDFromIndex (connection.device1); + dc.device2 = getDeviceIDFromIndex (connection.device2); - // For backwards compatibility we assume the first device we see in a group is the master and won't change - if (masterBlock == 0) - masterBlock = uid; + if (dc.device1 <= 0 || dc.device2 <= 0) + jassertfalse; - currentDeviceInfo.add ({ uid, - device.index, - device.serialNumber, - version, - name, - masterBlock == uid }); - } + dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, connection.port1); + dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, connection.port2); + + return dc; } - void refreshCurrentDeviceConnections() + void updateCurrentDeviceConnections() { currentDeviceConnections.clearQuick(); + currentDeviceConnections.swapWith (incomingTopologyConnections); - for (auto&& c : currentTopologyConnections) - { - BlockDeviceConnection dc; - dc.device1 = getDeviceIDFromIndex (c.device1); - dc.device2 = getDeviceIDFromIndex (c.device2); - - if (dc.device1 <= 0 || dc.device2 <= 0) - continue; - - dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1); - dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2); - - currentDeviceConnections.add (dc); - } + detector.handleConnectionsChanged(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup) diff --git a/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp b/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp index c338ad1d9c..f63a28b4e2 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_DepreciatedVersionReader.cpp @@ -55,8 +55,8 @@ public: for (size_t i = 1; i < numFirmwareApps; ++i) { - const BlocksVersion highest { asString (highestVersion) }; - const BlocksVersion test { asString ( result[i]) }; + const BlocksVersion highest { highestVersion.asString() }; + const BlocksVersion test { result[i].asString() }; if (highest < test) highestVersion = result[i]; @@ -80,10 +80,21 @@ private: { static constexpr size_t requestSize { 8 }; static constexpr uint8 requests[][requestSize] = {{ 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x00, 0xf7 }, // Main App - { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }, // Stm32 - { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }}; // Bootloader + { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x03, 0xf7 }, // Bootloader + { 0xf0, 0x00, 0x21, 0x10, 0x47, 0x03, 0x01, 0xf7 }}; // Stm32 - deviceConnection.sendMessageToDevice (&requests[currentRequest.get()][0], requestSize); + static const BlocksVersion depreciatedVersion ("0.3.0"); + + if (currentRequest.get() == numFirmwareApps - 1 + && (BlocksVersion (result[0].asString()) >= depreciatedVersion + || BlocksVersion (result[1].asString()) >= depreciatedVersion)) + { + stopTimer(); + } + else + { + deviceConnection.sendMessageToDevice (&requests[currentRequest.get()][0], requestSize); + } } //============================================================================== diff --git a/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp b/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp index 1b0cd03b30..94b97989cf 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_Detector.cpp @@ -23,40 +23,11 @@ namespace juce { -namespace -{ - static bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) noexcept - { - for (auto&& block : blocks) - if (block->uid == uid) - return true; - - return false; - } - - static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept - { - auto deviceVersion = asString (device.version); - return deviceVersion != version && deviceVersion.isNotEmpty(); - } - - static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept - { - jassert (deviceInfo.uid == block.uid); - block.versionNumber = asString (deviceInfo.version); - } - - static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block) - { - jassert (deviceInfo.uid == block.uid); - block.name = asString (deviceInfo.name); - } -} - //============================================================================== /** This is the main singleton object that keeps track of connected blocks */ struct Detector : public juce::ReferenceCountedObject, - private juce::Timer + private juce::Timer, + private juce::AsyncUpdater { using BlockImpl = BlockImplementation; @@ -100,11 +71,10 @@ struct Detector : public juce::ReferenceCountedObject, if (activeTopologySources.isEmpty()) { for (auto& b : currentTopology.blocks) - if (auto bi = BlockImpl::getFrom (*b)) + if (auto bi = BlockImpl::getFrom (b)) bi->sendCommandMessage (BlocksProtocol::endAPIMode); currentTopology = {}; - lastTopology = {}; auto& d = getDefaultDetectorPointer(); @@ -124,76 +94,127 @@ struct Detector : public juce::ReferenceCountedObject, return false; } - const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept + void handleDeviceAdded (const DeviceInfo& info) { - for (auto d : connectedDeviceGroups) - if (auto status = d->getLastStatus (deviceID)) - return status; + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - return nullptr; - } + const auto blockWasRemoved = containsBlockWithUID (blocksToRemove, info.uid); + const auto knownBlock = std::find_if (previouslySeenBlocks.begin(), previouslySeenBlocks.end(), + [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); - void handleTopologyChange() - { - JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + Block::Ptr block; + if (knownBlock != previouslySeenBlocks.end()) { - juce::Array newDeviceInfo; - juce::Array newDeviceConnections; + block = *knownBlock; - for (auto d : connectedDeviceGroups) + if (auto* blockImpl = BlockImpl::getFrom (*block)) { - newDeviceInfo.addArray (d->getCurrentDeviceInfo()); - newDeviceConnections.addArray (d->getCurrentDeviceConnections()); + blockImpl->markReconnected (info); + previouslySeenBlocks.removeObject (block); } + } + else + { + block = new BlockImpl (*this, info); + } - for (int i = currentTopology.blocks.size(); --i >= 0;) - { - auto currentBlock = currentTopology.blocks.getUnchecked (i); + currentTopology.blocks.addIfNotAlreadyThere (block); - auto newDeviceIter = std::find_if (newDeviceInfo.begin(), newDeviceInfo.end(), - [&] (DeviceInfo& info) { return info.uid == currentBlock->uid; }); + if (blockWasRemoved) + { + blocksToUpdate.addIfNotAlreadyThere (block); + blocksToAdd.removeObject (block); + } + else + { + blocksToAdd.addIfNotAlreadyThere (block); + blocksToUpdate.removeObject (block); + } - auto* blockImpl = BlockImpl::getFrom (*currentBlock); + blocksToRemove.removeObject (block); - if (newDeviceIter == newDeviceInfo.end()) - { - if (blockImpl != nullptr) - blockImpl->markDisconnected(); + triggerAsyncUpdate(); + } - disconnectedBlocks.addIfNotAlreadyThere (currentTopology.blocks.removeAndReturn (i).get()); - } - else - { - if (blockImpl != nullptr && blockImpl->wasPowerCycled()) - { - blockImpl->resetPowerCycleFlag(); - blockImpl->markReconnected (*newDeviceIter); - } + void handleDeviceRemoved (const DeviceInfo& info) + { + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - updateCurrentBlockInfo (currentBlock, *newDeviceIter); - } - } + const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), + [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); + + if (blockIt != currentTopology.blocks.end()) + { + const auto block = *blockIt; + + if (auto blockImpl = BlockImpl::getFrom (block)) + blockImpl->markDisconnected(); + + currentTopology.blocks.removeObject (block); + previouslySeenBlocks.addIfNotAlreadyThere (block); + + blocksToRemove.addIfNotAlreadyThere (block); + blocksToUpdate.removeObject (block); + blocksToAdd.removeObject (block); + triggerAsyncUpdate(); + } + } - static const int maxBlocksToSave = 100; + void handleConnectionsChanged() + { + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + triggerAsyncUpdate(); + } + + void handleDeviceUpdated (const DeviceInfo& info) + { + if (containsBlockWithUID (blocksToRemove, info.uid)) + return; - if (disconnectedBlocks.size() > maxBlocksToSave) - disconnectedBlocks.removeRange (0, 2 * (disconnectedBlocks.size() - maxBlocksToSave)); + const auto blockIt = std::find_if (currentTopology.blocks.begin(), currentTopology.blocks.end(), + [uid = info.uid] (Block::Ptr block) { return uid == block->uid; }); - for (auto& info : newDeviceInfo) - if (info.serial.isValid() && ! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial))) - addBlock (info); + if (blockIt != currentTopology.blocks.end()) + { + const auto block = *blockIt; + + if (auto blockImpl = BlockImpl::getFrom (block)) + blockImpl->markReconnected (info); - currentTopology.connections.swapWith (newDeviceConnections); + if (! containsBlockWithUID (blocksToAdd, info.uid)) + { + blocksToUpdate.addIfNotAlreadyThere (block); + triggerAsyncUpdate(); + } } + } + + void handleBatteryChargingChanged (Block::UID deviceID, const BlocksProtocol::BatteryCharging isCharging) + { + if (auto block = currentTopology.getBlockWithUID (deviceID)) + if (auto blockImpl = BlockImpl::getFrom (*block)) + blockImpl->batteryCharging = isCharging; + } + + void handleBatteryLevelChanged (Block::UID deviceID, const BlocksProtocol::BatteryLevel batteryLevel) + { + if (auto block = currentTopology.getBlockWithUID (deviceID)) + if (auto blockImpl = BlockImpl::getFrom (*block)) + blockImpl->batteryLevel = batteryLevel; + } - broadcastTopology(); + void handleIndexChanged (Block::UID deviceID, const BlocksProtocol::TopologyIndex index) + { + if (auto block = currentTopology.getBlockWithUID (deviceID)) + if (auto blockImpl = BlockImpl::getFrom (*block)) + blockImpl->topologyIndex = index; } void notifyBlockIsRestarting (Block::UID deviceID) { for (auto& group : connectedDeviceGroups) - group->notifyBlockIsRestarting (deviceID); + group->handleBlockRestarting (deviceID); } void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const @@ -305,24 +326,11 @@ struct Detector : public juce::ReferenceCountedObject, } //============================================================================== - int getIndexFromDeviceID (Block::UID deviceID) const noexcept - { - for (auto* c : connectedDeviceGroups) - { - auto index = c->getIndexFromDeviceID (deviceID); - - if (index >= 0) - return index; - } - - return -1; - } - template bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const { for (auto* c : connectedDeviceGroups) - if (c->getIndexFromDeviceID (deviceID) >= 0) + if (c->contains (deviceID)) return c->sendMessageToDevice (builder); return false; @@ -341,11 +349,8 @@ struct Detector : public juce::ReferenceCountedObject, { for (const auto& d : connectedDeviceGroups) { - for (const auto& info : d->getCurrentDeviceInfo()) - { - if (info.uid == b.uid) - return d->getDeviceConnection(); - } + if (d->contains (b.uid)) + return d->getDeviceConnection(); } return nullptr; @@ -355,11 +360,8 @@ struct Detector : public juce::ReferenceCountedObject, { for (const auto& d : connectedDeviceGroups) { - for (const auto& info : d->getCurrentDeviceInfo()) - { - if (info.uid == b.uid) - return d->getDeviceConnection(); - } + if (d->contains (b.uid)) + return d->getDeviceConnection(); } return nullptr; @@ -370,10 +372,11 @@ struct Detector : public juce::ReferenceCountedObject, juce::Array activeTopologySources; - BlockTopology currentTopology, lastTopology; - juce::ReferenceCountedArray disconnectedBlocks; + BlockTopology currentTopology; private: + Block::Array previouslySeenBlocks, blocksToAdd, blocksToRemove, blocksToUpdate; + void timerCallback() override { startTimer (1500); @@ -384,21 +387,20 @@ private: handleDevicesAdded (detectedDevices); } - void handleDevicesRemoved (const juce::StringArray& detectedDevices) + bool containsBlockWithUID (const juce::Block::Array& blocks, juce::Block::UID uid) { - bool anyDevicesRemoved = false; + for (const auto block : blocks) + if (block->uid == uid) + return true; + + return false; + } + void handleDevicesRemoved (const juce::StringArray& detectedDevices) + { for (int i = connectedDeviceGroups.size(); --i >= 0;) - { if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices)) - { connectedDeviceGroups.remove (i); - anyDevicesRemoved = true; - } - } - - if (anyDevicesRemoved) - handleTopologyChange(); } void handleDevicesAdded (const juce::StringArray& detectedDevices) @@ -424,57 +426,9 @@ private: return false; } - void addBlock (DeviceInfo info) - { - if (! reactivateBlockIfKnown (info)) - addNewBlock (info); - } - - bool reactivateBlockIfKnown (DeviceInfo info) - { - const auto uid = getBlockUIDFromSerialNumber (info.serial); - - for (int i = disconnectedBlocks.size(); --i >= 0;) - { - if (uid != disconnectedBlocks.getUnchecked (i)->uid) - continue; - - auto block = disconnectedBlocks.removeAndReturn (i); - - if (auto* blockImpl = BlockImpl::getFrom (*block)) - { - blockImpl->markReconnected (info); - currentTopology.blocks.add (block); - return true; - } - } - - return false; - } - - void addNewBlock (DeviceInfo info) - { - currentTopology.blocks.add (new BlockImpl (info.serial, *this, info.version, - info.name, info.isMaster)); - } - - void updateCurrentBlockInfo (Block::Ptr blockToUpdate, DeviceInfo& updatedInfo) - { - jassert (updatedInfo.uid == blockToUpdate->uid); - - if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber)) - setVersionNumberForBlock (updatedInfo, *blockToUpdate); - - if (updatedInfo.name.isNotEmpty()) - setNameForBlock (updatedInfo, *blockToUpdate); - - if (updatedInfo.isMaster != blockToUpdate->isMasterBlock()) - BlockImpl::getFrom (*blockToUpdate)->setToMaster (updatedInfo.isMaster); - } - BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept { - if (auto&& block = currentTopology.getBlockWithUID (deviceID)) + if (auto block = currentTopology.getBlockWithUID (deviceID)) return BlockImpl::getFrom (*block); return nullptr; @@ -484,31 +438,41 @@ private: //============================================================================== /** This is a friend of the BlocksImplementation that will scan and set the - physical positions of the blocks */ - struct BlocksTraverser + physical positions of the blocks. + + Returns an array of blocks that were updated. + */ + struct BlocksLayoutTraverser { - void traverseBlockArray (const BlockTopology& topology) + static Block::Array updateBlocks (const BlockTopology& topology) { + Block::Array updated; juce::Array visited; for (auto& block : topology.blocks) { if (block->isMasterBlock() && ! visited.contains (block->uid)) { - if (auto* bi = dynamic_cast (block)) + if (auto* bi = BlockImpl::getFrom (block)) { - bi->masterUID = {}; - bi->position = {}; - bi->rotation = 0; + if (bi->rotation != 0 || bi->position.first != 0 || bi->position.second != 0) + { + bi->rotation = 0; + bi->position = {}; + updated.add (block); + } } - layoutNeighbours (*block, topology, block->uid, visited); + layoutNeighbours (*block, topology, visited, updated); } } + + return updated; } + private: // returns the distance from corner clockwise - int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index) + static int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index) { if (block->getType() == Block::seaboardBlock) { @@ -533,7 +497,7 @@ private: } // returns how often north needs to rotate by 90 degrees - int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge) + static int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge) { switch (edge) { @@ -547,8 +511,10 @@ private: return 0; } - void layoutNeighbours (Block::Ptr block, const BlockTopology& topology, - Block::UID masterUid, juce::Array& visited) + static void layoutNeighbours (const Block::Ptr block, + const BlockTopology& topology, + juce::Array& visited, + Block::Array& updated) { visited.add (block->uid); @@ -568,10 +534,17 @@ private: const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index); const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index); - neighbour->masterUID = masterUid; - neighbour->rotation = (2 + block->getRotation() - + getRotationForEdge (myPort.edge) - - getRotationForEdge (theirPort.edge)) % 4; + { + const auto neighbourRotation = (2 + block->getRotation() + + getRotationForEdge (myPort.edge) + - getRotationForEdge (theirPort.edge)) % 4; + + if (neighbour->rotation != neighbourRotation) + { + neighbour->rotation = neighbourRotation; + updated.addIfNotAlreadyThere (neighbourPtr); + } + } std::pair delta; const auto theirBounds = neighbour->getBlockAreaWithinLayout(); @@ -592,10 +565,22 @@ private: break; } - neighbour->position = { myBounds.x + delta.first, myBounds.y + delta.second }; - } + { + const auto neighbourX = myBounds.x + delta.first; + const auto neighbourY = myBounds.y + delta.second; - layoutNeighbours (neighbourPtr, topology, masterUid, visited); + if (neighbour->position.first != neighbourX + || neighbour->position.second != neighbourY) + { + neighbour->position.first = neighbourX; + neighbour->position.second = neighbourY; + + updated.addIfNotAlreadyThere (neighbourPtr); + } + } + + layoutNeighbours (neighbourPtr, topology, visited, updated); + } } } } @@ -674,22 +659,64 @@ private: #endif //============================================================================== - void broadcastTopology() + void updateBlockPositions() { - if (currentTopology != lastTopology) + const auto updated = BlocksLayoutTraverser::updateBlocks (currentTopology); + + for (const auto block : updated) { - lastTopology = currentTopology; + if (containsBlockWithUID (blocksToAdd, block->uid) || containsBlockWithUID (blocksToRemove, block->uid)) + continue; - BlocksTraverser traverser; - traverser.traverseBlockArray (currentTopology); + blocksToUpdate.addIfNotAlreadyThere (block); + } + } - for (auto* d : activeTopologySources) - d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); }); + void updateBlockConnections() + { + currentTopology.connections.clearQuick(); + + for (auto d : connectedDeviceGroups) + currentTopology.connections.addArray (d->getCurrentDeviceConnections()); + } + + void handleAsyncUpdate() override + { + updateBlockConnections(); + updateBlockPositions(); + + for (auto* d : activeTopologySources) + { + for (const auto block : blocksToAdd) + d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockAdded (block); }); + for (const auto block : blocksToRemove) + d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockRemoved (block); }); + + for (const auto block : blocksToUpdate) + d->listeners.call ([&block] (TopologySource::Listener& l) { l.blockUpdated (block); }); + } + + const auto topologyChanged = blocksToAdd.size() > 0 || blocksToRemove.size() > 0 || blocksToUpdate.size() > 0; + + if (topologyChanged) + { #if DUMP_TOPOLOGY - dumpTopology (lastTopology); + dumpTopology (currentTopology); #endif + + for (auto* d : activeTopologySources) + d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); }); } + + blocksToUpdate.clear(); + blocksToAdd.clear(); + blocksToRemove.clear(); + + static const int maxBlocksToSave = 100; + + if (previouslySeenBlocks.size() > maxBlocksToSave) + previouslySeenBlocks.removeRange (0, 2 * (previouslySeenBlocks.size() - maxBlocksToSave)); } //============================================================================== diff --git a/modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp b/modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp index 3eaffb916f..e161b38517 100644 --- a/modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp +++ b/modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp @@ -26,9 +26,13 @@ namespace juce struct DeviceInfo { // VS2015 requires a constructor to avoid aggregate initialization - DeviceInfo (Block::UID buid, BlocksProtocol::TopologyIndex tidx, BlocksProtocol::BlockSerialNumber s, - BlocksProtocol::VersionNumber v, BlocksProtocol::BlockName n, bool master = false) - : uid (buid), index (tidx), serial (s), version (v), name (n), isMaster (master) + DeviceInfo (Block::UID buid, BlocksProtocol::TopologyIndex tidx, + BlocksProtocol::BlockSerialNumber s, BlocksProtocol::VersionNumber v, + BlocksProtocol::BlockName n, BlocksProtocol::BatteryLevel level, + BlocksProtocol::BatteryCharging charging, Block::UID master) + : uid (buid), index (tidx), serial (s), version (v), name (n), + batteryLevel (level), batteryCharging (charging), masterUid (master), + isMaster (uid == master) { } @@ -37,6 +41,9 @@ struct DeviceInfo BlocksProtocol::BlockSerialNumber serial; BlocksProtocol::VersionNumber version; BlocksProtocol::BlockName name; + BlocksProtocol::BatteryLevel batteryLevel; + BlocksProtocol::BatteryCharging batteryCharging; + Block::UID masterUid; bool isMaster {}; }; diff --git a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp index bc73ce3da0..521473e048 100644 --- a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp +++ b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp @@ -47,17 +47,6 @@ #include "internal/juce_BandwidthStatsLogger.cpp" #endif -namespace -{ - /** Helper function to create juce::String from BlockStringData */ - template - juce::String asString (juce::BlocksProtocol::BlockStringData blockString) - { - return { reinterpret_cast (blockString.data), - juce::jmin (sizeof (blockString.data), static_cast (blockString.length))}; - } -} - #include "internal/juce_MidiDeviceConnection.cpp" #include "internal/juce_MIDIDeviceDetector.cpp" #include "internal/juce_DeviceInfo.cpp" diff --git a/modules/juce_blocks_basics/topology/juce_TopologySource.h b/modules/juce_blocks_basics/topology/juce_TopologySource.h index 224e1e7a9d..5b95c7b400 100644 --- a/modules/juce_blocks_basics/topology/juce_TopologySource.h +++ b/modules/juce_blocks_basics/topology/juce_TopologySource.h @@ -48,7 +48,20 @@ public: struct Listener { virtual ~Listener() = default; - virtual void topologyChanged() = 0; + + /** Called for any change in topology - devices changed, connections changed, etc. */ + virtual void topologyChanged() {} + + /** Called when a new block is added to the topology. */ + virtual void blockAdded (const Block::Ptr) {} + + /** Called when a block is removed from the topology. */ + virtual void blockRemoved (const Block::Ptr) {} + + /** Called when a known block is updated. + This could be becasue details have been reveived asyncroniously. E.g. Block name. + */ + virtual void blockUpdated (const Block::Ptr) {} }; void addListener (Listener* l) { listeners.add (l); }