From 8bf2bf16d32093d71776f3cb744532a269cc2617 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sun, 17 May 2026 11:02:34 -0400 Subject: [PATCH] EXPERIMENTAL: this is a fundamental change to the function of ChannelLookup, instead of maintaining a list in which we add or remove elements as they are available or in use, we will maintain a mapped channel ID to index bit array, where each bit index within the array of bits represents the availability state of the channel, this should make it far more stable to mark a channel as free or allocated instead of manipulating an array everytime; --- src/common/lookups/AffiliationLookup.cpp | 12 +- src/common/lookups/ChannelLookup.cpp | 228 ++++++++++++++++-- src/common/lookups/ChannelLookup.h | 75 ++++-- src/host/Host.Config.cpp | 4 +- src/host/dmr/lookups/DMRAffiliationLookup.cpp | 6 +- 5 files changed, 282 insertions(+), 43 deletions(-) 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--;