diff --git a/src/common/lookups/AffiliationLookup.cpp b/src/common/lookups/AffiliationLookup.cpp index 124382c5..3d924bca 100644 --- a/src/common/lookups/AffiliationLookup.cpp +++ b/src/common/lookups/AffiliationLookup.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2022,2024,2026 Bryan Biedenkapp, N2PLL * */ #include "lookups/AffiliationLookup.h" @@ -341,12 +341,8 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi return false; } - if (!m_chLookup->isRFChAvailable()) { - return false; - } - - uint32_t chNo = m_chLookup->getFirstRFChannel(); - if (!m_chLookup->removeRFCh(chNo)) { + uint32_t chNo = 0U; + if (!m_chLookup->takeFirstRFChannel(chNo)) { return false; } @@ -451,7 +447,7 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_netGrantedTable.erase(dstId); if (m_chLookup != nullptr) { - m_chLookup->addRFCh(chNo, true); + m_chLookup->freeRFCh(chNo); } if (m_rfGrantChCnt > 0U) { diff --git a/src/common/lookups/ChannelLookup.cpp b/src/common/lookups/ChannelLookup.cpp index 5f081e23..0a5f48bb 100644 --- a/src/common/lookups/ChannelLookup.cpp +++ b/src/common/lookups/ChannelLookup.cpp @@ -4,12 +4,14 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * */ #include "lookups/ChannelLookup.h" #include "Log.h" +#include + using namespace lookups; // --------------------------------------------------------------------------- @@ -19,10 +21,15 @@ using namespace lookups; /* Initializes a new instance of the ChannelLookup class. */ ChannelLookup::ChannelLookup() : - m_rfChTable(), + m_rfChMutex(), + m_idxToCh(), + m_chToIdx(), + m_freeBits(), m_rfChDataTable() { - m_rfChTable.clear(); + m_idxToCh.clear(); + m_chToIdx.clear(); + m_freeBits.clear(); } /* Finalizes a instance of the ChannelLookup class. */ @@ -47,40 +54,229 @@ VoiceChData ChannelLookup::getRFChData(uint32_t chNo) const return data; } -/* Helper to add a RF channel. */ +/* Helper to get first available channel number. */ + +uint32_t ChannelLookup::getFirstRFChannel() const +{ + std::lock_guard lock(m_rfChMutex); + + uint32_t idx = 0U; + if (!findFirstClearBit(idx) || idx >= m_idxToCh.size()) { + return 0U; + } + + return m_idxToCh[idx]; +} + +/* Helper to atomically take first available RF channel. */ + +bool ChannelLookup::takeFirstRFChannel(uint32_t& chNo) +{ + chNo = 0U; + + std::lock_guard lock(m_rfChMutex); + + uint32_t idx = 0U; + if (!findFirstClearBit(idx) || idx >= m_idxToCh.size()) { + return false; + } + + setBit(idx); + chNo = m_idxToCh[idx]; + + return true; +} + +/* Helper to get the count of RF channels. */ + +uint8_t ChannelLookup::rfChSize() const +{ + std::lock_guard lock(m_rfChMutex); + + uint32_t count = 0U; + for (uint32_t idx = 0U; idx < m_idxToCh.size(); ++idx) { + count++; + } + + if (count > 255U) { + return 255U; + } + + return (uint8_t)count; +} + +/* Helper to get RF channels table. */ + +std::vector ChannelLookup::rfChTable() const +{ + std::lock_guard lock(m_rfChMutex); + + std::vector channels; + channels.reserve(m_idxToCh.size()); + + for (uint32_t idx = 0U; idx < m_idxToCh.size(); ++idx) { + channels.push_back(m_idxToCh[idx]); + } + + return channels; +} + +/* Helper to initialize a RF channel. */ -bool ChannelLookup::addRFCh(uint32_t chNo, bool force) +bool ChannelLookup::initializeRFCh(uint32_t chNo) { if (chNo == 0U) { return false; } - if (force) { - m_rfChTable.push_back(chNo); + std::lock_guard lock(m_rfChMutex); + + // check if channel already exists, if not add it and mark as free (clear bit) + auto it = m_chToIdx.find(chNo); + if (it == m_chToIdx.end()) { + uint32_t idx = (uint32_t)m_idxToCh.size(); + m_chToIdx[chNo] = idx; + m_idxToCh.push_back(chNo); + ensureBitCapacity(idx); + clearBit(idx); return true; } - auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); - if (it == m_rfChTable.end()) { - m_rfChTable.push_back(chNo); + return false; +} + +/* Helper to mark a RF channel allocated for use. */ + +bool ChannelLookup::allocRFCh(uint32_t chNo) +{ + if (chNo == 0U) { + return false; + } + + std::lock_guard lock(m_rfChMutex); + + // check if channel already exists, if not add it and mark allocated (set bit) + auto it = m_chToIdx.find(chNo); + if (it == m_chToIdx.end()) { + uint32_t idx = (uint32_t)m_idxToCh.size(); + m_chToIdx[chNo] = idx; + m_idxToCh.push_back(chNo); + ensureBitCapacity(idx); + setBit(idx); + return true; + } + + // channel exists, set bit if currently free + uint32_t idx = it->second; + if (!isBitSet(idx)) { + setBit(idx); return true; } return false; } -/* Helper to remove a RF channel. */ +/* Helper to mark a RF channel as free for use. */ -bool ChannelLookup::removeRFCh(uint32_t chNo) +bool ChannelLookup::freeRFCh(uint32_t chNo) { if (chNo == 0U) { return false; } - auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo); - if (it != m_rfChTable.end()) { - m_rfChTable.erase(it); - return true; + std::lock_guard lock(m_rfChMutex); + + // check if channel exists and bit is set (allocated), then clear to free + auto it = m_chToIdx.find(chNo); + if (it == m_chToIdx.end()) { + return false; + } + + // channel exists, clear bit if allocated + uint32_t idx = it->second; + if (!isBitSet(idx)) { + return false; + } + + clearBit(idx); + + return true; +} + +/* Helper to determine if there are any RF channels available. */ + +bool ChannelLookup::isRFChAvailable() const +{ + std::lock_guard lock(m_rfChMutex); + + for (uint32_t idx = 0U; idx < m_idxToCh.size(); ++idx) { + if (!isBitSet(idx)) { + return true; + } + } + + return false; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/* Ensures bitset has enough words for the given index. */ + +void ChannelLookup::ensureBitCapacity(uint32_t idx) +{ + size_t wordIdx = (size_t)(idx / BITS_PER_WORD); + if (wordIdx >= m_freeBits.size()) { + m_freeBits.resize(wordIdx + 1U, 0ULL); + } +} + +/* Determines if the bit for index is currently set. */ + +bool ChannelLookup::isBitSet(uint32_t idx) const +{ + size_t wordIdx = (size_t)(idx / BITS_PER_WORD); + if (wordIdx >= m_freeBits.size()) { + return false; + } + + uint32_t bit = idx % BITS_PER_WORD; + return ((m_freeBits[wordIdx] >> bit) & 1ULL) != 0ULL; +} + +/* Sets the bit for index. */ + +void ChannelLookup::setBit(uint32_t idx) +{ + ensureBitCapacity(idx); + size_t wordIdx = (size_t)(idx / BITS_PER_WORD); + uint32_t bit = idx % BITS_PER_WORD; + m_freeBits[wordIdx] |= (1ULL << bit); +} + +/* Clears the bit for index. */ + +void ChannelLookup::clearBit(uint32_t idx) +{ + size_t wordIdx = (size_t)(idx / BITS_PER_WORD); + if (wordIdx >= m_freeBits.size()) { + return; + } + + uint32_t bit = idx % BITS_PER_WORD; + m_freeBits[wordIdx] &= ~(1ULL << bit); +} + +/* Finds the first available (clear) channel bit index. */ + +bool ChannelLookup::findFirstClearBit(uint32_t& idx) const +{ + for (uint32_t candidateIdx = 0U; candidateIdx < m_idxToCh.size(); ++candidateIdx) { + if (!isBitSet(candidateIdx)) { + idx = candidateIdx; + return true; + } } return false; diff --git a/src/common/lookups/ChannelLookup.h b/src/common/lookups/ChannelLookup.h index 2c442093..e1114c7c 100644 --- a/src/common/lookups/ChannelLookup.h +++ b/src/common/lookups/ChannelLookup.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * -* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -21,11 +21,14 @@ #define __CHANNEL_LOOKUP_H__ #include "common/Defines.h" -#include "common/concurrent/vector.h" #include "common/concurrent/unordered_map.h" #include "common/Timer.h" #include +#include +#include +#include +#include namespace lookups { @@ -232,40 +235,84 @@ namespace lookups * @brief Helper to get first available channel number. * @returns uint32_t First available channel number. */ - uint32_t getFirstRFChannel() const { return m_rfChTable.at(0); } + uint32_t getFirstRFChannel() const; + /** + * @brief Helper to atomically acquire the first available channel number. + * @param chNo Acquired channel number. + * @returns bool True, if a channel was acquired, otherwise false. + */ + bool takeFirstRFChannel(uint32_t& chNo); /** * @brief Gets the count of RF channels. * @returns uint8_t Total count of RF channels. */ - uint8_t rfChSize() const { return m_rfChTable.size(); } + uint8_t rfChSize() const; /** * @brief Gets the RF channels table. * @returns std::vector RF channel table. */ - std::vector rfChTable() const { return m_rfChTable.get(); } + std::vector rfChTable() const; + /** + * @brief Helper to initialize a RF channel. + * @param chNo Channel Number. + * @returns bool True, if channel initialized, otherwise false. + */ + bool initializeRFCh(uint32_t chNo); /** - * @brief Helper to add a RF channel. + * @brief Helper to mark a RF channel allocated for use. * @param chNo Channel Number. - * @param force Flag indicating the channel should be forcibly added. - * @returns bool True, if channel added, otherwise false. + * @returns bool True, if channel allocated, otherwise false. */ - bool addRFCh(uint32_t chNo, bool force = false); + bool allocRFCh(uint32_t chNo); /** - * @brief Helper to remove a RF channel. + * @brief Helper to mark a RF channel as free for use. * @param chNo Channel Number. - * @returns bool True, if channel remove, otherwise false. + * @returns bool True, if channel freed, otherwise false. */ - bool removeRFCh(uint32_t chNo); + bool freeRFCh(uint32_t chNo); /** * @brief Helper to determine if there are any RF channels available.. * @returns bool True, if any RF channels are available for use, otherwise false. */ - bool isRFChAvailable() const { return !m_rfChTable.empty(); } + bool isRFChAvailable() const; /** @} */ private: - concurrent::vector m_rfChTable; + static constexpr uint32_t BITS_PER_WORD = 64U; + + /** + * @brief Ensures bitset has enough words for the given index. + * @param idx Index of the bit. + */ + void ensureBitCapacity(uint32_t idx); + /** + * @brief Determines if the bit for index is currently set (allocated). + * @param idx Bit index. + * @returns bool True, if the bit is set, otherwise false. + */ + bool isBitSet(uint32_t idx) const; + /** + * @brief Sets the bit for index. + * @param idx Bit index. + */ + void setBit(uint32_t idx); + /** + * @brief Clears the bit for index. + * @param idx Bit index. + */ + void clearBit(uint32_t idx); + /** + * @brief Finds the first available (clear) channel bit index. + * @param idx Index of the first clear bit. + * @returns bool True, if an available channel bit was found, otherwise false. + */ + bool findFirstClearBit(uint32_t& idx) const; + + mutable std::mutex m_rfChMutex; + std::vector m_idxToCh; + std::unordered_map m_chToIdx; + std::vector m_freeBits; concurrent::unordered_map m_rfChDataTable; }; } // namespace lookups diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index 618a80e1..7a995e82 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -304,13 +304,13 @@ bool Host::readParams() VoiceChData data = VoiceChData(chId, chNo, rxChId, rxChNo, rpcApiAddress, rpcApiPort, rpcApiPassword); m_channelLookup->setRFChData(chNo, data); - m_channelLookup->addRFCh(chNo); + m_channelLookup->initializeRFCh(chNo); } else { ::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X (%u-%u) RPC Address %s:%u", chId, chNo, chId, chNo, rpcApiAddress.c_str(), rpcApiPort); VoiceChData data = VoiceChData(chId, chNo, rpcApiAddress, rpcApiPort, rpcApiPassword); m_channelLookup->setRFChData(chNo, data); - m_channelLookup->addRFCh(chNo); + m_channelLookup->initializeRFCh(chNo); } } diff --git a/src/host/dmr/lookups/DMRAffiliationLookup.cpp b/src/host/dmr/lookups/DMRAffiliationLookup.cpp index 336e59fa..34099a2b 100644 --- a/src/host/dmr/lookups/DMRAffiliationLookup.cpp +++ b/src/host/dmr/lookups/DMRAffiliationLookup.cpp @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ #include "common/Log.h" @@ -62,7 +62,7 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s } if (getAvailableSlotForChannel(chNo) == 0U || chNo == m_tsccChNo) { - m_chLookup->removeRFCh(chNo); + m_chLookup->allocRFCh(chNo); } __lock(); @@ -136,7 +136,7 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) m_grantChSlotTable.erase(dstId); m_netGrantedTable.erase(dstId); - m_chLookup->addRFCh(chNo); + m_chLookup->freeRFCh(chNo); if (m_rfGrantChCnt > 0U) { m_rfGrantChCnt--;