From bce3ca41b6c0860bb3f69f478c6840938075d57b Mon Sep 17 00:00:00 2001 From: Consti10 Date: Fri, 16 Feb 2024 01:56:43 +0100 Subject: [PATCH] 2.5.4-alpha (#659) * add raw parse & decode * add raw parse & decode * fix weird malloc bug (hopefully) that crashes tcp connection from time to time. work on raw * fix weird malloc bug (hopefully) that crashes tcp connection from time to time. work on raw * fix weird malloc bug (hopefully) that crashes tcp connection from time to time. work on raw * fix scale to fit grayed out on android * back to rtp for now * Improve Camera selection & camera (AIR CAM1, AIR CAM2) user interface (Hide full param set) * user interface- improve ui of param set when connection is lost, generify the (big) settings popup, work on camera selection * user interface- improve ui of param set when connection is lost, generify the (big) settings popup, work on camera selection * user interface- improve ui of param set when connection is lost, generify the (big) settings popup, work on camera selection * more verbose camera param names * improve connection mode auto / tcp connection (auto reconnect on dead server) * improve connection mode auto / tcp connection (auto reconnect on dead server) * experimental - add auto fetch params functionality * improve params * can't really remember all the things done ... * can't really remember all the things done ... * document a few more params * fix a few comp warnings * fix a few comp warnings * fix a few comp warnings * make soc status widget generic to reduce duplicated code * add platform type, improve UI * add dev overlay (stats), add preliminary joystick support for setting frequency, ... * improve joystick menu, allow changing frequency on air only * improve busy indicator, and joy * use the quick panel as a new element only for joystick for now * finalize joystick edit values element * fix icon of gnd (CPU) status widget * Finalize autofetch of params * uncomment Quick panel for now * A ton of things: 1) async 'interface' for qml -> cpp param -> qml 2) Generic UI element for mavlink int-choices 3) Use this UI element in the sidebar where appropriate * A ton of things: 1) async 'interface' for qml -> cpp param -> qml 2) Generic UI element for mavlink int-choices 3) Use this UI element in the sidebar where appropriate * A ton of things: 1) async 'interface' for qml -> cpp param -> qml 2) Generic UI element for mavlink int-choices 3) Use this UI element in the sidebar where appropriate * A ton of things: 1) async 'interface' for qml -> cpp param -> qml 2) Generic UI element for mavlink int-choices 3) Use this UI element in the sidebar where appropriate * A ton of things: 1) async 'interface' for qml -> cpp param -> qml 2) Generic UI element for mavlink int-choices 3) Use this UI element in the sidebar where appropriate * improve param * add setting 5180Mhz / lowband for pretty much norbert only ;) * sidebar generify * sidebar generify - make it (almost ;) joystick navigatable, fix a few other issues * add joystick navigation to sidebar * add joystick navigation to sidebar * finalize sidebar, for now ;) @Raphael * add option to log the param set result (async) * override qopenhd batt n cells setting from openhd if set * fix a few minor quirks,show radiation icon instead of annoying tx errors log message * finalize FC_BATT_N_CELLS param * lowband - use 40Mhz spacing, show everything in the range of 5180 - 5300 * Frequency element - implement disarm requirement (can be overridden) * Generify - mavlink choices now also supports string, so we can use it for the reslution_fps * default camera choices * default camera resolution choice(s) * default camera resolution choice(s) * Fix the annoying TCP cpu usage - if we are running on the ground station itself, we don't need all this autoconnect jam ;) * Fix the annoying TCP cpu usage - if we are running on the ground station itself, we don't need all this autoconnect jam ;) * i disabled a few checks for testing way back here... * update mavlink * update mavlink * cleanup, also replace moodycamel queue with simple thread safe queue using std::condition_variable * cleanup, also replace moodycamel queue with simple thread safe queue using std::condition_variable * cleanup, also replace moodycamel queue with simple thread safe queue using std::condition_variable * generify rc channels element * Sidebar - add flips. Add busy indicator for a restarting camera. Threadsafe queue, optimize mutex congestion,too. * fix the sidebar channels * typos * experiment - OSD widget presets. uncomment for now * experiment - OSD widget presets. uncomment for now * use proper model for resolutions inside sidebar * use proper model for resolutions inside sidebar * use proper model for resolutions inside sidebar * use proper model for resolutions inside sidebar * use proper model for resolutions inside sidebar * fix weird binding loops * fix ui bug where camera selection dialoque shown for rock is incorrect * show not available if the param is not exposed for a setting (which means the HW does not support changing it) * wb max fec blk size changed in openhd --- QOpenHD.pro | 2 +- app/common/Helper.hpp | 138 - app/common/ThreadsafeQueue.hpp | 59 + .../concurrentqueue/blockingconcurrentqueue.h | 582 --- .../concurrentqueue/concurrentqueue.h | 3747 ----------------- .../concurrentqueue/lightweightsemaphore.h | 425 -- .../moodycamel/readerwriterqueue/atomicops.h | 761 ---- .../readerwritercircularbuffer.h | 288 -- app/telemetry/MavlinkTelemetry.cpp | 148 +- app/telemetry/MavlinkTelemetry.h | 17 +- app/telemetry/action/impl/xparam.cpp | 10 +- app/telemetry/connection/mavlinkchannel.cpp | 25 + app/telemetry/connection/mavlinkchannel.h | 21 + app/telemetry/connection/tcp_connection.cpp | 111 +- app/telemetry/connection/tcp_connection.h | 35 +- app/telemetry/connection/udp_connection.cpp | 32 +- app/telemetry/connection/udp_connection.h | 8 +- app/telemetry/models/aohdsystem.cpp | 88 +- app/telemetry/models/aohdsystem.h | 9 +- app/telemetry/models/camerastreammodel.cpp | 57 +- app/telemetry/models/camerastreammodel.h | 8 +- app/telemetry/models/fcmavlinksystem.cpp | 2 +- app/telemetry/models/openhd_core/README.md | 1 + app/telemetry/models/openhd_core/camera.hpp | 327 ++ app/telemetry/models/openhd_core/platform.hpp | 56 + app/telemetry/models/wificard.cpp | 3 + app/telemetry/settings/documentedparam.cpp | 164 +- .../settings/mavlinksettingsmodel.cpp | 191 +- app/telemetry/settings/mavlinksettingsmodel.h | 30 +- .../settings/wblinksettingshelper.cpp | 8 +- app/telemetry/settings/wifi_channel.h | 418 +- app/telemetry/telemetry.pri | 4 + app/util/qopenhd.cpp | 23 + app/util/qopenhd.h | 9 +- app/videostreaming/android/lowlagdecoder.cpp | 16 +- app/videostreaming/android/lowlagdecoder.h | 4 +- .../avcodec/avcodec_decoder.cpp | 5 + app/videostreaming/avcodec/avcodec_decoder.h | 12 +- .../vscommon/custom/rawreceiver.cpp | 199 + .../vscommon/custom/rawreceiver.h | 55 + .../vscommon/decodingstatistcs.cpp | 9 +- .../vscommon/decodingstatistcs.h | 3 +- ...yFrameFinder.hpp => CodecConfigFinder.hpp} | 12 +- app/videostreaming/vscommon/nalu/NALU.hpp | 16 + app/videostreaming/vscommon/rtp/ParseRTP.cpp | 2 +- .../vscommon/rtp/rtpreceiver.cpp | 35 +- app/videostreaming/vscommon/rtp/rtpreceiver.h | 9 +- app/videostreaming/vscommon/vscommon.pri | 8 +- lib/mavlink-headers | 2 +- qml/main.qml | 22 +- qml/qml.qrc | 52 +- qml/ui/AnyParamBusyIndicator.qml | 76 + qml/ui/HUDOverlayGrid.qml | 35 +- qml/ui/configpopup/ConfigPopup.qml | 20 + .../dev/AppDeveloperStatsPanel.qml | 27 +- .../openhd_settings/ChooseCameraDialoque.qml | 354 ++ .../ChooseResolutionDialoque.qml | 209 + .../openhd_settings/LinkQuickPanel.qml | 9 +- .../openhd_settings/MavlinkParamEditor.qml | 127 +- .../openhd_settings/MavlinkParamPanel.qml | 259 +- .../MavlinkParamValueEditElement.qml | 63 + .../openhd_settings/OHDSettingsPanel.qml | 6 + .../openhd_settings/PopupAnalyzeChannels.qml | 57 +- .../openhd_settings/PopupBigGeneric.qml | 84 + .../openhd_settings/PopupEnableSTBCLDPC.qml | 45 +- .../openhd_settings/PopupScanChannels.qml | 58 +- .../openhd_settings/PopupTxPowerEditor.qml | 50 +- .../qopenhd_settings/AppDevSettingsView.qml | 14 + .../AppGeneralSettingsView.qml | 74 +- .../qopenhd_settings/AppVideoSettingsView.qml | 3 +- .../AppWidgetSettingsView.qml | 129 +- qml/ui/configpopup/status/PanelStatus.qml | 4 +- .../status/StatusCardBodyOpenHD.qml | 8 +- qml/ui/elements/AppSettings.qml | 3 + qml/ui/elements/BaseHeaderItem.qml | 29 + qml/ui/elements/BaseHeaderItem2.qml | 60 + qml/ui/elements/BigClickableText.qml | 28 + qml/ui/elements/ButtonIconGear.qml | 10 + qml/ui/elements/SettingBaseElement2.qml | 106 + qml/ui/elements/SettingsCategory.qml | 45 +- .../elements/SimpleAdvancedChoiceElement.qml | 39 + qml/ui/sidebar/BaseJoyEditElement.qml | 167 + qml/ui/sidebar/EditChannelWidthElement.qml | 58 + qml/ui/sidebar/EditFrequencyElement.qml | 123 + qml/ui/sidebar/EditRateElement.qml | 55 + qml/ui/sidebar/GoBackElement.qml | 46 + qml/ui/sidebar/InfoElement.qml | 30 + qml/ui/sidebar/MavlinkChoiceElement.qml | 316 ++ qml/ui/sidebar/MavlinkSwitch.qml | 78 + qml/ui/sidebar/Panel1Link.qml | 46 + qml/ui/sidebar/Panel2Video.qml | 110 + qml/ui/sidebar/Panel3Camera.qml | 75 + qml/ui/sidebar/Panel4Recording.qml | 54 + qml/ui/sidebar/Panel5RC.qml | 57 + qml/ui/sidebar/Panel6Misc.qml | 51 + qml/ui/sidebar/Panel7Status.qml | 211 + qml/ui/sidebar/RCChannelView.qml | 75 + qml/ui/sidebar/SideBarBasePanel.qml | 46 + qml/ui/sidebar/SideBarMain.qml | 244 ++ qml/ui/sidebar/SidebarStackButton.qml | 90 + qml/ui/sidebar/TriangleButton.qml | 53 + qml/ui/widgets/BaseWidget.qml | 7 +- qml/ui/widgets/DevStreamingInfo.qml | 57 + qml/ui/widgets/DevStreamingInfoText.qml | 29 + qml/ui/widgets/LinkDownRSSIWidget.qml | 23 + qml/ui/widgets/MessageHUD.qml | 1 + qml/ui/widgets/SOCStatusWidgetAir.qml | 446 +- qml/ui/widgets/SOCStatusWidgetGeneric.qml | 493 +++ qml/ui/widgets/SOCStatusWidgetGround.qml | 446 +- qml/ui/widgets/Sidebar.qml | 1112 ----- qml/ui/widgets/SpeedSecondWidget.qml | 2 +- qml/ui/widgets/SpeedWidget.qml | 2 +- qml/ui/widgets/VideoBitrateWidgetGeneric.qml | 92 +- qml/ui/widgets/WBLinkRateControlWidget.qml | 24 +- 114 files changed, 6081 insertions(+), 8937 deletions(-) delete mode 100644 app/common/Helper.hpp create mode 100644 app/common/ThreadsafeQueue.hpp delete mode 100644 app/common/moodycamel/concurrentqueue/blockingconcurrentqueue.h delete mode 100644 app/common/moodycamel/concurrentqueue/concurrentqueue.h delete mode 100644 app/common/moodycamel/concurrentqueue/lightweightsemaphore.h delete mode 100644 app/common/moodycamel/readerwriterqueue/atomicops.h delete mode 100644 app/common/moodycamel/readerwriterqueue/readerwritercircularbuffer.h create mode 100644 app/telemetry/connection/mavlinkchannel.cpp create mode 100644 app/telemetry/connection/mavlinkchannel.h create mode 100644 app/telemetry/models/openhd_core/README.md create mode 100644 app/telemetry/models/openhd_core/camera.hpp create mode 100644 app/telemetry/models/openhd_core/platform.hpp create mode 100644 app/videostreaming/vscommon/custom/rawreceiver.cpp create mode 100644 app/videostreaming/vscommon/custom/rawreceiver.h rename app/videostreaming/vscommon/nalu/{KeyFrameFinder.hpp => CodecConfigFinder.hpp} (92%) create mode 100644 qml/ui/AnyParamBusyIndicator.qml create mode 100644 qml/ui/configpopup/openhd_settings/ChooseCameraDialoque.qml create mode 100644 qml/ui/configpopup/openhd_settings/ChooseResolutionDialoque.qml create mode 100644 qml/ui/configpopup/openhd_settings/MavlinkParamValueEditElement.qml create mode 100644 qml/ui/configpopup/openhd_settings/PopupBigGeneric.qml create mode 100644 qml/ui/elements/BaseHeaderItem2.qml create mode 100644 qml/ui/elements/BigClickableText.qml create mode 100644 qml/ui/elements/ButtonIconGear.qml create mode 100644 qml/ui/elements/SettingBaseElement2.qml create mode 100644 qml/ui/elements/SimpleAdvancedChoiceElement.qml create mode 100644 qml/ui/sidebar/BaseJoyEditElement.qml create mode 100644 qml/ui/sidebar/EditChannelWidthElement.qml create mode 100644 qml/ui/sidebar/EditFrequencyElement.qml create mode 100644 qml/ui/sidebar/EditRateElement.qml create mode 100644 qml/ui/sidebar/GoBackElement.qml create mode 100644 qml/ui/sidebar/InfoElement.qml create mode 100644 qml/ui/sidebar/MavlinkChoiceElement.qml create mode 100644 qml/ui/sidebar/MavlinkSwitch.qml create mode 100644 qml/ui/sidebar/Panel1Link.qml create mode 100644 qml/ui/sidebar/Panel2Video.qml create mode 100644 qml/ui/sidebar/Panel3Camera.qml create mode 100644 qml/ui/sidebar/Panel4Recording.qml create mode 100644 qml/ui/sidebar/Panel5RC.qml create mode 100644 qml/ui/sidebar/Panel6Misc.qml create mode 100644 qml/ui/sidebar/Panel7Status.qml create mode 100644 qml/ui/sidebar/RCChannelView.qml create mode 100644 qml/ui/sidebar/SideBarBasePanel.qml create mode 100644 qml/ui/sidebar/SideBarMain.qml create mode 100644 qml/ui/sidebar/SidebarStackButton.qml create mode 100644 qml/ui/sidebar/TriangleButton.qml create mode 100644 qml/ui/widgets/DevStreamingInfo.qml create mode 100644 qml/ui/widgets/DevStreamingInfoText.qml create mode 100644 qml/ui/widgets/SOCStatusWidgetGeneric.qml delete mode 100644 qml/ui/widgets/Sidebar.qml diff --git a/QOpenHD.pro b/QOpenHD.pro index f330404c4..5c8d1e633 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -111,10 +111,10 @@ SOURCES += \ app/main.cpp \ HEADERS += \ + app/common/ThreadsafeQueue.hpp \ app/common/util_fs.h \ app/common/StringHelper.hpp \ app/common/TimeHelper.hpp \ - app/common/Helper.hpp \ app/logging/hudlogmessagesmodel.h \ app/logging/loghelper.h \ app/logging/logmessagesmodel.h \ diff --git a/app/common/Helper.hpp b/app/common/Helper.hpp deleted file mode 100644 index b80b14f3b..000000000 --- a/app/common/Helper.hpp +++ /dev/null @@ -1,138 +0,0 @@ -// -// Created by consti10 on 17.11.21. -// - -#ifndef RV1126_RV1109_V2_2_0_20210825_HELPER_H -#define RV1126_RV1109_V2_2_0_20210825_HELPER_H - - -#include -#include -#include -#include -#include -#include - -namespace GenericHelper{ - // fill buffer with random bytes - static void fillBufferWithRandomData(std::vector& data){ - const std::size_t size=data.size(); - for(std::size_t i=0;i - static void fillBufferWithRandomData(std::array& data){ - for(std::size_t i=0;i createRandomDataBuffer(const ssize_t sizeBytes){ - std::vector buf(sizeBytes); - fillBufferWithRandomData(buf); - return buf; - } - // same as above but return shared ptr - std::shared_ptr> createRandomDataBuffer2(const ssize_t sizeBytes){ - return std::make_shared>(createRandomDataBuffer(sizeBytes)); - } - // Create a buffer filled with random data where size is chosen Randomly between [minSizeB,...,maxSizeB] - std::vector createRandomDataBuffer(const ssize_t minSizeB,const ssize_t maxSizeB){ - // https://stackoverflow.com/questions/12657962/how-do-i-generate-a-random-number-between-two-variables-that-i-have-stored - const auto sizeBytes = rand()%(maxSizeB-minSizeB + 1) + minSizeB; - return createRandomDataBuffer(sizeBytes); - } - // create n random data buffers with size [minSizeB,...,maxSizeB] - std::vector> createRandomDataBuffers(const std::size_t nBuffers, const std::size_t minSizeB, const std::size_t maxSizeB){ - assert(minSizeB >= 0); - std::vector> buffers; - for(std::size_t i=0;i - static std::vector> createRandomDataBuffers(const std::size_t nBuffers){ - std::vector> ret(nBuffers); - for(auto& buff:ret){ - GenericHelper::fillBufferWithRandomData(buff); - } - return ret; - } - bool compareVectors(const std::vector& sb,const std::vector& rb){ - if(sb.size()!=rb.size()){ - return false; - } - const int result=memcmp (sb.data(),rb.data(),sb.size()); - return result==0; - } - void assertVectorsEqual(const std::vector& sb,const std::vector& rb){ - assert(sb.size()==rb.size()); - const int result=memcmp (sb.data(),rb.data(),sb.size()); - assert(result==0); - } - template - void assertArraysEqual(const std::array& sb,const std::array& rb){ - const int result=memcmp (sb.data(),rb.data(),sb.size()); - if(result!=0){ - //std::cout<<"Data1:"< takeNRandomElements(std::vector values, const std::size_t nElements){ - assert(nElements<=values.size()); - std::vector ret; - for(std::size_t i=0;i createIndices(const std::size_t nIndices){ - std::vector ret(nIndices); - for(std::size_t i=0;i - static std::vector convertToP(std::vector>& buff,std::size_t offset=0,std::size_t n=-1){ - if(n==-1)n=buff.size(); - std::vector ret(n); - for(int i=0;i findMissingIndices(const std::vector& indicesAvailable,const std::size_t range){ - std::vector indicesMissing; - for(unsigned int i=0;i +#include +#include +#include +#include +#include + +/** + * Simple threadsafe queue supporting wait dequeue + */ +namespace qopenhd { +template +class ThreadsafeQueue { + public: + explicit ThreadsafeQueue(int capacity):m_capacity(capacity){}; + // Enqueues a new element. Return true on success, false otherwise + bool try_enqueue(T element){ + std::unique_lock lk(mtx); + if(queue.size()>=m_capacity){ + return false; + } + queue.push(element); + lk.unlock();// minimize lock contention + cv.notify_one(); + return true; + } + // Wait up to timeout until element is available. + template + std::optional wait_dequeue_timed(std::chrono::duration const& timeout){ + std::unique_lock ul(mtx); + if(!queue.empty()){ + auto tmp=queue.front(); + queue.pop(); + return tmp; + } + const auto res=cv.wait_for(ul,timeout,[this](){ + return !queue.empty(); + }); + if(!res){ + // Timeout + return std::nullopt; + } + assert(!queue.empty()); + auto tmp=queue.front(); + queue.pop(); + return tmp; + } +private: + const int m_capacity; + std::queue queue; + std::mutex mtx; + std::condition_variable cv; +}; +} + +#endif // THREADSAFEQUEUE_H diff --git a/app/common/moodycamel/concurrentqueue/blockingconcurrentqueue.h b/app/common/moodycamel/concurrentqueue/blockingconcurrentqueue.h deleted file mode 100644 index 205a4db70..000000000 --- a/app/common/moodycamel/concurrentqueue/blockingconcurrentqueue.h +++ /dev/null @@ -1,582 +0,0 @@ -// Provides an efficient blocking version of moodycamel::ConcurrentQueue. -// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified -// BSD license, available at the top of concurrentqueue.h. -// Also dual-licensed under the Boost Software License (see LICENSE.md) -// Uses Jeff Preshing's semaphore implementation (under the terms of its -// separate zlib license, see lightweightsemaphore.h). - -#pragma once - -#include "concurrentqueue.h" -#include "lightweightsemaphore.h" - -#include -#include -#include -#include -#include - -namespace moodycamel -{ -// This is a blocking version of the queue. It has an almost identical interface to -// the normal non-blocking version, with the addition of various wait_dequeue() methods -// and the removal of producer-specific dequeue methods. -template -class BlockingConcurrentQueue -{ -private: - typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; - typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; - -public: - typedef typename ConcurrentQueue::producer_token_t producer_token_t; - typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; - - typedef typename ConcurrentQueue::index_t index_t; - typedef typename ConcurrentQueue::size_t size_t; - typedef typename std::make_signed::type ssize_t; - - static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; - static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; - -public: - // Creates a queue with at least `capacity` element slots; note that the - // actual number of elements that can be inserted without additional memory - // allocation depends on the number of producers and the block size (e.g. if - // the block size is equal to `capacity`, only a single block will be allocated - // up-front, which means only a single producer will be able to enqueue elements - // without an extra allocation -- blocks aren't shared between producers). - // This method is not thread safe -- it is up to the user to ensure that the - // queue is fully constructed before it starts being used by other threads (this - // includes making the memory effects of construction visible, possibly with a - // memory barrier). - explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) - : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) - { - assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); - if (!sema) { - MOODYCAMEL_THROW(std::bad_alloc()); - } - } - - BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) - : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) - { - assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); - if (!sema) { - MOODYCAMEL_THROW(std::bad_alloc()); - } - } - - // Disable copying and copy assignment - BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - - // Moving is supported, but note that it is *not* a thread-safe operation. - // Nobody can use the queue while it's being moved, and the memory effects - // of that move must be propagated to other threads before they can use it. - // Note: When a queue is moved, its tokens are still valid but can only be - // used with the destination queue (i.e. semantically they are moved along - // with the queue itself). - BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - : inner(std::move(other.inner)), sema(std::move(other.sema)) - { } - - inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - { - return swap_internal(other); - } - - // Swaps this queue's state with the other's. Not thread-safe. - // Swapping two queues does not invalidate their tokens, however - // the tokens that were created for one queue must be used with - // only the swapped queue (i.e. the tokens are tied to the - // queue's movable state, not the object itself). - inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT - { - swap_internal(other); - } - -private: - BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) - { - if (this == &other) { - return *this; - } - - inner.swap(other.inner); - sema.swap(other.sema); - return *this; - } - -public: - // Enqueues a single item (by copying it). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T const& item) - { - if ((details::likely)(inner.enqueue(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T&& item) - { - if ((details::likely)(inner.enqueue(std::move(item)))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T const& item) - { - if ((details::likely)(inner.enqueue(token, item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T&& item) - { - if ((details::likely)(inner.enqueue(token, std::move(item)))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues several items. - // Allocates memory if required. Only fails if memory allocation fails (or - // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved instead of copied. - // Thread-safe. - template - inline bool enqueue_bulk(It itemFirst, size_t count) - { - if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues several items using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails - // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues a single item (by copying it). - // Does not allocate memory. Fails if not enough room to enqueue (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0). - // Thread-safe. - inline bool try_enqueue(T const& item) - { - if (inner.try_enqueue(item)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible). - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Thread-safe. - inline bool try_enqueue(T&& item) - { - if (inner.try_enqueue(std::move(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T const& item) - { - if (inner.try_enqueue(token, item)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T&& item) - { - if (inner.try_enqueue(token, std::move(item))) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues several items. - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool try_enqueue_bulk(It itemFirst, size_t count) - { - if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - // Enqueues several items using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { - sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); - return true; - } - return false; - } - - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue(U& item) - { - if (sema->tryWait()) { - while (!inner.try_dequeue(item)) { - continue; - } - return true; - } - return false; - } - - // Attempts to dequeue from the queue using an explicit consumer token. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue(consumer_token_t& token, U& item) - { - if (sema->tryWait()) { - while (!inner.try_dequeue(token, item)) { - continue; - } - return true; - } - return false; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - - - // Blocks the current thread until there's something to dequeue, then - // dequeues it. - // Never allocates. Thread-safe. - template - inline void wait_dequeue(U& item) - { - while (!sema->wait()) { - continue; - } - while (!inner.try_dequeue(item)) { - continue; - } - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout (specified in microseconds) expires. Returns false - // without setting `item` if the timeout expires, otherwise assigns - // to `item` and returns true. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) - { - if (!sema->wait(timeout_usecs)) { - return false; - } - while (!inner.try_dequeue(item)) { - continue; - } - return true; - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) - { - return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); - } - - // Blocks the current thread until there's something to dequeue, then - // dequeues it using an explicit consumer token. - // Never allocates. Thread-safe. - template - inline void wait_dequeue(consumer_token_t& token, U& item) - { - while (!sema->wait()) { - continue; - } - while (!inner.try_dequeue(token, item)) { - continue; - } - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout (specified in microseconds) expires. Returns false - // without setting `item` if the timeout expires, otherwise assigns - // to `item` and returns true. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) - { - if (!sema->wait(timeout_usecs)) { - return false; - } - while (!inner.try_dequeue(token, item)) { - continue; - } - return true; - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Never allocates. Thread-safe. - template - inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) - { - return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which will - // always be at least one (this method blocks until the queue - // is non-empty) and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue_bulk. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); - while (count != max) { - count += inner.template try_dequeue_bulk(itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) - { - return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which will - // always be at least one (this method blocks until the queue - // is non-empty) and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue_bulk. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) - { - size_t count = 0; - max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); - while (count != max) { - count += inner.template try_dequeue_bulk(token, itemFirst, max - count); - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued, which can - // be 0 if the timeout expires while waiting for elements, - // and at most max. - // Never allocates. Thread-safe. - template - inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) - { - return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); - } - - - // Returns an estimate of the total number of elements currently in the queue. This - // estimate is only accurate if the queue has completely stabilized before it is called - // (i.e. all enqueue and dequeue operations have completed and their memory effects are - // visible on the calling thread, and no further operations start while this method is - // being called). - // Thread-safe. - inline size_t size_approx() const - { - return (size_t)sema->availableApprox(); - } - - - // Returns true if the underlying atomic variables used by - // the queue are lock-free (they should be on most platforms). - // Thread-safe. - static constexpr bool is_lock_free() - { - return ConcurrentQueue::is_lock_free(); - } - - -private: - template - static inline U* create(A1&& a1, A2&& a2) - { - void* p = (Traits::malloc)(sizeof(U)); - return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; - } - - template - static inline void destroy(U* p) - { - if (p != nullptr) { - p->~U(); - } - (Traits::free)(p); - } - -private: - ConcurrentQueue inner; - std::unique_ptr sema; -}; - - -template -inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -} // end namespace moodycamel diff --git a/app/common/moodycamel/concurrentqueue/concurrentqueue.h b/app/common/moodycamel/concurrentqueue/concurrentqueue.h deleted file mode 100644 index 4b2ad791d..000000000 --- a/app/common/moodycamel/concurrentqueue/concurrentqueue.h +++ /dev/null @@ -1,3747 +0,0 @@ -// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. -// An overview, including benchmark results, is provided here: -// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ -// The full design is also described in excruciating detail at: -// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue - -// Simplified BSD license: -// Copyright (c) 2013-2020, Cameron Desrochers. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Also dual-licensed under the Boost Software License (see LICENSE.md) - -#pragma once - -#if defined(__GNUC__) && !defined(__INTEL_COMPILER) -// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and -// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings -// upon assigning any computed values) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" - -#ifdef MCDBGQ_USE_RELACY -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" -#endif -#endif - -#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) -// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher -// does not support `if constexpr`, so we have no choice but to simply disable the warning -#pragma warning(push) -#pragma warning(disable: 4127) // conditional expression is constant -#endif - -#if defined(__APPLE__) -#include "TargetConditionals.h" -#endif - -#ifdef MCDBGQ_USE_RELACY -#include "relacy/relacy_std.hpp" -#include "relacy_shims.h" -// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. -// We'll override the default trait malloc ourselves without a macro. -#undef new -#undef delete -#undef malloc -#undef free -#else -#include // Requires C++11. Sorry VS2010. -#include -#endif -#include // for max_align_t -#include -#include -#include -#include -#include -#include -#include // for CHAR_BIT -#include -#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading -#include // used for thread exit synchronization - -// Platform-specific definitions of a numeric thread ID type and an invalid value -namespace moodycamel { namespace details { - template struct thread_id_converter { - typedef thread_id_t thread_id_numeric_size_t; - typedef thread_id_t thread_id_hash_t; - static thread_id_hash_t prehash(thread_id_t const& x) { return x; } - }; -} } -#if defined(MCDBGQ_USE_RELACY) -namespace moodycamel { namespace details { - typedef std::uint32_t thread_id_t; - static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; - static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; - static inline thread_id_t thread_id() { return rl::thread_index(); } -} } -#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) -// No sense pulling in windows.h in a header, we'll manually declare the function -// we use and rely on backwards-compatibility for this not to break -extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); -namespace moodycamel { namespace details { - static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); - typedef std::uint32_t thread_id_t; - static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx - static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. - static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } -} } -#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) -namespace moodycamel { namespace details { - static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); - - typedef std::thread::id thread_id_t; - static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID - - // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's - // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't - // be. - static inline thread_id_t thread_id() { return std::this_thread::get_id(); } - - template struct thread_id_size { }; - template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; - template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; - - template<> struct thread_id_converter { - typedef thread_id_size::numeric_t thread_id_numeric_size_t; -#ifndef __APPLE__ - typedef std::size_t thread_id_hash_t; -#else - typedef thread_id_numeric_size_t thread_id_hash_t; -#endif - - static thread_id_hash_t prehash(thread_id_t const& x) - { -#ifndef __APPLE__ - return std::hash()(x); -#else - return *reinterpret_cast(&x); -#endif - } - }; -} } -#else -// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 -// In order to get a numeric thread ID in a platform-independent way, we use a thread-local -// static variable's address as a thread identifier :-) -#if defined(__GNUC__) || defined(__INTEL_COMPILER) -#define MOODYCAMEL_THREADLOCAL __thread -#elif defined(_MSC_VER) -#define MOODYCAMEL_THREADLOCAL __declspec(thread) -#else -// Assume C++11 compliant compiler -#define MOODYCAMEL_THREADLOCAL thread_local -#endif -namespace moodycamel { namespace details { - typedef std::uintptr_t thread_id_t; - static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr - static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. - inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } -} } -#endif - -// Constexpr if -#ifndef MOODYCAMEL_CONSTEXPR_IF -#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L -#define MOODYCAMEL_CONSTEXPR_IF if constexpr -#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] -#else -#define MOODYCAMEL_CONSTEXPR_IF if -#define MOODYCAMEL_MAYBE_UNUSED -#endif -#endif - -// Exceptions -#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED -#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) -#define MOODYCAMEL_EXCEPTIONS_ENABLED -#endif -#endif -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED -#define MOODYCAMEL_TRY try -#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) -#define MOODYCAMEL_RETHROW throw -#define MOODYCAMEL_THROW(expr) throw (expr) -#else -#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) -#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) -#define MOODYCAMEL_RETHROW -#define MOODYCAMEL_THROW(expr) -#endif - -#ifndef MOODYCAMEL_NOEXCEPT -#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) -#define MOODYCAMEL_NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 -// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( -// We have to assume *all* non-trivial constructors may throw on VS2012! -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 -#define MOODYCAMEL_NOEXCEPT _NOEXCEPT -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) -#else -#define MOODYCAMEL_NOEXCEPT noexcept -#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) -#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) -#endif -#endif - -#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY -#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#else -// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 -// g++ <=4.7 doesn't support thread_local either. -// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work -#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) -// Assume `thread_local` is fully supported in all other C++11 compilers/platforms -#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // tentatively enabled for now; years ago several users report having problems with it on -#endif -#endif -#endif - -// VS2012 doesn't support deleted functions. -// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. -#ifndef MOODYCAMEL_DELETE_FUNCTION -#if defined(_MSC_VER) && _MSC_VER < 1800 -#define MOODYCAMEL_DELETE_FUNCTION -#else -#define MOODYCAMEL_DELETE_FUNCTION = delete -#endif -#endif - -namespace moodycamel { namespace details { -#ifndef MOODYCAMEL_ALIGNAS -// VS2013 doesn't support alignas or alignof, and align() requires a constant literal -#if defined(_MSC_VER) && _MSC_VER <= 1800 -#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) -#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) -#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type - template struct Vs2013Aligned { }; // default, unsupported alignment - template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; - template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; - template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; - template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; - template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; - template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; - template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; - template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; - template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; -#else - template struct identity { typedef T type; }; -#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) -#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) -#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type -#endif -#endif -} } - - -// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, -// we can apply per-function compile-time suppression. -// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer -#define MOODYCAMEL_NO_TSAN -#if defined(__has_feature) - #if __has_feature(thread_sanitizer) - #undef MOODYCAMEL_NO_TSAN - #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) - #endif // TSAN -#endif // TSAN - -// Compiler-specific likely/unlikely hints -namespace moodycamel { namespace details { -#if defined(__GNUC__) - static inline bool (likely)(bool x) { return __builtin_expect((x), true); } - static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } -#else - static inline bool (likely)(bool x) { return x; } - static inline bool (unlikely)(bool x) { return x; } -#endif -} } - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG -#include "internal/concurrentqueue_internal_debug.h" -#endif - -namespace moodycamel { -namespace details { - template - struct const_numeric_max { - static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); - static const T value = std::numeric_limits::is_signed - ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) - : static_cast(-1); - }; - -#if defined(__GLIBCXX__) - typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while -#else - typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: -#endif - - // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting - // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. - typedef union { - std_max_align_t x; - long long y; - void* z; - } max_align_t; -} - -// Default traits for the ConcurrentQueue. To change some of the -// traits without re-implementing all of them, inherit from this -// struct and shadow the declarations you wish to be different; -// since the traits are used as a template type parameter, the -// shadowed declarations will be used where defined, and the defaults -// otherwise. -struct ConcurrentQueueDefaultTraits -{ - // General-purpose size type. std::size_t is strongly recommended. - typedef std::size_t size_t; - - // The type used for the enqueue and dequeue indices. Must be at least as - // large as size_t. Should be significantly larger than the number of elements - // you expect to hold at once, especially if you have a high turnover rate; - // for example, on 32-bit x86, if you expect to have over a hundred million - // elements or pump several million elements through your queue in a very - // short space of time, using a 32-bit type *may* trigger a race condition. - // A 64-bit int type is recommended in that case, and in practice will - // prevent a race condition no matter the usage of the queue. Note that - // whether the queue is lock-free with a 64-int type depends on the whether - // std::atomic is lock-free, which is platform-specific. - typedef std::size_t index_t; - - // Internally, all elements are enqueued and dequeued from multi-element - // blocks; this is the smallest controllable unit. If you expect few elements - // but many producers, a smaller block size should be favoured. For few producers - // and/or many elements, a larger block size is preferred. A sane default - // is provided. Must be a power of 2. - static const size_t BLOCK_SIZE = 32; - - // For explicit producers (i.e. when using a producer token), the block is - // checked for being empty by iterating through a list of flags, one per element. - // For large block sizes, this is too inefficient, and switching to an atomic - // counter-based approach is faster. The switch is made for block sizes strictly - // larger than this threshold. - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; - - // How many full blocks can be expected for a single explicit producer? This should - // reflect that number's maximum for optimal performance. Must be a power of 2. - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; - - // How many full blocks can be expected for a single implicit producer? This should - // reflect that number's maximum for optimal performance. Must be a power of 2. - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; - - // The initial size of the hash table mapping thread IDs to implicit producers. - // Note that the hash is resized every time it becomes half full. - // Must be a power of two, and either 0 or at least 1. If 0, implicit production - // (using the enqueue methods without an explicit producer token) is disabled. - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; - - // Controls the number of items that an explicit consumer (i.e. one with a token) - // must consume before it causes all consumers to rotate and move on to the next - // internal queue. - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; - - // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. - // Enqueue operations that would cause this limit to be surpassed will fail. Note - // that this limit is enforced at the block level (for performance reasons), i.e. - // it's rounded up to the nearest block size. - static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; - - // The number of times to spin before sleeping when waiting on a semaphore. - // Recommended values are on the order of 1000-10000 unless the number of - // consumer threads exceeds the number of idle cores (in which case try 0-100). - // Only affects instances of the BlockingConcurrentQueue. - static const int MAX_SEMA_SPINS = 10000; - - // Whether to recycle dynamically-allocated blocks into an internal free list or - // not. If false, only pre-allocated blocks (controlled by the constructor - // arguments) will be recycled, and all others will be `free`d back to the heap. - // Note that blocks consumed by explicit producers are only freed on destruction - // of the queue (not following destruction of the token) regardless of this trait. - static const bool RECYCLE_ALLOCATED_BLOCKS = false; - - -#ifndef MCDBGQ_USE_RELACY - // Memory allocation can be customized if needed. - // malloc should return nullptr on failure, and handle alignment like std::malloc. -#if defined(malloc) || defined(free) - // Gah, this is 2015, stop defining macros that break standard code already! - // Work around malloc/free being special macros: - static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } - static inline void WORKAROUND_free(void* ptr) { return free(ptr); } - static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } - static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } -#else - static inline void* malloc(size_t size) { return std::malloc(size); } - static inline void free(void* ptr) { return std::free(ptr); } -#endif -#else - // Debug versions when running under the Relacy race detector (ignore - // these in user code) - static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } - static inline void free(void* ptr) { return rl::rl_free(ptr, $); } -#endif -}; - - -// When producing or consuming many elements, the most efficient way is to: -// 1) Use one of the bulk-operation methods of the queue with a token -// 2) Failing that, use the bulk-operation methods without a token -// 3) Failing that, create a token and use that with the single-item methods -// 4) Failing that, use the single-parameter methods of the queue -// Having said that, don't create tokens willy-nilly -- ideally there should be -// a maximum of one token per thread (of each kind). -struct ProducerToken; -struct ConsumerToken; - -template class ConcurrentQueue; -template class BlockingConcurrentQueue; -class ConcurrentQueueTests; - - -namespace details -{ - struct ConcurrentQueueProducerTypelessBase - { - ConcurrentQueueProducerTypelessBase* next; - std::atomic inactive; - ProducerToken* token; - - ConcurrentQueueProducerTypelessBase() - : next(nullptr), inactive(false), token(nullptr) - { - } - }; - - template struct _hash_32_or_64 { - static inline std::uint32_t hash(std::uint32_t h) - { - // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp - // Since the thread ID is already unique, all we really want to do is propagate that - // uniqueness evenly across all the bits, so that we can use a subset of the bits while - // reducing collisions significantly - h ^= h >> 16; - h *= 0x85ebca6b; - h ^= h >> 13; - h *= 0xc2b2ae35; - return h ^ (h >> 16); - } - }; - template<> struct _hash_32_or_64<1> { - static inline std::uint64_t hash(std::uint64_t h) - { - h ^= h >> 33; - h *= 0xff51afd7ed558ccd; - h ^= h >> 33; - h *= 0xc4ceb9fe1a85ec53; - return h ^ (h >> 33); - } - }; - template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; - - static inline size_t hash_thread_id(thread_id_t id) - { - static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); - return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( - thread_id_converter::prehash(id))); - } - - template - static inline bool circular_less_than(T a, T b) - { - static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); - return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); - // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 - // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. - } - - template - static inline char* align_for(char* ptr) - { - const std::size_t alignment = std::alignment_of::value; - return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; - } - - template - static inline T ceil_to_pow_2(T x) - { - static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); - - // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - for (std::size_t i = 1; i < sizeof(T); i <<= 1) { - x |= x >> (i << 3); - } - ++x; - return x; - } - - template - static inline void swap_relaxed(std::atomic& left, std::atomic& right) - { - T temp = std::move(left.load(std::memory_order_relaxed)); - left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); - right.store(std::move(temp), std::memory_order_relaxed); - } - - template - static inline T const& nomove(T const& x) - { - return x; - } - - template - struct nomove_if - { - template - static inline T const& eval(T const& x) - { - return x; - } - }; - - template<> - struct nomove_if - { - template - static inline auto eval(U&& x) - -> decltype(std::forward(x)) - { - return std::forward(x); - } - }; - - template - static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) - { - return *it; - } - -#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) - template struct is_trivially_destructible : std::is_trivially_destructible { }; -#else - template struct is_trivially_destructible : std::has_trivial_destructor { }; -#endif - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED -#ifdef MCDBGQ_USE_RELACY - typedef RelacyThreadExitListener ThreadExitListener; - typedef RelacyThreadExitNotifier ThreadExitNotifier; -#else - class ThreadExitNotifier; - - struct ThreadExitListener - { - typedef void (*callback_t)(void*); - callback_t callback; - void* userData; - - ThreadExitListener* next; // reserved for use by the ThreadExitNotifier - ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier - }; - - class ThreadExitNotifier - { - public: - static void subscribe(ThreadExitListener* listener) - { - auto& tlsInst = instance(); - std::lock_guard guard(mutex()); - listener->next = tlsInst.tail; - listener->chain = &tlsInst; - tlsInst.tail = listener; - } - - static void unsubscribe(ThreadExitListener* listener) - { - std::lock_guard guard(mutex()); - if (!listener->chain) { - return; // race with ~ThreadExitNotifier - } - auto& tlsInst = *listener->chain; - listener->chain = nullptr; - ThreadExitListener** prev = &tlsInst.tail; - for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { - if (ptr == listener) { - *prev = ptr->next; - break; - } - prev = &ptr->next; - } - } - - private: - ThreadExitNotifier() : tail(nullptr) { } - ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; - ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; - - ~ThreadExitNotifier() - { - // This thread is about to exit, let everyone know! - assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); - std::lock_guard guard(mutex()); - for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { - ptr->chain = nullptr; - ptr->callback(ptr->userData); - } - } - - // Thread-local - static inline ThreadExitNotifier& instance() - { - static thread_local ThreadExitNotifier notifier; - return notifier; - } - - static inline std::mutex& mutex() - { - // Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called - static std::mutex mutex; - return mutex; - } - - private: - ThreadExitListener* tail; - }; -#endif -#endif - - template struct static_is_lock_free_num { enum { value = 0 }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; - template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; - template struct static_is_lock_free : static_is_lock_free_num::type> { }; - template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; - template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; -} - - -struct ProducerToken -{ - template - explicit ProducerToken(ConcurrentQueue& queue); - - template - explicit ProducerToken(BlockingConcurrentQueue& queue); - - ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT - : producer(other.producer) - { - other.producer = nullptr; - if (producer != nullptr) { - producer->token = this; - } - } - - inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT - { - std::swap(producer, other.producer); - if (producer != nullptr) { - producer->token = this; - } - if (other.producer != nullptr) { - other.producer->token = &other; - } - } - - // A token is always valid unless: - // 1) Memory allocation failed during construction - // 2) It was moved via the move constructor - // (Note: assignment does a swap, leaving both potentially valid) - // 3) The associated queue was destroyed - // Note that if valid() returns true, that only indicates - // that the token is valid for use with a specific queue, - // but not which one; that's up to the user to track. - inline bool valid() const { return producer != nullptr; } - - ~ProducerToken() - { - if (producer != nullptr) { - producer->token = nullptr; - producer->inactive.store(true, std::memory_order_release); - } - } - - // Disable copying and assignment - ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; - -private: - template friend class ConcurrentQueue; - friend class ConcurrentQueueTests; - -protected: - details::ConcurrentQueueProducerTypelessBase* producer; -}; - - -struct ConsumerToken -{ - template - explicit ConsumerToken(ConcurrentQueue& q); - - template - explicit ConsumerToken(BlockingConcurrentQueue& q); - - ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT - : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) - { - } - - inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT - { - std::swap(initialOffset, other.initialOffset); - std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); - std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); - std::swap(currentProducer, other.currentProducer); - std::swap(desiredProducer, other.desiredProducer); - } - - // Disable copying and assignment - ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; - ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; - -private: - template friend class ConcurrentQueue; - friend class ConcurrentQueueTests; - -private: // but shared with ConcurrentQueue - std::uint32_t initialOffset; - std::uint32_t lastKnownGlobalOffset; - std::uint32_t itemsConsumedFromCurrent; - details::ConcurrentQueueProducerTypelessBase* currentProducer; - details::ConcurrentQueueProducerTypelessBase* desiredProducer; -}; - -// Need to forward-declare this swap because it's in a namespace. -// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces -template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; - - -template -class ConcurrentQueue -{ -public: - typedef ::moodycamel::ProducerToken producer_token_t; - typedef ::moodycamel::ConsumerToken consumer_token_t; - - typedef typename Traits::index_t index_t; - typedef typename Traits::size_t size_t; - - static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); - static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); - static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); - static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); - static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); - static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) -#pragma warning(disable: 4309) // static_cast: Truncation of constant value -#endif - static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); - static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); - static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); - static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); - static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); - static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); - static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); - static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); - static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); - -public: - // Creates a queue with at least `capacity` element slots; note that the - // actual number of elements that can be inserted without additional memory - // allocation depends on the number of producers and the block size (e.g. if - // the block size is equal to `capacity`, only a single block will be allocated - // up-front, which means only a single producer will be able to enqueue elements - // without an extra allocation -- blocks aren't shared between producers). - // This method is not thread safe -- it is up to the user to ensure that the - // queue is fully constructed before it starts being used by other threads (this - // includes making the memory effects of construction visible, possibly with a - // memory barrier). - explicit ConcurrentQueue(size_t capacity = 32 * BLOCK_SIZE) - : producerListTail(nullptr), - producerCount(0), - initialBlockPoolIndex(0), - nextExplicitConsumerId(0), - globalExplicitConsumerOffset(0) - { - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - // Track all the producers using a fully-resolved typed list for - // each kind; this makes it possible to debug them starting from - // the root queue object (otherwise wacky casts are needed that - // don't compile in the debugger's expression evaluator). - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - } - - // Computes the correct amount of pre-allocated blocks for you based - // on the minimum number of elements you want available at any given - // time, and the maximum concurrent number of each type of producer. - ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) - : producerListTail(nullptr), - producerCount(0), - initialBlockPoolIndex(0), - nextExplicitConsumerId(0), - globalExplicitConsumerOffset(0) - { - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); - populate_initial_block_list(blocks); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - } - - // Note: The queue should not be accessed concurrently while it's - // being deleted. It's up to the user to synchronize this. - // This method is not thread safe. - ~ConcurrentQueue() - { - // Destroy producers - auto ptr = producerListTail.load(std::memory_order_relaxed); - while (ptr != nullptr) { - auto next = ptr->next_prod(); - if (ptr->token != nullptr) { - ptr->token->producer = nullptr; - } - destroy(ptr); - ptr = next; - } - - // Destroy implicit producer hash tables - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { - auto hash = implicitProducerHash.load(std::memory_order_relaxed); - while (hash != nullptr) { - auto prev = hash->prev; - if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically - for (size_t i = 0; i != hash->capacity; ++i) { - hash->entries[i].~ImplicitProducerKVP(); - } - hash->~ImplicitProducerHash(); - (Traits::free)(hash); - } - hash = prev; - } - } - - // Destroy global free list - auto block = freeList.head_unsafe(); - while (block != nullptr) { - auto next = block->freeListNext.load(std::memory_order_relaxed); - if (block->dynamicallyAllocated) { - destroy(block); - } - block = next; - } - - // Destroy initial free list - destroy_array(initialBlockPool, initialBlockPoolSize); - } - - // Disable copying and copy assignment - ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; - - // Moving is supported, but note that it is *not* a thread-safe operation. - // Nobody can use the queue while it's being moved, and the memory effects - // of that move must be propagated to other threads before they can use it. - // Note: When a queue is moved, its tokens are still valid but can only be - // used with the destination queue (i.e. semantically they are moved along - // with the queue itself). - ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), - producerCount(other.producerCount.load(std::memory_order_relaxed)), - initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), - initialBlockPool(other.initialBlockPool), - initialBlockPoolSize(other.initialBlockPoolSize), - freeList(std::move(other.freeList)), - nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), - globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) - { - // Move the other one into this, and leave the other one as an empty queue - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - populate_initial_implicit_producer_hash(); - swap_implicit_producer_hashes(other); - - other.producerListTail.store(nullptr, std::memory_order_relaxed); - other.producerCount.store(0, std::memory_order_relaxed); - other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); - other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.explicitProducers.store(nullptr, std::memory_order_relaxed); - implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); - other.implicitProducers.store(nullptr, std::memory_order_relaxed); -#endif - - other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); - other.initialBlockPoolSize = 0; - other.initialBlockPool = nullptr; - - reown_producers(); - } - - inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT - { - return swap_internal(other); - } - - // Swaps this queue's state with the other's. Not thread-safe. - // Swapping two queues does not invalidate their tokens, however - // the tokens that were created for one queue must be used with - // only the swapped queue (i.e. the tokens are tied to the - // queue's movable state, not the object itself). - inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT - { - swap_internal(other); - } - -private: - ConcurrentQueue& swap_internal(ConcurrentQueue& other) - { - if (this == &other) { - return *this; - } - - details::swap_relaxed(producerListTail, other.producerListTail); - details::swap_relaxed(producerCount, other.producerCount); - details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); - std::swap(initialBlockPool, other.initialBlockPool); - std::swap(initialBlockPoolSize, other.initialBlockPoolSize); - freeList.swap(other.freeList); - details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); - details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); - - swap_implicit_producer_hashes(other); - - reown_producers(); - other.reown_producers(); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - details::swap_relaxed(explicitProducers, other.explicitProducers); - details::swap_relaxed(implicitProducers, other.implicitProducers); -#endif - - return *this; - } - -public: - // Enqueues a single item (by copying it). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T const& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(item); - } - - // Enqueues a single item (by moving it, if possible). - // Allocates memory if required. Only fails if memory allocation fails (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, - // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(T&& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(std::move(item)); - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T const& item) - { - return inner_enqueue(token, item); - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails (or - // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Thread-safe. - inline bool enqueue(producer_token_t const& token, T&& item) - { - return inner_enqueue(token, std::move(item)); - } - - // Enqueues several items. - // Allocates memory if required. Only fails if memory allocation fails (or - // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved instead of copied. - // Thread-safe. - template - bool enqueue_bulk(It itemFirst, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue_bulk(itemFirst, count); - } - - // Enqueues several items using an explicit producer token. - // Allocates memory if required. Only fails if memory allocation fails - // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return inner_enqueue_bulk(token, itemFirst, count); - } - - // Enqueues a single item (by copying it). - // Does not allocate memory. Fails if not enough room to enqueue (or implicit - // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - // is 0). - // Thread-safe. - inline bool try_enqueue(T const& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(item); - } - - // Enqueues a single item (by moving it, if possible). - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Thread-safe. - inline bool try_enqueue(T&& item) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue(std::move(item)); - } - - // Enqueues a single item (by copying it) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T const& item) - { - return inner_enqueue(token, item); - } - - // Enqueues a single item (by moving it, if possible) using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Thread-safe. - inline bool try_enqueue(producer_token_t const& token, T&& item) - { - return inner_enqueue(token, std::move(item)); - } - - // Enqueues several items. - // Does not allocate memory (except for one-time implicit producer). - // Fails if not enough room to enqueue (or implicit production is - // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool try_enqueue_bulk(It itemFirst, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; - else return inner_enqueue_bulk(itemFirst, count); - } - - // Enqueues several items using an explicit producer token. - // Does not allocate memory. Fails if not enough room to enqueue. - // Note: Use std::make_move_iterator if the elements should be moved - // instead of copied. - // Thread-safe. - template - bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return inner_enqueue_bulk(token, itemFirst, count); - } - - - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - bool try_dequeue(U& item) - { - // Instead of simply trying each producer in turn (which could cause needless contention on the first - // producer), we score them heuristically. - size_t nonEmptyCount = 0; - ProducerBase* best = nullptr; - size_t bestSize = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { - auto size = ptr->size_approx(); - if (size > 0) { - if (size > bestSize) { - bestSize = size; - best = ptr; - } - ++nonEmptyCount; - } - } - - // If there was at least one non-empty queue but it appears empty at the time - // we try to dequeue from it, we need to make sure every queue's been tried - if (nonEmptyCount > 0) { - if ((details::likely)(best->dequeue(item))) { - return true; - } - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr != best && ptr->dequeue(item)) { - return true; - } - } - } - return false; - } - - // Attempts to dequeue from the queue. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // This differs from the try_dequeue(item) method in that this one does - // not attempt to reduce contention by interleaving the order that producer - // streams are dequeued from. So, using this method can reduce overall throughput - // under contention, but will give more predictable results in single-threaded - // consumer scenarios. This is mostly only useful for internal unit tests. - // Never allocates. Thread-safe. - template - bool try_dequeue_non_interleaved(U& item) - { - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr->dequeue(item)) { - return true; - } - } - return false; - } - - // Attempts to dequeue from the queue using an explicit consumer token. - // Returns false if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - bool try_dequeue(consumer_token_t& token, U& item) - { - // The idea is roughly as follows: - // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less - // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place - // If there's no items where you're supposed to be, keep moving until you find a producer with some items - // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it - - if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { - if (!update_current_producer_after_rotation(token)) { - return false; - } - } - - // If there was at least one non-empty queue but it appears empty at the time - // we try to dequeue from it, we need to make sure every queue's been tried - if (static_cast(token.currentProducer)->dequeue(item)) { - if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { - globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); - } - return true; - } - - auto tail = producerListTail.load(std::memory_order_acquire); - auto ptr = static_cast(token.currentProducer)->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - while (ptr != static_cast(token.currentProducer)) { - if (ptr->dequeue(item)) { - token.currentProducer = ptr; - token.itemsConsumedFromCurrent = 1; - return true; - } - ptr = ptr->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - } - return false; - } - - // Attempts to dequeue several elements from the queue. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - size_t try_dequeue_bulk(It itemFirst, size_t max) - { - size_t count = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - count += ptr->dequeue_bulk(itemFirst, max - count); - if (count == max) { - break; - } - } - return count; - } - - // Attempts to dequeue several elements from the queue using an explicit consumer token. - // Returns the number of items actually dequeued. - // Returns 0 if all producer streams appeared empty at the time they - // were checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) - { - if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { - if (!update_current_producer_after_rotation(token)) { - return 0; - } - } - - size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); - if (count == max) { - if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { - globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); - } - return max; - } - token.itemsConsumedFromCurrent += static_cast(count); - max -= count; - - auto tail = producerListTail.load(std::memory_order_acquire); - auto ptr = static_cast(token.currentProducer)->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - while (ptr != static_cast(token.currentProducer)) { - auto dequeued = ptr->dequeue_bulk(itemFirst, max); - count += dequeued; - if (dequeued != 0) { - token.currentProducer = ptr; - token.itemsConsumedFromCurrent = static_cast(dequeued); - } - if (dequeued == max) { - break; - } - max -= dequeued; - ptr = ptr->next_prod(); - if (ptr == nullptr) { - ptr = tail; - } - } - return count; - } - - - - // Attempts to dequeue from a specific producer's inner queue. - // If you happen to know which producer you want to dequeue from, this - // is significantly faster than using the general-case try_dequeue methods. - // Returns false if the producer's queue appeared empty at the time it - // was checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) - { - return static_cast(producer.producer)->dequeue(item); - } - - // Attempts to dequeue several elements from a specific producer's inner queue. - // Returns the number of items actually dequeued. - // If you happen to know which producer you want to dequeue from, this - // is significantly faster than using the general-case try_dequeue methods. - // Returns 0 if the producer's queue appeared empty at the time it - // was checked (so, the queue is likely but not guaranteed to be empty). - // Never allocates. Thread-safe. - template - inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) - { - return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); - } - - - // Returns an estimate of the total number of elements currently in the queue. This - // estimate is only accurate if the queue has completely stabilized before it is called - // (i.e. all enqueue and dequeue operations have completed and their memory effects are - // visible on the calling thread, and no further operations start while this method is - // being called). - // Thread-safe. - size_t size_approx() const - { - size_t size = 0; - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - size += ptr->size_approx(); - } - return size; - } - - - // Returns true if the underlying atomic variables used by - // the queue are lock-free (they should be on most platforms). - // Thread-safe. - static constexpr bool is_lock_free() - { - return - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::value == 2 && - details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; - } - - -private: - friend struct ProducerToken; - friend struct ConsumerToken; - struct ExplicitProducer; - friend struct ExplicitProducer; - struct ImplicitProducer; - friend struct ImplicitProducer; - friend class ConcurrentQueueTests; - - enum AllocationMode { CanAlloc, CannotAlloc }; - - - /////////////////////////////// - // Queue methods - /////////////////////////////// - - template - inline bool inner_enqueue(producer_token_t const& token, U&& element) - { - return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); - } - - template - inline bool inner_enqueue(U&& element) - { - auto producer = get_or_add_implicit_producer(); - return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); - } - - template - inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) - { - return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); - } - - template - inline bool inner_enqueue_bulk(It itemFirst, size_t count) - { - auto producer = get_or_add_implicit_producer(); - return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); - } - - inline bool update_current_producer_after_rotation(consumer_token_t& token) - { - // Ah, there's been a rotation, figure out where we should be! - auto tail = producerListTail.load(std::memory_order_acquire); - if (token.desiredProducer == nullptr && tail == nullptr) { - return false; - } - auto prodCount = producerCount.load(std::memory_order_relaxed); - auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); - if ((details::unlikely)(token.desiredProducer == nullptr)) { - // Aha, first time we're dequeueing anything. - // Figure out our local position - // Note: offset is from start, not end, but we're traversing from end -- subtract from count first - std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); - token.desiredProducer = tail; - for (std::uint32_t i = 0; i != offset; ++i) { - token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); - if (token.desiredProducer == nullptr) { - token.desiredProducer = tail; - } - } - } - - std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; - if (delta >= prodCount) { - delta = delta % prodCount; - } - for (std::uint32_t i = 0; i != delta; ++i) { - token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); - if (token.desiredProducer == nullptr) { - token.desiredProducer = tail; - } - } - - token.lastKnownGlobalOffset = globalOffset; - token.currentProducer = token.desiredProducer; - token.itemsConsumedFromCurrent = 0; - return true; - } - - - /////////////////////////// - // Free list - /////////////////////////// - - template - struct FreeListNode - { - FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } - - std::atomic freeListRefs; - std::atomic freeListNext; - }; - - // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but - // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly - // speedy under low contention. - template // N must inherit FreeListNode or have the same fields (and initialization of them) - struct FreeList - { - FreeList() : freeListHead(nullptr) { } - FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } - void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } - - FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; - FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; - - inline void add(N* node) - { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif - // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to - // set it using a fetch_add - if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { - // Oh look! We were the last ones referencing this node, and we know - // we want to add it to the free list, so let's do it! - add_knowing_refcount_is_zero(node); - } - } - - inline N* try_get() - { -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugLock lock(mutex); -#endif - auto head = freeListHead.load(std::memory_order_acquire); - while (head != nullptr) { - auto prevHead = head; - auto refs = head->freeListRefs.load(std::memory_order_relaxed); - if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { - head = freeListHead.load(std::memory_order_acquire); - continue; - } - - // Good, reference count has been incremented (it wasn't at zero), which means we can read the - // next and not worry about it changing between now and the time we do the CAS - auto next = head->freeListNext.load(std::memory_order_relaxed); - if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { - // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no - // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). - assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); - - // Decrease refcount twice, once for our ref, and once for the list's ref - head->freeListRefs.fetch_sub(2, std::memory_order_release); - return head; - } - - // OK, the head must have changed on us, but we still need to decrease the refcount we increased. - // Note that we don't need to release any memory effects, but we do need to ensure that the reference - // count decrement happens-after the CAS on the head. - refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); - if (refs == SHOULD_BE_ON_FREELIST + 1) { - add_knowing_refcount_is_zero(prevHead); - } - } - - return nullptr; - } - - // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) - N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } - - private: - inline void add_knowing_refcount_is_zero(N* node) - { - // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run - // only one copy of this method per node at a time, i.e. the single thread case), then we know - // we can safely change the next pointer of the node; however, once the refcount is back above - // zero, then other threads could increase it (happens under heavy contention, when the refcount - // goes to zero in between a load and a refcount increment of a node in try_get, then back up to - // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS - // to add the node to the actual list fails, decrease the refcount and leave the add operation to - // the next thread who puts the refcount back at zero (which could be us, hence the loop). - auto head = freeListHead.load(std::memory_order_relaxed); - while (true) { - node->freeListNext.store(head, std::memory_order_relaxed); - node->freeListRefs.store(1, std::memory_order_release); - if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { - // Hmm, the add failed, but we can only try again when the refcount goes back to zero - if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { - continue; - } - } - return; - } - } - - private: - // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) - std::atomic freeListHead; - - static const std::uint32_t REFS_MASK = 0x7FFFFFFF; - static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; - -#ifdef MCDBGQ_NOLOCKFREE_FREELIST - debug::DebugMutex mutex; -#endif - }; - - - /////////////////////////// - // Block - /////////////////////////// - - enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; - - struct Block - { - Block() - : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), dynamicallyAllocated(true) - { -#ifdef MCDBGQ_TRACKMEM - owner = nullptr; -#endif - } - - template - inline bool is_empty() const - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Check flags - for (size_t i = 0; i < BLOCK_SIZE; ++i) { - if (!emptyFlags[i].load(std::memory_order_relaxed)) { - return false; - } - } - - // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set - std::atomic_thread_fence(std::memory_order_acquire); - return true; - } - else { - // Check counter - if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { - std::atomic_thread_fence(std::memory_order_acquire); - return true; - } - assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); - return false; - } - } - - // Returns true if the block is now empty (does not apply in explicit context) - template - inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set flag - assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); - emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); - return false; - } - else { - // Increment counter - auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); - assert(prevVal < BLOCK_SIZE); - return prevVal == BLOCK_SIZE - 1; - } - } - - // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). - // Returns true if the block is now empty (does not apply in explicit context). - template - inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set flags - std::atomic_thread_fence(std::memory_order_release); - i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; - for (size_t j = 0; j != count; ++j) { - assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); - emptyFlags[i + j].store(true, std::memory_order_relaxed); - } - return false; - } - else { - // Increment counter - auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); - assert(prevVal + count <= BLOCK_SIZE); - return prevVal + count == BLOCK_SIZE; - } - } - - template - inline void set_all_empty() - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Set all flags - for (size_t i = 0; i != BLOCK_SIZE; ++i) { - emptyFlags[i].store(true, std::memory_order_relaxed); - } - } - else { - // Reset counter - elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); - } - } - - template - inline void reset_empty() - { - MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { - // Reset flags - for (size_t i = 0; i != BLOCK_SIZE; ++i) { - emptyFlags[i].store(false, std::memory_order_relaxed); - } - } - else { - // Reset counter - elementsCompletelyDequeued.store(0, std::memory_order_relaxed); - } - } - - inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } - inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } - - private: - static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); - MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; - public: - Block* next; - std::atomic elementsCompletelyDequeued; - std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; - public: - std::atomic freeListRefs; - std::atomic freeListNext; - bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' - -#ifdef MCDBGQ_TRACKMEM - void* owner; -#endif - }; - static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); - - -#ifdef MCDBGQ_TRACKMEM -public: - struct MemStats; -private: -#endif - - /////////////////////////// - // Producer base - /////////////////////////// - - struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase - { - ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : - tailIndex(0), - headIndex(0), - dequeueOptimisticCount(0), - dequeueOvercommit(0), - tailBlock(nullptr), - isExplicit(isExplicit_), - parent(parent_) - { - } - - virtual ~ProducerBase() { } - - template - inline bool dequeue(U& element) - { - if (isExplicit) { - return static_cast(this)->dequeue(element); - } - else { - return static_cast(this)->dequeue(element); - } - } - - template - inline size_t dequeue_bulk(It& itemFirst, size_t max) - { - if (isExplicit) { - return static_cast(this)->dequeue_bulk(itemFirst, max); - } - else { - return static_cast(this)->dequeue_bulk(itemFirst, max); - } - } - - inline ProducerBase* next_prod() const { return static_cast(next); } - - inline size_t size_approx() const - { - auto tail = tailIndex.load(std::memory_order_relaxed); - auto head = headIndex.load(std::memory_order_relaxed); - return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; - } - - inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } - protected: - std::atomic tailIndex; // Where to enqueue to next - std::atomic headIndex; // Where to dequeue from next - - std::atomic dequeueOptimisticCount; - std::atomic dequeueOvercommit; - - Block* tailBlock; - - public: - bool isExplicit; - ConcurrentQueue* parent; - - protected: -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - /////////////////////////// - // Explicit queue - /////////////////////////// - - struct ExplicitProducer : public ProducerBase - { - explicit ExplicitProducer(ConcurrentQueue* parent_) : - ProducerBase(parent_, true), - blockIndex(nullptr), - pr_blockIndexSlotsUsed(0), - pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), - pr_blockIndexFront(0), - pr_blockIndexEntries(nullptr), - pr_blockIndexRaw(nullptr) - { - size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; - if (poolBasedIndexSize > pr_blockIndexSize) { - pr_blockIndexSize = poolBasedIndexSize; - } - - new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE - } - - ~ExplicitProducer() - { - // Destruct any elements not yet dequeued. - // Since we're in the destructor, we can assume all elements - // are either completely dequeued or completely not (no halfways). - if (this->tailBlock != nullptr) { // Note this means there must be a block index too - // First find the block that's partially dequeued, if any - Block* halfDequeuedBlock = nullptr; - if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { - // The head's not on a block boundary, meaning a block somewhere is partially dequeued - // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) - size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); - while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { - i = (i + 1) & (pr_blockIndexSize - 1); - } - assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); - halfDequeuedBlock = pr_blockIndexEntries[i].block; - } - - // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) - auto block = this->tailBlock; - do { - block = block->next; - if (block->ConcurrentQueue::Block::template is_empty()) { - continue; - } - - size_t i = 0; // Offset into block - if (block == halfDequeuedBlock) { - i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); - } - - // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index - auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); - while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { - (*block)[i++]->~T(); - } - } while (block != this->tailBlock); - } - - // Destroy all blocks that we own - if (this->tailBlock != nullptr) { - auto block = this->tailBlock; - do { - auto nextBlock = block->next; - this->parent->add_block_to_free_list(block); - block = nextBlock; - } while (block != this->tailBlock); - } - - // Destroy the block indices - auto header = static_cast(pr_blockIndexRaw); - while (header != nullptr) { - auto prev = static_cast(header->prev); - header->~BlockIndexHeader(); - (Traits::free)(header); - header = prev; - } - } - - template - inline bool enqueue(U&& element) - { - index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); - index_t newTailIndex = 1 + currentTailIndex; - if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - // We reached the end of a block, start a new one - auto startBlock = this->tailBlock; - auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; - if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { - // We can re-use the block ahead of us, it's empty! - this->tailBlock = this->tailBlock->next; - this->tailBlock->ConcurrentQueue::Block::template reset_empty(); - - // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the - // last block from it first -- except instead of removing then adding, we can just overwrite). - // Note that there must be a valid block index here, since even if allocation failed in the ctor, - // it would have been re-attempted when adding the first block to the queue; since there is such - // a block, a block index must have been successfully allocated. - } - else { - // Whatever head value we see here is >= the last value we saw here (relatively), - // and <= its current value. Since we have the most recent tail, the head must be - // <= to it. - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) - || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { - // We can't enqueue in another block because there's not enough leeway -- the - // tail could surpass the head by the time the block fills up! (Or we'll exceed - // the size limit, if the second part of the condition was true.) - return false; - } - // We're going to need a new block; check that the block index has room - if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { - // Hmm, the circular block index is already full -- we'll need - // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if - // the initial allocation failed in the constructor. - - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - return false; - } - else if (!new_block_index(pr_blockIndexSlotsUsed)) { - return false; - } - } - - // Insert a new block in the circular linked list - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - return false; - } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - if (this->tailBlock == nullptr) { - newBlock->next = newBlock; - } - else { - newBlock->next = this->tailBlock->next; - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - ++pr_blockIndexSlotsUsed; - } - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - // The constructor may throw. We want the element not to appear in the queue in - // that case (without corrupting the queue): - MOODYCAMEL_TRY { - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - // Revert change to the current block, but leave the new block available - // for next time - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; - MOODYCAMEL_RETHROW; - } - } - else { - (void)startBlock; - (void)originalBlockIndexSlotsUsed; - } - - // Add block to block index - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - } - - // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - bool dequeue(U& element) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { - // Might be something to dequeue, let's give it a try - - // Note that this if is purely for performance purposes in the common case when the queue is - // empty and the values are eventually consistent -- we may enter here spuriously. - - // Note that whatever the values of overcommit and tail are, they are not going to change (unless we - // change them) and must be the same value at this point (inside the if) as when the if condition was - // evaluated. - - // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. - // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in - // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). - // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all - // read-modify-write operations are guaranteed to work on the latest value in the modification order), but - // unfortunately that can't be shown to be correct using only the C++11 standard. - // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case - std::atomic_thread_fence(std::memory_order_acquire); - - // Increment optimistic counter, then check if it went over the boundary - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); - - // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever - // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now - // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon - // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. - // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) - // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. - - // Note that we reload tail here in case it changed; it will be the same value as before or greater, since - // this load is sequenced after (happens after) the earlier load above. This is supported by read-read - // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order - tail = this->tailIndex.load(std::memory_order_acquire); - if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { - // Guaranteed to be at least one element to dequeue! - - // Get the index. Note that since there's guaranteed to be at least one element, this - // will never exceed tail. We need to do an acquire-release fence here since it's possible - // that whatever condition got us to this point was for an earlier enqueued element (that - // we already see the memory effects for), but that by the time we increment somebody else - // has incremented it, and we need to see the memory effects for *that* element, which is - // in such a case is necessarily visible on the thread that incremented it in the first - // place with the more current condition (they must have acquired a tail that is at least - // as recent). - auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); - - - // Determine which block the element is in - - auto localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); - - // We need to be careful here about subtracting and dividing because of index wrap-around. - // When an index wraps, we need to preserve the sign of the offset when dividing it by the - // block size (in order to get a correct signed block count offset in all cases): - auto headBase = localBlockIndex->entries[localBlockIndexHead].base; - auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); - auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); - auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; - - // Dequeue - auto& el = *((*block)[index]); - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { - // Make sure the element is still fully dequeued and destroyed even if the assignment - // throws - struct Guard { - Block* block; - index_t index; - - ~Guard() - { - (*block)[index]->~T(); - block->ConcurrentQueue::Block::template set_empty(index); - } - } guard = { block, index }; - - element = std::move(el); // NOLINT - } - else { - element = std::move(el); // NOLINT - el.~T(); // NOLINT - block->ConcurrentQueue::Block::template set_empty(index); - } - - return true; - } - else { - // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent - this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write - } - } - - return false; - } - - template - bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) - { - // First, we need to make sure we have enough room to enqueue all of the elements; - // this means pre-allocating blocks and putting them in the block index (but only if - // all the allocations succeeded). - index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); - auto startBlock = this->tailBlock; - auto originalBlockIndexFront = pr_blockIndexFront; - auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; - - Block* firstAllocatedBlock = nullptr; - - // Figure out how many blocks we'll need to allocate, and do so - size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); - index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - if (blockBaseDiff > 0) { - // Allocate as many blocks as possible from ahead - while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - this->tailBlock = this->tailBlock->next; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; - - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - } - - // Now allocate as many blocks as necessary from the block pool - while (blockBaseDiff > 0) { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); - if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - // Failed to allocate, undo changes (but keep injected blocks) - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { - // Failed to allocate, undo changes (but keep injected blocks) - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - - // pr_blockIndexFront is updated inside new_block_index, so we need to - // update our fallback value too (since we keep the new index even if we - // later fail) - originalBlockIndexFront = originalBlockIndexSlotsUsed; - } - - // Insert a new block in the circular linked list - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - return false; - } - -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template set_all_empty(); - if (this->tailBlock == nullptr) { - newBlock->next = newBlock; - } - else { - newBlock->next = this->tailBlock->next; - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; - - ++pr_blockIndexSlotsUsed; - - auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; - entry.base = currentTailIndex; - entry.block = this->tailBlock; - pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - } - - // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and - // publish the new block index front - auto block = firstAllocatedBlock; - while (true) { - block->ConcurrentQueue::Block::template reset_empty(); - if (block == this->tailBlock) { - break; - } - block = block->next; - } - - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); - } - } - - // Enqueue, one block at a time - index_t newTailIndex = startTailIndex + static_cast(count); - currentTailIndex = startTailIndex; - auto endBlock = this->tailBlock; - this->tailBlock = startBlock; - assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { - this->tailBlock = firstAllocatedBlock; - } - while (true) { - index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(newTailIndex, stopIndex)) { - stopIndex = newTailIndex; - } - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); - } - } - else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - // Must use copy constructor even if move constructor is available - // because we may have to revert if there's an exception. - // Sorry about the horrible templated next line, but it was the only way - // to disable moving *at compile time*, which is important because a type - // may only define a (noexcept) move constructor, and so calls to the - // cctor will not compile, even if they are in an if branch that will never - // be executed - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - // Oh dear, an exception's been thrown -- destroy the elements that - // were enqueued so far and revert the entire bulk operation (we'll keep - // any allocated blocks in our linked list for later, though). - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - pr_blockIndexFront = originalBlockIndexFront; - pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; - this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - MOODYCAMEL_RETHROW; - } - } - - if (this->tailBlock == endBlock) { - assert(currentTailIndex == newTailIndex); - break; - } - this->tailBlock = this->tailBlock->next; - } - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - if (firstAllocatedBlock != nullptr) - blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); - } - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - size_t dequeue_bulk(It& itemFirst, size_t max) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); - if (details::circular_less_than(0, desiredCount)) { - desiredCount = desiredCount < max ? desiredCount : max; - std::atomic_thread_fence(std::memory_order_acquire); - - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); - - tail = this->tailIndex.load(std::memory_order_acquire); - auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); - if (details::circular_less_than(0, actualCount)) { - actualCount = desiredCount < actualCount ? desiredCount : actualCount; - if (actualCount < desiredCount) { - this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); - } - - // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this - // will never exceed tail. - auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); - - // Determine which block the first element is in - auto localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); - - auto headBase = localBlockIndex->entries[localBlockIndexHead].base; - auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); - auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); - auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); - - // Iterate the blocks and dequeue - auto index = firstIndex; - do { - auto firstIndexInBlock = index; - index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - auto block = localBlockIndex->entries[indexIndex].block; - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst++ = std::move(el); - el.~T(); - ++index; - } - } - else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - // It's too late to revert the dequeue, but we can make sure that all - // the dequeued objects are properly destroyed and the block index - // (and empty count) are properly updated before we propagate the exception - do { - block = localBlockIndex->entries[indexIndex].block; - while (index != endIndex) { - (*block)[index++]->~T(); - } - block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); - indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); - - firstIndexInBlock = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; - } - } - block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); - indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); - } while (index != firstIndex + actualCount); - - return actualCount; - } - else { - // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent - this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); - } - } - - return 0; - } - - private: - struct BlockIndexEntry - { - index_t base; - Block* block; - }; - - struct BlockIndexHeader - { - size_t size; - std::atomic front; // Current slot (not next, like pr_blockIndexFront) - BlockIndexEntry* entries; - void* prev; - }; - - - bool new_block_index(size_t numberOfFilledSlotsToExpose) - { - auto prevBlockSizeMask = pr_blockIndexSize - 1; - - // Create the new block - pr_blockIndexSize <<= 1; - auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); - if (newRawPtr == nullptr) { - pr_blockIndexSize >>= 1; // Reset to allow graceful retry - return false; - } - - auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); - - // Copy in all the old indices, if any - size_t j = 0; - if (pr_blockIndexSlotsUsed != 0) { - auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; - do { - newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; - i = (i + 1) & prevBlockSizeMask; - } while (i != pr_blockIndexFront); - } - - // Update everything - auto header = new (newRawPtr) BlockIndexHeader; - header->size = pr_blockIndexSize; - header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); - header->entries = newBlockIndexEntries; - header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later - - pr_blockIndexFront = j; - pr_blockIndexEntries = newBlockIndexEntries; - pr_blockIndexRaw = newRawPtr; - blockIndex.store(header, std::memory_order_release); - - return true; - } - - private: - std::atomic blockIndex; - - // To be used by producer only -- consumer must use the ones in referenced by blockIndex - size_t pr_blockIndexSlotsUsed; - size_t pr_blockIndexSize; - size_t pr_blockIndexFront; // Next slot (not current) - BlockIndexEntry* pr_blockIndexEntries; - void* pr_blockIndexRaw; - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ExplicitProducer* nextExplicitProducer; - private: -#endif - -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - ////////////////////////////////// - // Implicit queue - ////////////////////////////////// - - struct ImplicitProducer : public ProducerBase - { - ImplicitProducer(ConcurrentQueue* parent_) : - ProducerBase(parent_, false), - nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), - blockIndex(nullptr) - { - new_block_index(); - } - - ~ImplicitProducer() - { - // Note that since we're in the destructor we can assume that all enqueue/dequeue operations - // completed already; this means that all undequeued elements are placed contiguously across - // contiguous blocks, and that only the first and last remaining blocks can be only partially - // empty (all other remaining blocks must be completely full). - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - // Unregister ourselves for thread termination notification - if (!this->inactive.load(std::memory_order_relaxed)) { - details::ThreadExitNotifier::unsubscribe(&threadExitListener); - } -#endif - - // Destroy all remaining elements! - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto index = this->headIndex.load(std::memory_order_relaxed); - Block* block = nullptr; - assert(index == tail || details::circular_less_than(index, tail)); - bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed - while (index != tail) { - if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { - if (block != nullptr) { - // Free the old block - this->parent->add_block_to_free_list(block); - } - - block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); - } - - ((*block)[index])->~T(); - ++index; - } - // Even if the queue is empty, there's still one block that's not on the free list - // (unless the head index reached the end of it, in which case the tail will be poised - // to create a new block). - if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { - this->parent->add_block_to_free_list(this->tailBlock); - } - - // Destroy block index - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); - if (localBlockIndex != nullptr) { - for (size_t i = 0; i != localBlockIndex->capacity; ++i) { - localBlockIndex->index[i]->~BlockIndexEntry(); - } - do { - auto prev = localBlockIndex->prev; - localBlockIndex->~BlockIndexHeader(); - (Traits::free)(localBlockIndex); - localBlockIndex = prev; - } while (localBlockIndex != nullptr); - } - } - - template - inline bool enqueue(U&& element) - { - index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); - index_t newTailIndex = 1 + currentTailIndex; - if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - // We reached the end of a block, start a new one - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { - return false; - } -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Find out where we'll be inserting this block in the block index - BlockIndexEntry* idxEntry; - if (!insert_block_index_entry(idxEntry, currentTailIndex)) { - return false; - } - - // Get ahold of a new block - auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); - if (newBlock == nullptr) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - return false; - } -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - // May throw, try to insert now before we publish the fact that we have this new block - MOODYCAMEL_TRY { - new ((*newBlock)[currentTailIndex]) T(std::forward(element)); - } - MOODYCAMEL_CATCH (...) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(newBlock); - MOODYCAMEL_RETHROW; - } - } - - // Insert the new block into the index - idxEntry->value.store(newBlock, std::memory_order_relaxed); - - this->tailBlock = newBlock; - - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - } - - // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); - - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } - - template - bool dequeue(U& element) - { - // See ExplicitProducer::dequeue for rationale and explanation - index_t tail = this->tailIndex.load(std::memory_order_relaxed); - index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { - std::atomic_thread_fence(std::memory_order_acquire); - - index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); - tail = this->tailIndex.load(std::memory_order_acquire); - if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { - index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); - - // Determine which block the element is in - auto entry = get_block_index_entry_for_index(index); - - // Dequeue - auto block = entry->value.load(std::memory_order_relaxed); - auto& el = *((*block)[index]); - - if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - // Note: Acquiring the mutex with every dequeue instead of only when a block - // is released is very sub-optimal, but it is, after all, purely debug code. - debug::DebugLock lock(producer->mutex); -#endif - struct Guard { - Block* block; - index_t index; - BlockIndexEntry* entry; - ConcurrentQueue* parent; - - ~Guard() - { - (*block)[index]->~T(); - if (block->ConcurrentQueue::Block::template set_empty(index)) { - entry->value.store(nullptr, std::memory_order_relaxed); - parent->add_block_to_free_list(block); - } - } - } guard = { block, index, entry, this->parent }; - - element = std::move(el); // NOLINT - } - else { - element = std::move(el); // NOLINT - el.~T(); // NOLINT - - if (block->ConcurrentQueue::Block::template set_empty(index)) { - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Add the block back into the global free pool (and remove from block index) - entry->value.store(nullptr, std::memory_order_relaxed); - } - this->parent->add_block_to_free_list(block); // releases the above store - } - } - - return true; - } - else { - this->dequeueOvercommit.fetch_add(1, std::memory_order_release); - } - } - - return false; - } - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4706) // assignment within conditional expression -#endif - template - bool enqueue_bulk(It itemFirst, size_t count) - { - // First, we need to make sure we have enough room to enqueue all of the elements; - // this means pre-allocating blocks and putting them in the block index (but only if - // all the allocations succeeded). - - // Note that the tailBlock we start off with may not be owned by us any more; - // this happens if it was filled up exactly to the top (setting tailIndex to - // the first index of the next block which is not yet allocated), then dequeued - // completely (putting it on the free list) before we enqueue again. - - index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); - auto startBlock = this->tailBlock; - Block* firstAllocatedBlock = nullptr; - auto endBlock = this->tailBlock; - - // Figure out how many blocks we'll need to allocate, and do so - size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); - index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - if (blockBaseDiff > 0) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - do { - blockBaseDiff -= static_cast(BLOCK_SIZE); - currentTailIndex += static_cast(BLOCK_SIZE); - - // Find out where we'll be inserting this block in the block index - BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell - Block* newBlock; - bool indexInserted = false; - auto head = this->headIndex.load(std::memory_order_relaxed); - assert(!details::circular_less_than(currentTailIndex, head)); - bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); - - if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { - // Index allocation or block allocation failed; revert any other allocations - // and index insertions done so far for this operation - if (indexInserted) { - rewind_block_index_tail(); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - } - currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { - currentTailIndex += static_cast(BLOCK_SIZE); - idxEntry = get_block_index_entry_for_index(currentTailIndex); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - rewind_block_index_tail(); - } - this->parent->add_blocks_to_free_list(firstAllocatedBlock); - this->tailBlock = startBlock; - - return false; - } - -#ifdef MCDBGQ_TRACKMEM - newBlock->owner = this; -#endif - newBlock->ConcurrentQueue::Block::template reset_empty(); - newBlock->next = nullptr; - - // Insert the new block into the index - idxEntry->value.store(newBlock, std::memory_order_relaxed); - - // Store the chain of blocks so that we can undo if later allocations fail, - // and so that we can find the blocks when we do the actual enqueueing - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { - assert(this->tailBlock != nullptr); - this->tailBlock->next = newBlock; - } - this->tailBlock = newBlock; - endBlock = newBlock; - firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; - } while (blockBaseDiff > 0); - } - - // Enqueue, one block at a time - index_t newTailIndex = startTailIndex + static_cast(count); - currentTailIndex = startTailIndex; - this->tailBlock = startBlock; - assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { - this->tailBlock = firstAllocatedBlock; - } - while (true) { - index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(newTailIndex, stopIndex)) { - stopIndex = newTailIndex; - } - MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); - } - } - else { - MOODYCAMEL_TRY { - while (currentTailIndex != stopIndex) { - new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); - ++currentTailIndex; - ++itemFirst; - } - } - MOODYCAMEL_CATCH (...) { - auto constructedStopIndex = currentTailIndex; - auto lastBlockEnqueued = this->tailBlock; - - if (!details::is_trivially_destructible::value) { - auto block = startBlock; - if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { - block = firstAllocatedBlock; - } - currentTailIndex = startTailIndex; - while (true) { - stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - if (details::circular_less_than(constructedStopIndex, stopIndex)) { - stopIndex = constructedStopIndex; - } - while (currentTailIndex != stopIndex) { - (*block)[currentTailIndex++]->~T(); - } - if (block == lastBlockEnqueued) { - break; - } - block = block->next; - } - } - - currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); - for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { - currentTailIndex += static_cast(BLOCK_SIZE); - auto idxEntry = get_block_index_entry_for_index(currentTailIndex); - idxEntry->value.store(nullptr, std::memory_order_relaxed); - rewind_block_index_tail(); - } - this->parent->add_blocks_to_free_list(firstAllocatedBlock); - this->tailBlock = startBlock; - MOODYCAMEL_RETHROW; - } - } - - if (this->tailBlock == endBlock) { - assert(currentTailIndex == newTailIndex); - break; - } - this->tailBlock = this->tailBlock->next; - } - this->tailIndex.store(newTailIndex, std::memory_order_release); - return true; - } -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - template - size_t dequeue_bulk(It& itemFirst, size_t max) - { - auto tail = this->tailIndex.load(std::memory_order_relaxed); - auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); - auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); - if (details::circular_less_than(0, desiredCount)) { - desiredCount = desiredCount < max ? desiredCount : max; - std::atomic_thread_fence(std::memory_order_acquire); - - auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); - - tail = this->tailIndex.load(std::memory_order_acquire); - auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); - if (details::circular_less_than(0, actualCount)) { - actualCount = desiredCount < actualCount ? desiredCount : actualCount; - if (actualCount < desiredCount) { - this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); - } - - // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this - // will never exceed tail. - auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); - - // Iterate the blocks and dequeue - auto index = firstIndex; - BlockIndexHeader* localBlockIndex; - auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); - do { - auto blockStartIndex = index; - index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - - auto entry = localBlockIndex->index[indexIndex]; - auto block = entry->value.load(std::memory_order_relaxed); - if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst++ = std::move(el); - el.~T(); - ++index; - } - } - else { - MOODYCAMEL_TRY { - while (index != endIndex) { - auto& el = *((*block)[index]); - *itemFirst = std::move(el); - ++itemFirst; - el.~T(); - ++index; - } - } - MOODYCAMEL_CATCH (...) { - do { - entry = localBlockIndex->index[indexIndex]; - block = entry->value.load(std::memory_order_relaxed); - while (index != endIndex) { - (*block)[index++]->~T(); - } - - if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - entry->value.store(nullptr, std::memory_order_relaxed); - this->parent->add_block_to_free_list(block); - } - indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); - - blockStartIndex = index; - endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); - endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; - } while (index != firstIndex + actualCount); - - MOODYCAMEL_RETHROW; - } - } - if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - // Note that the set_many_empty above did a release, meaning that anybody who acquires the block - // we're about to free can use it safely since our writes (and reads!) will have happened-before then. - entry->value.store(nullptr, std::memory_order_relaxed); - } - this->parent->add_block_to_free_list(block); // releases the above store - } - indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); - } while (index != firstIndex + actualCount); - - return actualCount; - } - else { - this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); - } - } - - return 0; - } - - private: - // The block size must be > 1, so any number with the low bit set is an invalid block base index - static const index_t INVALID_BLOCK_BASE = 1; - - struct BlockIndexEntry - { - std::atomic key; - std::atomic value; - }; - - struct BlockIndexHeader - { - size_t capacity; - std::atomic tail; - BlockIndexEntry* entries; - BlockIndexEntry** index; - BlockIndexHeader* prev; - }; - - template - inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) - { - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK - if (localBlockIndex == nullptr) { - return false; // this can happen if new_block_index failed in the constructor - } - size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); - idxEntry = localBlockIndex->index[newTail]; - if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || - idxEntry->value.load(std::memory_order_relaxed) == nullptr) { - - idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); - localBlockIndex->tail.store(newTail, std::memory_order_release); - return true; - } - - // No room in the old block index, try to allocate another one! - MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { - return false; - } - else if (!new_block_index()) { - return false; - } - else { - localBlockIndex = blockIndex.load(std::memory_order_relaxed); - newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); - idxEntry = localBlockIndex->index[newTail]; - assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); - idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); - localBlockIndex->tail.store(newTail, std::memory_order_release); - return true; - } - } - - inline void rewind_block_index_tail() - { - auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); - localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); - } - - inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const - { - BlockIndexHeader* localBlockIndex; - auto idx = get_block_index_index_for_index(index, localBlockIndex); - return localBlockIndex->index[idx]; - } - - inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - debug::DebugLock lock(mutex); -#endif - index &= ~static_cast(BLOCK_SIZE - 1); - localBlockIndex = blockIndex.load(std::memory_order_acquire); - auto tail = localBlockIndex->tail.load(std::memory_order_acquire); - auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); - assert(tailBase != INVALID_BLOCK_BASE); - // Note: Must use division instead of shift because the index may wrap around, causing a negative - // offset, whose negativity we want to preserve - auto offset = static_cast(static_cast::type>(index - tailBase) / static_cast::type>(BLOCK_SIZE)); - size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); - assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); - return idx; - } - - bool new_block_index() - { - auto prev = blockIndex.load(std::memory_order_relaxed); - size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; - auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; - auto raw = static_cast((Traits::malloc)( - sizeof(BlockIndexHeader) + - std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + - std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); - if (raw == nullptr) { - return false; - } - - auto header = new (raw) BlockIndexHeader; - auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); - auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); - if (prev != nullptr) { - auto prevTail = prev->tail.load(std::memory_order_relaxed); - auto prevPos = prevTail; - size_t i = 0; - do { - prevPos = (prevPos + 1) & (prev->capacity - 1); - index[i++] = prev->index[prevPos]; - } while (prevPos != prevTail); - assert(i == prevCapacity); - } - for (size_t i = 0; i != entryCount; ++i) { - new (entries + i) BlockIndexEntry; - entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); - index[prevCapacity + i] = entries + i; - } - header->prev = prev; - header->entries = entries; - header->index = index; - header->capacity = nextBlockIndexCapacity; - header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); - - blockIndex.store(header, std::memory_order_release); - - nextBlockIndexCapacity <<= 1; - - return true; - } - - private: - size_t nextBlockIndexCapacity; - std::atomic blockIndex; - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - public: - details::ThreadExitListener threadExitListener; - private: -#endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - public: - ImplicitProducer* nextImplicitProducer; - private: -#endif - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX - mutable debug::DebugMutex mutex; -#endif -#ifdef MCDBGQ_TRACKMEM - friend struct MemStats; -#endif - }; - - - ////////////////////////////////// - // Block pool manipulation - ////////////////////////////////// - - void populate_initial_block_list(size_t blockCount) - { - initialBlockPoolSize = blockCount; - if (initialBlockPoolSize == 0) { - initialBlockPool = nullptr; - return; - } - - initialBlockPool = create_array(blockCount); - if (initialBlockPool == nullptr) { - initialBlockPoolSize = 0; - } - for (size_t i = 0; i < initialBlockPoolSize; ++i) { - initialBlockPool[i].dynamicallyAllocated = false; - } - } - - inline Block* try_get_block_from_initial_pool() - { - if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { - return nullptr; - } - - auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); - - return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; - } - - inline void add_block_to_free_list(Block* block) - { -#ifdef MCDBGQ_TRACKMEM - block->owner = nullptr; -#endif - if (!Traits::RECYCLE_ALLOCATED_BLOCKS && block->dynamicallyAllocated) { - destroy(block); - } - else { - freeList.add(block); - } - } - - inline void add_blocks_to_free_list(Block* block) - { - while (block != nullptr) { - auto next = block->next; - add_block_to_free_list(block); - block = next; - } - } - - inline Block* try_get_block_from_free_list() - { - return freeList.try_get(); - } - - // Gets a free block from one of the memory pools, or allocates a new one (if applicable) - template - Block* requisition_block() - { - auto block = try_get_block_from_initial_pool(); - if (block != nullptr) { - return block; - } - - block = try_get_block_from_free_list(); - if (block != nullptr) { - return block; - } - - MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { - return create(); - } - else { - return nullptr; - } - } - - -#ifdef MCDBGQ_TRACKMEM - public: - struct MemStats { - size_t allocatedBlocks; - size_t usedBlocks; - size_t freeBlocks; - size_t ownedBlocksExplicit; - size_t ownedBlocksImplicit; - size_t implicitProducers; - size_t explicitProducers; - size_t elementsEnqueued; - size_t blockClassBytes; - size_t queueClassBytes; - size_t implicitBlockIndexBytes; - size_t explicitBlockIndexBytes; - - friend class ConcurrentQueue; - - private: - static MemStats getFor(ConcurrentQueue* q) - { - MemStats stats = { 0 }; - - stats.elementsEnqueued = q->size_approx(); - - auto block = q->freeList.head_unsafe(); - while (block != nullptr) { - ++stats.allocatedBlocks; - ++stats.freeBlocks; - block = block->freeListNext.load(std::memory_order_relaxed); - } - - for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - bool implicit = dynamic_cast(ptr) != nullptr; - stats.implicitProducers += implicit ? 1 : 0; - stats.explicitProducers += implicit ? 0 : 1; - - if (implicit) { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ImplicitProducer); - auto head = prod->headIndex.load(std::memory_order_relaxed); - auto tail = prod->tailIndex.load(std::memory_order_relaxed); - auto hash = prod->blockIndex.load(std::memory_order_relaxed); - if (hash != nullptr) { - for (size_t i = 0; i != hash->capacity; ++i) { - if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { - ++stats.allocatedBlocks; - ++stats.ownedBlocksImplicit; - } - } - stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); - for (; hash != nullptr; hash = hash->prev) { - stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); - } - } - for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { - //auto block = prod->get_block_index_entry_for_index(head); - ++stats.usedBlocks; - } - } - else { - auto prod = static_cast(ptr); - stats.queueClassBytes += sizeof(ExplicitProducer); - auto tailBlock = prod->tailBlock; - bool wasNonEmpty = false; - if (tailBlock != nullptr) { - auto block = tailBlock; - do { - ++stats.allocatedBlocks; - if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { - ++stats.usedBlocks; - wasNonEmpty = wasNonEmpty || block != tailBlock; - } - ++stats.ownedBlocksExplicit; - block = block->next; - } while (block != tailBlock); - } - auto index = prod->blockIndex.load(std::memory_order_relaxed); - while (index != nullptr) { - stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); - index = static_cast(index->prev); - } - } - } - - auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); - stats.allocatedBlocks += freeOnInitialPool; - stats.freeBlocks += freeOnInitialPool; - - stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; - stats.queueClassBytes += sizeof(ConcurrentQueue); - - return stats; - } - }; - - // For debugging only. Not thread-safe. - MemStats getMemStats() - { - return MemStats::getFor(this); - } - private: - friend struct MemStats; -#endif - - - ////////////////////////////////// - // Producer list manipulation - ////////////////////////////////// - - ProducerBase* recycle_or_create_producer(bool isExplicit) - { -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - // Try to re-use one first - for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { - if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { - bool expected = true; - if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { - // We caught one! It's been marked as activated, the caller can have it - return ptr; - } - } - } - - return add_producer(isExplicit ? static_cast(create(this)) : create(this)); - } - - ProducerBase* add_producer(ProducerBase* producer) - { - // Handle failed memory allocation - if (producer == nullptr) { - return nullptr; - } - - producerCount.fetch_add(1, std::memory_order_relaxed); - - // Add it to the lock-free list - auto prevTail = producerListTail.load(std::memory_order_relaxed); - do { - producer->next = prevTail; - } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - if (producer->isExplicit) { - auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextExplicitProducer = prevTailExplicit; - } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } - else { - auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); - do { - static_cast(producer)->nextImplicitProducer = prevTailImplicit; - } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); - } -#endif - - return producer; - } - - void reown_producers() - { - // After another instance is moved-into/swapped-with this one, all the - // producers we stole still think their parents are the other queue. - // So fix them up! - for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { - ptr->parent = this; - } - } - - - ////////////////////////////////// - // Implicit producer hash - ////////////////////////////////// - - struct ImplicitProducerKVP - { - std::atomic key; - ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place - - ImplicitProducerKVP() : value(nullptr) { } - - ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT - { - key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); - value = other.value; - } - - inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT - { - swap(other); - return *this; - } - - inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT - { - if (this != &other) { - details::swap_relaxed(key, other.key); - std::swap(value, other.value); - } - } - }; - - template - friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; - - struct ImplicitProducerHash - { - size_t capacity; - ImplicitProducerKVP* entries; - ImplicitProducerHash* prev; - }; - - inline void populate_initial_implicit_producer_hash() - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { - return; - } - else { - implicitProducerHashCount.store(0, std::memory_order_relaxed); - auto hash = &initialImplicitProducerHash; - hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; - hash->entries = &initialImplicitProducerHashEntries[0]; - for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { - initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); - } - hash->prev = nullptr; - implicitProducerHash.store(hash, std::memory_order_relaxed); - } - } - - void swap_implicit_producer_hashes(ConcurrentQueue& other) - { - MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { - return; - } - else { - // Swap (assumes our implicit producer hash is initialized) - initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); - initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; - other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; - - details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); - - details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); - if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { - implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); - } - else { - ImplicitProducerHash* hash; - for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { - continue; - } - hash->prev = &initialImplicitProducerHash; - } - if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { - other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); - } - else { - ImplicitProducerHash* hash; - for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { - continue; - } - hash->prev = &other.initialImplicitProducerHash; - } - } - } - - // Only fails (returns nullptr) if memory allocation fails - ImplicitProducer* get_or_add_implicit_producer() - { - // Note that since the data is essentially thread-local (key is thread ID), - // there's a reduced need for fences (memory ordering is already consistent - // for any individual thread), except for the current table itself. - - // Start by looking for the thread ID in the current and all previous hash tables. - // If it's not found, it must not be in there yet, since this same thread would - // have added it previously to one of the tables that we traversed. - - // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - - auto id = details::thread_id(); - auto hashedId = details::hash_thread_id(id); - - auto mainHash = implicitProducerHash.load(std::memory_order_acquire); - assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) - for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { - // Look for the id in this hash - auto index = hashedId; - while (true) { // Not an infinite loop because at least one slot is free in the hash table - index &= hash->capacity - 1u; - - auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); - if (probedKey == id) { - // Found it! If we had to search several hashes deep, though, we should lazily add it - // to the current main hash table to avoid the extended search next time. - // Note there's guaranteed to be room in the current hash table since every subsequent - // table implicitly reserves space for all previous tables (there's only one - // implicitProducerHashCount). - auto value = hash->entries[index].value; - if (hash != mainHash) { - index = hashedId; - while (true) { - index &= mainHash->capacity - 1u; - auto empty = details::invalid_thread_id; -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - auto reusable = details::invalid_thread_id2; - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed) || - mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { -#else - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { -#endif - mainHash->entries[index].value = value; - break; - } - ++index; - } - } - - return value; - } - if (probedKey == details::invalid_thread_id) { - break; // Not in this hash table - } - ++index; - } - } - - // Insert! - auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); - while (true) { - // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) - if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { - // We've acquired the resize lock, try to allocate a bigger hash table. - // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when - // we reload implicitProducerHash it must be the most recent version (it only gets changed within this - // locked block). - mainHash = implicitProducerHash.load(std::memory_order_acquire); - if (newCount >= (mainHash->capacity >> 1)) { - size_t newCapacity = mainHash->capacity << 1; - while (newCount >= (newCapacity >> 1)) { - newCapacity <<= 1; - } - auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); - if (raw == nullptr) { - // Allocation failed - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); - implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); - return nullptr; - } - - auto newHash = new (raw) ImplicitProducerHash; - newHash->capacity = static_cast(newCapacity); - newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); - for (size_t i = 0; i != newCapacity; ++i) { - new (newHash->entries + i) ImplicitProducerKVP; - newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); - } - newHash->prev = mainHash; - implicitProducerHash.store(newHash, std::memory_order_release); - implicitProducerHashResizeInProgress.clear(std::memory_order_release); - mainHash = newHash; - } - else { - implicitProducerHashResizeInProgress.clear(std::memory_order_release); - } - } - - // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table - // to finish being allocated by another thread (and if we just finished allocating above, the condition will - // always be true) - if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { - auto producer = static_cast(recycle_or_create_producer(false)); - if (producer == nullptr) { - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); - return nullptr; - } - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; - producer->threadExitListener.userData = producer; - details::ThreadExitNotifier::subscribe(&producer->threadExitListener); -#endif - - auto index = hashedId; - while (true) { - index &= mainHash->capacity - 1u; - auto empty = details::invalid_thread_id; -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - auto reusable = details::invalid_thread_id2; - if (mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { - implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); // already counted as a used slot - mainHash->entries[index].value = producer; - break; - } -#endif - if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { - mainHash->entries[index].value = producer; - break; - } - ++index; - } - return producer; - } - - // Hmm, the old hash is quite full and somebody else is busy allocating a new one. - // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, - // we try to allocate ourselves). - mainHash = implicitProducerHash.load(std::memory_order_acquire); - } - } - -#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED - void implicit_producer_thread_exited(ImplicitProducer* producer) - { - // Remove from hash -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugLock lock(implicitProdMutex); -#endif - auto hash = implicitProducerHash.load(std::memory_order_acquire); - assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place - auto id = details::thread_id(); - auto hashedId = details::hash_thread_id(id); - details::thread_id_t probedKey; - - // We need to traverse all the hashes just in case other threads aren't on the current one yet and are - // trying to add an entry thinking there's a free slot (because they reused a producer) - for (; hash != nullptr; hash = hash->prev) { - auto index = hashedId; - do { - index &= hash->capacity - 1u; - probedKey = id; - if (hash->entries[index].key.compare_exchange_strong(probedKey, details::invalid_thread_id2, std::memory_order_seq_cst, std::memory_order_relaxed)) { - break; - } - ++index; - } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place - } - - // Mark the queue as being recyclable - producer->inactive.store(true, std::memory_order_release); - } - - static void implicit_producer_thread_exited_callback(void* userData) - { - auto producer = static_cast(userData); - auto queue = producer->parent; - queue->implicit_producer_thread_exited(producer); - } -#endif - - ////////////////////////////////// - // Utility functions - ////////////////////////////////// - - template - static inline void* aligned_malloc(size_t size) - { - MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) - return (Traits::malloc)(size); - else { - size_t alignment = std::alignment_of::value; - void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); - if (!raw) - return nullptr; - char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); - *(reinterpret_cast(ptr) - 1) = raw; - return ptr; - } - } - - template - static inline void aligned_free(void* ptr) - { - MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) - return (Traits::free)(ptr); - else - (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); - } - - template - static inline U* create_array(size_t count) - { - assert(count > 0); - U* p = static_cast(aligned_malloc(sizeof(U) * count)); - if (p == nullptr) - return nullptr; - - for (size_t i = 0; i != count; ++i) - new (p + i) U(); - return p; - } - - template - static inline void destroy_array(U* p, size_t count) - { - if (p != nullptr) { - assert(count > 0); - for (size_t i = count; i != 0; ) - (p + --i)->~U(); - } - aligned_free(p); - } - - template - static inline U* create() - { - void* p = aligned_malloc(sizeof(U)); - return p != nullptr ? new (p) U : nullptr; - } - - template - static inline U* create(A1&& a1) - { - void* p = aligned_malloc(sizeof(U)); - return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; - } - - template - static inline void destroy(U* p) - { - if (p != nullptr) - p->~U(); - aligned_free(p); - } - -private: - std::atomic producerListTail; - std::atomic producerCount; - - std::atomic initialBlockPoolIndex; - Block* initialBlockPool; - size_t initialBlockPoolSize; - -#ifndef MCDBGQ_USEDEBUGFREELIST - FreeList freeList; -#else - debug::DebugFreeList freeList; -#endif - - std::atomic implicitProducerHash; - std::atomic implicitProducerHashCount; // Number of slots logically used - ImplicitProducerHash initialImplicitProducerHash; - std::array initialImplicitProducerHashEntries; - std::atomic_flag implicitProducerHashResizeInProgress; - - std::atomic nextExplicitConsumerId; - std::atomic globalExplicitConsumerOffset; - -#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH - debug::DebugMutex implicitProdMutex; -#endif - -#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG - std::atomic explicitProducers; - std::atomic implicitProducers; -#endif -}; - - -template -ProducerToken::ProducerToken(ConcurrentQueue& queue) - : producer(queue.recycle_or_create_producer(true)) -{ - if (producer != nullptr) { - producer->token = this; - } -} - -template -ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) - : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) -{ - if (producer != nullptr) { - producer->token = this; - } -} - -template -ConsumerToken::ConsumerToken(ConcurrentQueue& queue) - : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) -{ - initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); - lastKnownGlobalOffset = static_cast(-1); -} - -template -ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) - : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) -{ - initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); - lastKnownGlobalOffset = static_cast(-1); -} - -template -inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -template -inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT -{ - a.swap(b); -} - -} - -#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) -#pragma warning(pop) -#endif - -#if defined(__GNUC__) && !defined(__INTEL_COMPILER) -#pragma GCC diagnostic pop -#endif diff --git a/app/common/moodycamel/concurrentqueue/lightweightsemaphore.h b/app/common/moodycamel/concurrentqueue/lightweightsemaphore.h deleted file mode 100644 index 41ba09438..000000000 --- a/app/common/moodycamel/concurrentqueue/lightweightsemaphore.h +++ /dev/null @@ -1,425 +0,0 @@ -// Provides an efficient implementation of a semaphore (LightweightSemaphore). -// This is an extension of Jeff Preshing's sempahore implementation (licensed -// under the terms of its separate zlib license) that has been adapted and -// extended by Cameron Desrochers. - -#pragma once - -#include // For std::size_t -#include -#include // For std::make_signed - -#if defined(_WIN32) -// Avoid including windows.h in a header; we only need a handful of -// items, so we'll redeclare them here (this is relatively safe since -// the API generally has to remain stable between Windows versions). -// I know this is an ugly hack but it still beats polluting the global -// namespace with thousands of generic names or adding a .cpp for nothing. -extern "C" { - struct _SECURITY_ATTRIBUTES; - __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); - __declspec(dllimport) int __stdcall CloseHandle(void* hObject); - __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); - __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); -} -#elif defined(__MACH__) -#include -#elif defined(__unix__) -#include - -#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) -#if __GLIBC_PREREQ(2,30) -#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC -#endif -#endif -#endif - -namespace moodycamel -{ -namespace details -{ - -// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's -// portable + lightweight semaphore implementations, originally from -// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h -// LICENSE: -// Copyright (c) 2015 Jeff Preshing -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgement in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -#if defined(_WIN32) -class Semaphore -{ -private: - void* m_hSema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - const long maxLong = 0x7fffffff; - m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); - assert(m_hSema); - } - - ~Semaphore() - { - CloseHandle(m_hSema); - } - - bool wait() - { - const unsigned long infinite = 0xffffffff; - return WaitForSingleObject(m_hSema, infinite) == 0; - } - - bool try_wait() - { - return WaitForSingleObject(m_hSema, 0) == 0; - } - - bool timed_wait(std::uint64_t usecs) - { - return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; - } - - void signal(int count = 1) - { - while (!ReleaseSemaphore(m_hSema, count, nullptr)); - } -}; -#elif defined(__MACH__) -//--------------------------------------------------------- -// Semaphore (Apple iOS and OSX) -// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html -//--------------------------------------------------------- -class Semaphore -{ -private: - semaphore_t m_sema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); - assert(rc == KERN_SUCCESS); - (void)rc; - } - - ~Semaphore() - { - semaphore_destroy(mach_task_self(), m_sema); - } - - bool wait() - { - return semaphore_wait(m_sema) == KERN_SUCCESS; - } - - bool try_wait() - { - return timed_wait(0); - } - - bool timed_wait(std::uint64_t timeout_usecs) - { - mach_timespec_t ts; - ts.tv_sec = static_cast(timeout_usecs / 1000000); - ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); - - // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html - kern_return_t rc = semaphore_timedwait(m_sema, ts); - return rc == KERN_SUCCESS; - } - - void signal() - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - - void signal(int count) - { - while (count-- > 0) - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - } -}; -#elif defined(__unix__) -//--------------------------------------------------------- -// Semaphore (POSIX, Linux) -//--------------------------------------------------------- -class Semaphore -{ -private: - sem_t m_sema; - - Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; - -public: - Semaphore(int initialCount = 0) - { - assert(initialCount >= 0); - int rc = sem_init(&m_sema, 0, static_cast(initialCount)); - assert(rc == 0); - (void)rc; - } - - ~Semaphore() - { - sem_destroy(&m_sema); - } - - bool wait() - { - // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error - int rc; - do { - rc = sem_wait(&m_sema); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool try_wait() - { - int rc; - do { - rc = sem_trywait(&m_sema); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool timed_wait(std::uint64_t usecs) - { - struct timespec ts; - const int usecs_in_1_sec = 1000000; - const int nsecs_in_1_sec = 1000000000; -#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC - clock_gettime(CLOCK_MONOTONIC, &ts); -#else - clock_gettime(CLOCK_REALTIME, &ts); -#endif - ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); - ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; - // sem_timedwait bombs if you have more than 1e9 in tv_nsec - // so we have to clean things up before passing it in - if (ts.tv_nsec >= nsecs_in_1_sec) { - ts.tv_nsec -= nsecs_in_1_sec; - ++ts.tv_sec; - } - - int rc; - do { -#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC - rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts); -#else - rc = sem_timedwait(&m_sema, &ts); -#endif - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - void signal() - { - while (sem_post(&m_sema) == -1); - } - - void signal(int count) - { - while (count-- > 0) - { - while (sem_post(&m_sema) == -1); - } - } -}; -#else -#error Unsupported platform! (No semaphore wrapper available) -#endif - -} // end namespace details - - -//--------------------------------------------------------- -// LightweightSemaphore -//--------------------------------------------------------- -class LightweightSemaphore -{ -public: - typedef std::make_signed::type ssize_t; - -private: - std::atomic m_count; - details::Semaphore m_sema; - int m_maxSpins; - - bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) - { - ssize_t oldCount; - int spin = m_maxSpins; - while (--spin >= 0) - { - oldCount = m_count.load(std::memory_order_relaxed); - if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) - return true; - std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. - } - oldCount = m_count.fetch_sub(1, std::memory_order_acquire); - if (oldCount > 0) - return true; - if (timeout_usecs < 0) - { - if (m_sema.wait()) - return true; - } - if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) - return true; - // At this point, we've timed out waiting for the semaphore, but the - // count is still decremented indicating we may still be waiting on - // it. So we have to re-adjust the count, but only if the semaphore - // wasn't signaled enough times for us too since then. If it was, we - // need to release the semaphore too. - while (true) - { - oldCount = m_count.load(std::memory_order_acquire); - if (oldCount >= 0 && m_sema.try_wait()) - return true; - if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) - return false; - } - } - - ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) - { - assert(max > 0); - ssize_t oldCount; - int spin = m_maxSpins; - while (--spin >= 0) - { - oldCount = m_count.load(std::memory_order_relaxed); - if (oldCount > 0) - { - ssize_t newCount = oldCount > max ? oldCount - max : 0; - if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) - return oldCount - newCount; - } - std::atomic_signal_fence(std::memory_order_acquire); - } - oldCount = m_count.fetch_sub(1, std::memory_order_acquire); - if (oldCount <= 0) - { - if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) - { - while (true) - { - oldCount = m_count.load(std::memory_order_acquire); - if (oldCount >= 0 && m_sema.try_wait()) - break; - if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) - return 0; - } - } - } - if (max > 1) - return 1 + tryWaitMany(max - 1); - return 1; - } - -public: - LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) - { - assert(initialCount >= 0); - assert(maxSpins >= 0); - } - - bool tryWait() - { - ssize_t oldCount = m_count.load(std::memory_order_relaxed); - while (oldCount > 0) - { - if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) - return true; - } - return false; - } - - bool wait() - { - return tryWait() || waitWithPartialSpinning(); - } - - bool wait(std::int64_t timeout_usecs) - { - return tryWait() || waitWithPartialSpinning(timeout_usecs); - } - - // Acquires between 0 and (greedily) max, inclusive - ssize_t tryWaitMany(ssize_t max) - { - assert(max >= 0); - ssize_t oldCount = m_count.load(std::memory_order_relaxed); - while (oldCount > 0) - { - ssize_t newCount = oldCount > max ? oldCount - max : 0; - if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) - return oldCount - newCount; - } - return 0; - } - - // Acquires at least one, and (greedily) at most max - ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) - { - assert(max >= 0); - ssize_t result = tryWaitMany(max); - if (result == 0 && max > 0) - result = waitManyWithPartialSpinning(max, timeout_usecs); - return result; - } - - ssize_t waitMany(ssize_t max) - { - ssize_t result = waitMany(max, -1); - assert(result > 0); - return result; - } - - void signal(ssize_t count = 1) - { - assert(count >= 0); - ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); - ssize_t toRelease = -oldCount < count ? -oldCount : count; - if (toRelease > 0) - { - m_sema.signal((int)toRelease); - } - } - - std::size_t availableApprox() const - { - ssize_t count = m_count.load(std::memory_order_relaxed); - return count > 0 ? static_cast(count) : 0; - } -}; - -} // end namespace moodycamel diff --git a/app/common/moodycamel/readerwriterqueue/atomicops.h b/app/common/moodycamel/readerwriterqueue/atomicops.h deleted file mode 100644 index a3e57b930..000000000 --- a/app/common/moodycamel/readerwriterqueue/atomicops.h +++ /dev/null @@ -1,761 +0,0 @@ -// ©2013-2016 Cameron Desrochers. -// Distributed under the simplified BSD license (see the license file that -// should have come with this header). -// Uses Jeff Preshing's semaphore implementation (under the terms of its -// separate zlib license, embedded below). - -#pragma once - -// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) implementation -// of low-level memory barriers, plus a few semi-portable utility macros (for inlining and alignment). -// Also has a basic atomic type (limited to hardware-supported atomics with no memory ordering guarantees). -// Uses the AE_* prefix for macros (historical reasons), and the "moodycamel" namespace for symbols. - -#include -#include -#include -#include -#include -#include - -// Platform detection -#if defined(__INTEL_COMPILER) -#define AE_ICC -#elif defined(_MSC_VER) -#define AE_VCPP -#elif defined(__GNUC__) -#define AE_GCC -#endif - -#if defined(_M_IA64) || defined(__ia64__) -#define AE_ARCH_IA64 -#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) -#define AE_ARCH_X64 -#elif defined(_M_IX86) || defined(__i386__) -#define AE_ARCH_X86 -#elif defined(_M_PPC) || defined(__powerpc__) -#define AE_ARCH_PPC -#else -#define AE_ARCH_UNKNOWN -#endif - - -// AE_UNUSED -#define AE_UNUSED(x) ((void)x) - -// AE_NO_TSAN/AE_TSAN_ANNOTATE_* -#if defined(__has_feature) -#if __has_feature(thread_sanitizer) -#if __cplusplus >= 201703L // inline variables require C++17 -namespace moodycamel { inline int ae_tsan_global; } -#define AE_TSAN_ANNOTATE_RELEASE() AnnotateHappensBefore(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) -#define AE_TSAN_ANNOTATE_ACQUIRE() AnnotateHappensAfter(__FILE__, __LINE__, (void *)(&::moodycamel::ae_tsan_global)) -extern "C" void AnnotateHappensBefore(const char*, int, void*); -extern "C" void AnnotateHappensAfter(const char*, int, void*); -#else // when we can't work with tsan, attempt to disable its warnings -#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) -#endif -#endif -#endif -#ifndef AE_NO_TSAN -#define AE_NO_TSAN -#endif -#ifndef AE_TSAN_ANNOTATE_RELEASE -#define AE_TSAN_ANNOTATE_RELEASE() -#define AE_TSAN_ANNOTATE_ACQUIRE() -#endif - - -// AE_FORCEINLINE -#if defined(AE_VCPP) || defined(AE_ICC) -#define AE_FORCEINLINE __forceinline -#elif defined(AE_GCC) -//#define AE_FORCEINLINE __attribute__((always_inline)) -#define AE_FORCEINLINE inline -#else -#define AE_FORCEINLINE inline -#endif - - -// AE_ALIGN -#if defined(AE_VCPP) || defined(AE_ICC) -#define AE_ALIGN(x) __declspec(align(x)) -#elif defined(AE_GCC) -#define AE_ALIGN(x) __attribute__((aligned(x))) -#else -// Assume GCC compliant syntax... -#define AE_ALIGN(x) __attribute__((aligned(x))) -#endif - - -// Portable atomic fences implemented below: - -namespace moodycamel { - -enum memory_order { - memory_order_relaxed, - memory_order_acquire, - memory_order_release, - memory_order_acq_rel, - memory_order_seq_cst, - - // memory_order_sync: Forces a full sync: - // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad - memory_order_sync = memory_order_seq_cst -}; - -} // end namespace moodycamel - -#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || (defined(AE_ICC) && __INTEL_COMPILER < 1600) -// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences - -#include - -#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) -#define AeFullSync _mm_mfence -#define AeLiteSync _mm_mfence -#elif defined(AE_ARCH_IA64) -#define AeFullSync __mf -#define AeLiteSync __mf -#elif defined(AE_ARCH_PPC) -#include -#define AeFullSync __sync -#define AeLiteSync __lwsync -#endif - - -#ifdef AE_VCPP -#pragma warning(push) -#pragma warning(disable: 4365) // Disable erroneous 'conversion from long to unsigned int, signed/unsigned mismatch' error when using `assert` -#ifdef __cplusplus_cli -#pragma managed(push, off) -#endif -#endif - -namespace moodycamel { - -AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN -{ - switch (order) { - case memory_order_relaxed: break; - case memory_order_acquire: _ReadBarrier(); break; - case memory_order_release: _WriteBarrier(); break; - case memory_order_acq_rel: _ReadWriteBarrier(); break; - case memory_order_seq_cst: _ReadWriteBarrier(); break; - default: assert(false); - } -} - -// x86/x64 have a strong memory model -- all loads and stores have -// acquire and release semantics automatically (so only need compiler -// barriers for those). -#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) -AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN -{ - switch (order) { - case memory_order_relaxed: break; - case memory_order_acquire: _ReadBarrier(); break; - case memory_order_release: _WriteBarrier(); break; - case memory_order_acq_rel: _ReadWriteBarrier(); break; - case memory_order_seq_cst: - _ReadWriteBarrier(); - AeFullSync(); - _ReadWriteBarrier(); - break; - default: assert(false); - } -} -#else -AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN -{ - // Non-specialized arch, use heavier memory barriers everywhere just in case :-( - switch (order) { - case memory_order_relaxed: - break; - case memory_order_acquire: - _ReadBarrier(); - AeLiteSync(); - _ReadBarrier(); - break; - case memory_order_release: - _WriteBarrier(); - AeLiteSync(); - _WriteBarrier(); - break; - case memory_order_acq_rel: - _ReadWriteBarrier(); - AeLiteSync(); - _ReadWriteBarrier(); - break; - case memory_order_seq_cst: - _ReadWriteBarrier(); - AeFullSync(); - _ReadWriteBarrier(); - break; - default: assert(false); - } -} -#endif -} // end namespace moodycamel -#else -// Use standard library of atomics -#include - -namespace moodycamel { - -AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN -{ - switch (order) { - case memory_order_relaxed: break; - case memory_order_acquire: std::atomic_signal_fence(std::memory_order_acquire); break; - case memory_order_release: std::atomic_signal_fence(std::memory_order_release); break; - case memory_order_acq_rel: std::atomic_signal_fence(std::memory_order_acq_rel); break; - case memory_order_seq_cst: std::atomic_signal_fence(std::memory_order_seq_cst); break; - default: assert(false); - } -} - -AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN -{ - switch (order) { - case memory_order_relaxed: break; - case memory_order_acquire: AE_TSAN_ANNOTATE_ACQUIRE(); std::atomic_thread_fence(std::memory_order_acquire); break; - case memory_order_release: AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_release); break; - case memory_order_acq_rel: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_acq_rel); break; - case memory_order_seq_cst: AE_TSAN_ANNOTATE_ACQUIRE(); AE_TSAN_ANNOTATE_RELEASE(); std::atomic_thread_fence(std::memory_order_seq_cst); break; - default: assert(false); - } -} - -} // end namespace moodycamel - -#endif - - -#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) -#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC -#endif - -#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC -#include -#endif -#include - -// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: -// Provides basic support for atomic variables -- no memory ordering guarantees are provided. -// The guarantee of atomicity is only made for types that already have atomic load and store guarantees -// at the hardware level -- on most platforms this generally means aligned pointers and integers (only). -namespace moodycamel { -template -class weak_atomic -{ - public: - AE_NO_TSAN weak_atomic() : value() { } -#ifdef AE_VCPP -#pragma warning(push) -#pragma warning(disable: 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning -#endif - template AE_NO_TSAN weak_atomic(U&& x) : value(std::forward(x)) { } -#ifdef __cplusplus_cli - // Work around bug with universal reference/nullptr combination that only appears when /clr is on - AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) { } -#endif - AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) { } - AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) { } -#ifdef AE_VCPP -#pragma warning(pop) -#endif - - AE_FORCEINLINE operator T() const AE_NO_TSAN { return load(); } - - -#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC - template AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward(x); return *this; } - AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { value = other.value; return *this; } - - AE_FORCEINLINE T load() const AE_NO_TSAN { return value; } - - AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN - { -#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) - if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); -#if defined(_M_AMD64) - else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); -#endif -#else -#error Unsupported platform -#endif - assert(false && "T must be either a 32 or 64 bit type"); - return value; - } - - AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN - { -#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) - if (sizeof(T) == 4) return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); -#if defined(_M_AMD64) - else if (sizeof(T) == 8) return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); -#endif -#else -#error Unsupported platform -#endif - assert(false && "T must be either a 32 or 64 bit type"); - return value; - } -#else - template - AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN - { - value.store(std::forward(x), std::memory_order_relaxed); - return *this; - } - - AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN - { - value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); - return *this; - } - - AE_FORCEINLINE T load() const AE_NO_TSAN { return value.load(std::memory_order_relaxed); } - - AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN - { - return value.fetch_add(increment, std::memory_order_acquire); - } - - AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN - { - return value.fetch_add(increment, std::memory_order_release); - } -#endif - - - private: -#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC - // No std::atomic support, but still need to circumvent compiler optimizations. - // `volatile` will make memory access slow, but is guaranteed to be reliable. - volatile T value; -#else - std::atomic value; -#endif -}; - -} // end namespace moodycamel - - - -// Portable single-producer, single-consumer semaphore below: - -#if defined(_WIN32) -// Avoid including windows.h in a header; we only need a handful of -// items, so we'll redeclare them here (this is relatively safe since -// the API generally has to remain stable between Windows versions). -// I know this is an ugly hack but it still beats polluting the global -// namespace with thousands of generic names or adding a .cpp for nothing. -extern "C" { -struct _SECURITY_ATTRIBUTES; -__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); -__declspec(dllimport) int __stdcall CloseHandle(void* hObject); -__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); -__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); -} -#elif defined(__MACH__) -#include -#elif defined(__unix__) -#include -#elif defined(FREERTOS) -#include -#include -#include -#endif - -namespace moodycamel -{ -// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's -// portable + lightweight semaphore implementations, originally from -// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h -// LICENSE: -// Copyright (c) 2015 Jeff Preshing -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgement in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -namespace spsc_sema -{ -#if defined(_WIN32) -class Semaphore -{ - private: - void* m_hSema; - - Semaphore(const Semaphore& other); - Semaphore& operator=(const Semaphore& other); - - public: - AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() - { - assert(initialCount >= 0); - const long maxLong = 0x7fffffff; - m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); - assert(m_hSema); - } - - AE_NO_TSAN ~Semaphore() - { - CloseHandle(m_hSema); - } - - bool wait() AE_NO_TSAN - { - const unsigned long infinite = 0xffffffff; - return WaitForSingleObject(m_hSema, infinite) == 0; - } - - bool try_wait() AE_NO_TSAN - { - return WaitForSingleObject(m_hSema, 0) == 0; - } - - bool timed_wait(std::uint64_t usecs) AE_NO_TSAN - { - return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; - } - - void signal(int count = 1) AE_NO_TSAN - { - while (!ReleaseSemaphore(m_hSema, count, nullptr)); - } -}; -#elif defined(__MACH__) -//--------------------------------------------------------- -// Semaphore (Apple iOS and OSX) -// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html -//--------------------------------------------------------- -class Semaphore -{ - private: - semaphore_t m_sema; - - Semaphore(const Semaphore& other); - Semaphore& operator=(const Semaphore& other); - - public: - AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() - { - assert(initialCount >= 0); - kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); - assert(rc == KERN_SUCCESS); - AE_UNUSED(rc); - } - - AE_NO_TSAN ~Semaphore() - { - semaphore_destroy(mach_task_self(), m_sema); - } - - bool wait() AE_NO_TSAN - { - return semaphore_wait(m_sema) == KERN_SUCCESS; - } - - bool try_wait() AE_NO_TSAN - { - return timed_wait(0); - } - - bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN - { - mach_timespec_t ts; - ts.tv_sec = static_cast(timeout_usecs / 1000000); - ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); - - // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html - kern_return_t rc = semaphore_timedwait(m_sema, ts); - return rc == KERN_SUCCESS; - } - - void signal() AE_NO_TSAN - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - - void signal(int count) AE_NO_TSAN - { - while (count-- > 0) - { - while (semaphore_signal(m_sema) != KERN_SUCCESS); - } - } -}; -#elif defined(__unix__) -//--------------------------------------------------------- -// Semaphore (POSIX, Linux) -//--------------------------------------------------------- -class Semaphore -{ - private: - sem_t m_sema; - - Semaphore(const Semaphore& other); - Semaphore& operator=(const Semaphore& other); - - public: - AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() - { - assert(initialCount >= 0); - int rc = sem_init(&m_sema, 0, static_cast(initialCount)); - assert(rc == 0); - AE_UNUSED(rc); - } - - AE_NO_TSAN ~Semaphore() - { - sem_destroy(&m_sema); - } - - bool wait() AE_NO_TSAN - { - // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error - int rc; - do - { - rc = sem_wait(&m_sema); - } - while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool try_wait() AE_NO_TSAN - { - int rc; - do { - rc = sem_trywait(&m_sema); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - bool timed_wait(std::uint64_t usecs) AE_NO_TSAN - { - struct timespec ts; - const int usecs_in_1_sec = 1000000; - const int nsecs_in_1_sec = 1000000000; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += static_cast(usecs / usecs_in_1_sec); - ts.tv_nsec += static_cast(usecs % usecs_in_1_sec) * 1000; - // sem_timedwait bombs if you have more than 1e9 in tv_nsec - // so we have to clean things up before passing it in - if (ts.tv_nsec >= nsecs_in_1_sec) { - ts.tv_nsec -= nsecs_in_1_sec; - ++ts.tv_sec; - } - - int rc; - do { - rc = sem_timedwait(&m_sema, &ts); - } while (rc == -1 && errno == EINTR); - return rc == 0; - } - - void signal() AE_NO_TSAN - { - while (sem_post(&m_sema) == -1); - } - - void signal(int count) AE_NO_TSAN - { - while (count-- > 0) - { - while (sem_post(&m_sema) == -1); - } - } -}; -#elif defined(FREERTOS) -//--------------------------------------------------------- -// Semaphore (FreeRTOS) -//--------------------------------------------------------- -class Semaphore -{ - private: - SemaphoreHandle_t m_sema; - - Semaphore(const Semaphore& other); - Semaphore& operator=(const Semaphore& other); - - public: - AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() - { - assert(initialCount >= 0); - m_sema = xSemaphoreCreateCounting(static_cast(~0ull), static_cast(initialCount)); - assert(m_sema); - } - - AE_NO_TSAN ~Semaphore() - { - vSemaphoreDelete(m_sema); - } - - bool wait() AE_NO_TSAN - { - return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; - } - - bool try_wait() AE_NO_TSAN - { - // Note: In an ISR context, if this causes a task to unblock, - // the caller won't know about it - if (xPortIsInsideInterrupt()) - return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; - return xSemaphoreTake(m_sema, 0) == pdTRUE; - } - - bool timed_wait(std::uint64_t usecs) AE_NO_TSAN - { - std::uint64_t msecs = usecs / 1000; - TickType_t ticks = static_cast(msecs / portTICK_PERIOD_MS); - if (ticks == 0) - return try_wait(); - return xSemaphoreTake(m_sema, ticks) == pdTRUE; - } - - void signal() AE_NO_TSAN - { - // Note: In an ISR context, if this causes a task to unblock, - // the caller won't know about it - BaseType_t rc; - if (xPortIsInsideInterrupt()) - rc = xSemaphoreGiveFromISR(m_sema, NULL); - else - rc = xSemaphoreGive(m_sema); - assert(rc == pdTRUE); - AE_UNUSED(rc); - } - - void signal(int count) AE_NO_TSAN - { - while (count-- > 0) - signal(); - } -}; -#else -#error Unsupported platform! (No semaphore wrapper available) -#endif - -//--------------------------------------------------------- -// LightweightSemaphore -//--------------------------------------------------------- -class LightweightSemaphore -{ - public: - typedef std::make_signed::type ssize_t; - - private: - weak_atomic m_count; - Semaphore m_sema; - - bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN - { - ssize_t oldCount; - // Is there a better way to set the initial spin count? - // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, - // as threads start hitting the kernel semaphore. - int spin = 1024; - while (--spin >= 0) - { - if (m_count.load() > 0) - { - m_count.fetch_add_acquire(-1); - return true; - } - compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. - } - oldCount = m_count.fetch_add_acquire(-1); - if (oldCount > 0) - return true; - if (timeout_usecs < 0) - { - if (m_sema.wait()) - return true; - } - if (timeout_usecs > 0 && m_sema.timed_wait(static_cast(timeout_usecs))) - return true; - // At this point, we've timed out waiting for the semaphore, but the - // count is still decremented indicating we may still be waiting on - // it. So we have to re-adjust the count, but only if the semaphore - // wasn't signaled enough times for us too since then. If it was, we - // need to release the semaphore too. - while (true) - { - oldCount = m_count.fetch_add_release(1); - if (oldCount < 0) - return false; // successfully restored things to the way they were - // Oh, the producer thread just signaled the semaphore after all. Try again: - oldCount = m_count.fetch_add_acquire(-1); - if (oldCount > 0 && m_sema.try_wait()) - return true; - } - } - - public: - AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() - { - assert(initialCount >= 0); - } - - bool tryWait() AE_NO_TSAN - { - if (m_count.load() > 0) - { - m_count.fetch_add_acquire(-1); - return true; - } - return false; - } - - bool wait() AE_NO_TSAN - { - return tryWait() || waitWithPartialSpinning(); - } - - bool wait(std::int64_t timeout_usecs) AE_NO_TSAN - { - return tryWait() || waitWithPartialSpinning(timeout_usecs); - } - - void signal(ssize_t count = 1) AE_NO_TSAN - { - assert(count >= 0); - ssize_t oldCount = m_count.fetch_add_release(count); - assert(oldCount >= -1); - if (oldCount < 0) - { - m_sema.signal(1); - } - } - - std::size_t availableApprox() const AE_NO_TSAN - { - ssize_t count = m_count.load(); - return count > 0 ? static_cast(count) : 0; - } -}; -} // end namespace spsc_sema -} // end namespace moodycamel - -#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) -#pragma warning(pop) -#ifdef __cplusplus_cli -#pragma managed(pop) -#endif -#endif \ No newline at end of file diff --git a/app/common/moodycamel/readerwriterqueue/readerwritercircularbuffer.h b/app/common/moodycamel/readerwriterqueue/readerwritercircularbuffer.h deleted file mode 100644 index 6e8763981..000000000 --- a/app/common/moodycamel/readerwriterqueue/readerwritercircularbuffer.h +++ /dev/null @@ -1,288 +0,0 @@ -// ©2020 Cameron Desrochers. -// Distributed under the simplified BSD license (see the license file that -// should have come with this header). - -// Provides a C++11 implementation of a single-producer, single-consumer wait-free concurrent -// circular buffer (fixed-size queue). - -#pragma once - -#include -#include -#include -#include -#include -#include - -// Note that this implementation is fully modern C++11 (not compatible with old MSVC versions) -// but we still include atomicops.h for its LightweightSemaphore implementation. -#include "atomicops.h" - -#ifndef MOODYCAMEL_CACHE_LINE_SIZE -#define MOODYCAMEL_CACHE_LINE_SIZE 64 -#endif - -namespace moodycamel { - -template -class BlockingReaderWriterCircularBuffer -{ - public: - typedef T value_type; - - public: - explicit BlockingReaderWriterCircularBuffer(std::size_t capacity) - : maxcap(capacity), mask(), rawData(), data(), - slots_(new spsc_sema::LightweightSemaphore(static_cast(capacity))), - items(new spsc_sema::LightweightSemaphore(0)), - nextSlot(0), nextItem(0) - { - // Round capacity up to power of two to compute modulo mask. - // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - --capacity; - capacity |= capacity >> 1; - capacity |= capacity >> 2; - capacity |= capacity >> 4; - for (std::size_t i = 1; i < sizeof(std::size_t); i <<= 1) - capacity |= capacity >> (i << 3); - mask = capacity++; - rawData = static_cast(std::malloc(capacity * sizeof(T) + std::alignment_of::value - 1)); - data = align_for(rawData); - } - - BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer&& other) - : maxcap(0), mask(0), rawData(nullptr), data(nullptr), - slots_(new spsc_sema::LightweightSemaphore(0)), - items(new spsc_sema::LightweightSemaphore(0)), - nextSlot(), nextItem() - { - swap(other); - } - - BlockingReaderWriterCircularBuffer(BlockingReaderWriterCircularBuffer const&) = delete; - - // Note: The queue should not be accessed concurrently while it's - // being deleted. It's up to the user to synchronize this. - ~BlockingReaderWriterCircularBuffer() - { - for (std::size_t i = 0, n = items->availableApprox(); i != n; ++i) - reinterpret_cast(data)[(nextItem + i) & mask].~T(); - std::free(rawData); - } - - BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer&& other) noexcept - { - swap(other); - return *this; - } - - BlockingReaderWriterCircularBuffer& operator=(BlockingReaderWriterCircularBuffer const&) = delete; - - // Swaps the contents of this buffer with the contents of another. - // Not thread-safe. - void swap(BlockingReaderWriterCircularBuffer& other) noexcept - { - std::swap(maxcap, other.maxcap); - std::swap(mask, other.mask); - std::swap(rawData, other.rawData); - std::swap(data, other.data); - std::swap(slots_, other.slots_); - std::swap(items, other.items); - std::swap(nextSlot, other.nextSlot); - std::swap(nextItem, other.nextItem); - } - - // Enqueues a single item (by copying it). - // Fails if not enough room to enqueue. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - bool try_enqueue(T const& item) - { - if (!slots_->tryWait()) - return false; - inner_enqueue(item); - return true; - } - - // Enqueues a single item (by moving it, if possible). - // Fails if not enough room to enqueue. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - bool try_enqueue(T&& item) - { - if (!slots_->tryWait()) - return false; - inner_enqueue(std::move(item)); - return true; - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // then enqueues it (via copy). - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - void wait_enqueue(T const& item) - { - while (!slots_->wait()); - inner_enqueue(item); - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // then enqueues it (via move, if possible). - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - void wait_enqueue(T&& item) - { - while (!slots_->wait()); - inner_enqueue(std::move(item)); - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // or the timeout expires. Returns false without enqueueing the item if the timeout - // expires, otherwise enqueues the item (via copy) and returns true. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - bool wait_enqueue_timed(T const& item, std::int64_t timeout_usecs) - { - if (!slots_->wait(timeout_usecs)) - return false; - inner_enqueue(item); - return true; - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // or the timeout expires. Returns false without enqueueing the item if the timeout - // expires, otherwise enqueues the item (via move, if possible) and returns true. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - bool wait_enqueue_timed(T&& item, std::int64_t timeout_usecs) - { - if (!slots_->wait(timeout_usecs)) - return false; - inner_enqueue(std::move(item)); - return true; - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // or the timeout expires. Returns false without enqueueing the item if the timeout - // expires, otherwise enqueues the item (via copy) and returns true. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - template - inline bool wait_enqueue_timed(T const& item, std::chrono::duration const& timeout) - { - return wait_enqueue_timed(item, std::chrono::duration_cast(timeout).count()); - } - - // Blocks the current thread until there's enough space to enqueue the given item, - // or the timeout expires. Returns false without enqueueing the item if the timeout - // expires, otherwise enqueues the item (via move, if possible) and returns true. - // Thread-safe when called by producer thread. - // No exception guarantee (state will be corrupted) if constructor of T throws. - template - inline bool wait_enqueue_timed(T&& item, std::chrono::duration const& timeout) - { - return wait_enqueue_timed(std::move(item), std::chrono::duration_cast(timeout).count()); - } - - // Attempts to dequeue a single item. - // Returns false if the buffer is empty. - // Thread-safe when called by consumer thread. - // No exception guarantee (state will be corrupted) if assignment operator of U throws. - template - bool try_dequeue(U& item) - { - if (!items->tryWait()) - return false; - inner_dequeue(item); - return true; - } - - // Blocks the current thread until there's something to dequeue, then dequeues it. - // Thread-safe when called by consumer thread. - // No exception guarantee (state will be corrupted) if assignment operator of U throws. - template - void wait_dequeue(U& item) - { - while (!items->wait()); - inner_dequeue(item); - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Thread-safe when called by consumer thread. - // No exception guarantee (state will be corrupted) if assignment operator of U throws. - template - bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) - { - if (!items->wait(timeout_usecs)) - return false; - inner_dequeue(item); - return true; - } - - // Blocks the current thread until either there's something to dequeue - // or the timeout expires. Returns false without setting `item` if the - // timeout expires, otherwise assigns to `item` and returns true. - // Thread-safe when called by consumer thread. - // No exception guarantee (state will be corrupted) if assignment operator of U throws. - template - inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) - { - return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); - } - - // Returns a (possibly outdated) snapshot of the total number of elements currently in the buffer. - // Thread-safe. - inline std::size_t size_approx() const - { - return items->availableApprox(); - } - - // Returns the maximum number of elements that this circular buffer can hold at once. - // Thread-safe. - inline std::size_t max_capacity() const - { - return maxcap; - } - - private: - template - void inner_enqueue(U&& item) - { - std::size_t i = nextSlot++; - new (reinterpret_cast(data) + (i & mask)) T(std::forward(item)); - items->signal(); - } - - template - void inner_dequeue(U& item) - { - std::size_t i = nextItem++; - T& element = reinterpret_cast(data)[i & mask]; - item = std::move(element); - element.~T(); - slots_->signal(); - } - - template - static inline char* align_for(char* ptr) - { - const std::size_t alignment = std::alignment_of::value; - return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; - } - - private: - std::size_t maxcap; // actual (non-power-of-two) capacity - std::size_t mask; // circular buffer capacity mask (for cheap modulo) - char* rawData; // raw circular buffer memory - char* data; // circular buffer memory aligned to element alignment - std::unique_ptr slots_; // number of slots currently free (named with underscore to accommodate Qt's 'slots' macro) - std::unique_ptr items; // number of elements currently enqueued - char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr) * 2]; - std::size_t nextSlot; // index of next free slot to enqueue into - char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(std::size_t)]; - std::size_t nextItem; // index of next element to dequeue from -}; - -} \ No newline at end of file diff --git a/app/telemetry/MavlinkTelemetry.cpp b/app/telemetry/MavlinkTelemetry.cpp index 7cb84f5cf..708a6aa62 100644 --- a/app/telemetry/MavlinkTelemetry.cpp +++ b/app/telemetry/MavlinkTelemetry.cpp @@ -11,6 +11,7 @@ #include "action/impl/cmdsender.h" #include "action/fcmsgintervalhandler.h" #include "action/impl/xparam.h" +#include "util/qopenhd.h" MavlinkTelemetry::MavlinkTelemetry(QObject *parent):QObject(parent) { @@ -23,17 +24,22 @@ void MavlinkTelemetry::start() if(mavlink_connection_mode<0 || mavlink_connection_mode>2)mavlink_connection_mode=0; m_connection_mode=mavlink_connection_mode; QString tcp_manual_ip=settings.value("qopenhd_mavlink_connection_manual_tcp_ip","192.168.178.36").toString(); - m_connction_manual_tcp_ip=std::make_shared(tcp_manual_ip.toStdString()); + threadsafe_set_manual_tcp_ip(tcp_manual_ip.toStdString()); auto cb_udp=[this](mavlink_message_t msg){ process_mavlink_message(msg); }; const auto udp_ip="0.0.0.0"; //"127.0.0.1" m_udp_connection=std::make_unique(udp_ip,QOPENHD_GROUND_CLIENT_UDP_PORT_IN,cb_udp); - m_udp_connection->start(); + m_udp_connection->start_looping(); auto cb_tcp=[this](mavlink_message_t msg){ process_mavlink_message(msg); }; - m_tcp_connection=std::make_unique(cb_tcp); + const std::string IP_OPENHD_WIFI_HOTSPOT="192.168.3.1"; + const std::string IP_OPENHD_ETHERNET_HOTSPOT="192.168.2.1"; + const int PORT_OHD_MAVLINK_TCP_SERVER=5760; + m_tcp_connection_wifi_hs=std::make_unique(cb_tcp,IP_OPENHD_WIFI_HOTSPOT,PORT_OHD_MAVLINK_TCP_SERVER,1); + m_tcp_connection_eth_hs=std::make_unique(cb_tcp,IP_OPENHD_ETHERNET_HOTSPOT,PORT_OHD_MAVLINK_TCP_SERVER,2); + m_tcp_connection_custom=std::make_unique(cb_tcp,tcp_manual_ip.toStdString(),PORT_OHD_MAVLINK_TCP_SERVER,3); m_heartbeat_thread_run=true; m_heartbeat_thread=std::make_unique(&MavlinkTelemetry::send_heartbeat_loop,this); } @@ -46,11 +52,13 @@ void MavlinkTelemetry::terminate() m_heartbeat_thread->join(); m_heartbeat_thread=nullptr; } - m_udp_connection=nullptr; - m_tcp_connection=nullptr; // Cleanup those 2 threads CmdSender::instance().terminate(); XParam::instance().terminate(); + m_udp_connection=nullptr; + m_tcp_connection_wifi_hs=nullptr; + m_tcp_connection_eth_hs=nullptr; + m_tcp_connection_custom=nullptr; qDebug()<<"MavlinkTelemetry::stopped"; } @@ -83,10 +91,16 @@ bool MavlinkTelemetry::sendMessage(mavlink_message_t msg){ m_udp_connection->send_message(msg); return true; }else{ - if(m_tcp_connection->threadsafe_is_alive()){ - m_tcp_connection->send_message(msg); - return true; + if(m_tcp_connection_wifi_hs->threadsafe_is_alive()){ + m_tcp_connection_wifi_hs->send_message(msg); + }else if(m_tcp_connection_eth_hs->threadsafe_is_alive()){ + m_tcp_connection_eth_hs->send_message(msg); + }else if(m_tcp_connection_custom->threadsafe_is_alive()){ + m_tcp_connection_custom->send_message(msg); + }else{ + qDebug()<<"No connection currently alive"; } + return true; } return false; } @@ -97,6 +111,10 @@ static int get_message_size(const mavlink_message_t& msg){ void MavlinkTelemetry::process_mavlink_message(const mavlink_message_t& msg) { + // The message might come from udp or tcp endpoint - make sure there are no weird races + // from 2 threads providing telemetry data (which is an edge case, normally, there is only one + // connection feeding us data + std::lock_guard lock(m_udp_or_tcp_mavlink_message_mutex); m_tele_received_packets++; m_tele_received_bytes+=get_message_size(msg); set_telemetry_pps_in(m_tele_pps_in.get_last_or_recalculate(m_tele_received_packets)); @@ -302,8 +320,7 @@ bool MavlinkTelemetry::change_manual_tcp_ip(QString ip) if(!OHDUtil::is_valid_ip(ip.toStdString())){ return false; } - m_connction_manual_tcp_ip=std::make_shared(ip.toStdString()); - return true; + threadsafe_set_manual_tcp_ip(ip.toStdString()); } void MavlinkTelemetry::send_heartbeat_loop() @@ -328,29 +345,45 @@ void MavlinkTelemetry::perform_connection_management() { const int mavlink_connection_mode=m_connection_mode; if(mavlink_connection_mode==0){ - // AUTO - if(m_udp_connection->threadsafe_is_alive()){ - // Stop TCP if it is running, we don't need it - m_tcp_connection->stop_receiving(); - set_telemetry_connection_status("AUTO-CONNECTED(UDP,LOCALHOST)"); + // AUTO + if(is_localhost_only_usage()){ + // If we are running on the same system openhd core (ground) is running on we don't need any tcp + m_tcp_connection_custom->stop_looping_if(); + m_tcp_connection_eth_hs->stop_looping_if(); + m_tcp_connection_wifi_hs->stop_looping_if(); + if(!m_udp_connection->is_looping()){ + m_udp_connection->start_looping(); + } + const bool connected=m_udp_connection->threadsafe_is_alive(); + std::string status_text; + if(connected){ + status_text="AUTO-CONNECTED(UDP,LOCALHOST,LOCAL ONLY)"; + }else{ + status_text="AUTO-DISCONNECTED(LOCAL ONLY) OPENHD CORE RUNNING ?"; + } + set_telemetry_connection_status(status_text.c_str()); }else{ - if(!m_tcp_connection->threadsafe_is_alive()){ - set_telemetry_connection_status("AUTO-CONNECTING"); + // We have to account for all the cases the user might connect this device to his ground station + m_tcp_connection_custom->stop_looping_if(); + if(!m_udp_connection->is_looping()){ + m_udp_connection->start_looping(); } - // UPP is not working, try TCP - const std::string IP_CONSTI_TEST="192.168.178.36"; - const std::string IP_OPENHD_WIFI_HOTSPOT="192.168.3.1"; - //const std::string IP_OPENHD_WIFI_HOTSPOT=IP_CONSTI_TEST; - const std::string IP_OPENHD_ETHERNET_HOTSPOT="192.168.2.1"; - if(m_tcp_connection->m_keep_receiving && m_tcp_connection->threadsafe_is_alive()){ - // Already connected - set_telemetry_connection_status("AUTO-CONNECTED"); + // Prefer UDP if possible + if(m_udp_connection->threadsafe_is_alive()){ + set_telemetry_connection_status("AUTO-CONNECTED(UDP,LOCALHOST)"); + m_tcp_connection_wifi_hs->stop_looping_if(); + m_tcp_connection_eth_hs->stop_looping_if(); }else{ - m_tcp_connection->stop_receiving(); - if(m_tcp_connection->try_connect_and_receive(IP_OPENHD_WIFI_HOTSPOT,QOPENHD_OPENHD_GROUND_TCP_SERVER_PORT)){ - set_telemetry_connection_status("AUTO-CONNECTED (WIFI,TCP)"); - }else if(m_tcp_connection->try_connect_and_receive(IP_OPENHD_ETHERNET_HOTSPOT,QOPENHD_OPENHD_GROUND_TCP_SERVER_PORT)){ - set_telemetry_connection_status("AUTO-CONNECTED (ETH,TCP)"); + if(!m_tcp_connection_wifi_hs->is_looping()){ + m_tcp_connection_wifi_hs->start_looping(); + } + if(!m_tcp_connection_eth_hs->is_looping()){ + m_tcp_connection_eth_hs->start_looping(); + } + if(m_tcp_connection_wifi_hs->threadsafe_is_alive()){ + set_telemetry_connection_status("AUTO-CONNECTED(TCP,WIFI HS)"); + }else if(m_tcp_connection_eth_hs->threadsafe_is_alive()){ + set_telemetry_connection_status("AUTO-CONNECTED(TCP,ETHERNET HS)"); }else{ set_telemetry_connection_status("AUTO-NOT CONNECTED"); } @@ -358,30 +391,61 @@ void MavlinkTelemetry::perform_connection_management() } }else if(mavlink_connection_mode==1){ // Explicit UDP - m_tcp_connection->stop_receiving(); + m_tcp_connection_wifi_hs->stop_looping_if(); + m_tcp_connection_eth_hs->stop_looping_if(); + m_tcp_connection_custom->stop_looping_if(); + if(!m_udp_connection->is_looping()){ + m_udp_connection->start_looping(); + } std::stringstream ss; ss<<"MANUAL UDP-"<<(m_udp_connection->threadsafe_is_alive() ? "ALIVE" : "NO DATA"); set_telemetry_connection_status(ss.str().c_str()); }else if(mavlink_connection_mode==2){ // Explicit TCP - auto tmp=m_connction_manual_tcp_ip; - const std::string user_ip=*tmp; + // Stop all the stuff from auto + m_tcp_connection_wifi_hs->stop_looping_if(); + m_tcp_connection_eth_hs->stop_looping_if(); + m_udp_connection->stop_looping_if(); + const std::string user_ip=threadsafe_get_manual_tcp_ip(); const int user_port=5760; - if(m_tcp_connection->m_remote_ip!=user_ip || m_tcp_connection->m_remote_port!=user_port){ - if(!m_tcp_connection->threadsafe_is_alive()){ - m_tcp_connection->stop_receiving(); - m_tcp_connection->try_connect_and_receive(user_ip,user_port); - } + if(m_tcp_connection_custom->m_remote_ip!=user_ip || m_tcp_connection_custom->m_remote_port!=user_port){ + m_tcp_connection_custom->stop_looping_if(); + m_tcp_connection_custom->set_remote(user_ip,user_port); + } + if(!m_tcp_connection_custom->is_looping()){ + m_tcp_connection_custom->start_looping(); } std::stringstream ss; - ss<<"MANUAL TCP - "; - if(m_tcp_connection->threadsafe_is_alive()){ - ss<<"CONNECTED"; + ss<<"MANUAL TCP -"; + if(m_tcp_connection_custom->threadsafe_is_alive()){ + ss<<"CONNECTED "<<"["< lock(m_connection_manual_tcp_ip_mutex); + m_connction_manual_tcp_ip=ip; +} +std::string MavlinkTelemetry::threadsafe_get_manual_tcp_ip() +{ + std::lock_guard lock(m_connection_manual_tcp_ip_mutex); + return m_connction_manual_tcp_ip; +} + +bool MavlinkTelemetry::is_localhost_only_usage() +{ + if(QOpenHD::instance().is_android() || QOpenHD::instance().is_mac() || QOpenHD::instance().is_windows()){ + // OpenHD core not running on the same device + return false; } + // OpenHD core running on the same device + return true; } diff --git a/app/telemetry/MavlinkTelemetry.h b/app/telemetry/MavlinkTelemetry.h index edef2c436..413c59d57 100644 --- a/app/telemetry/MavlinkTelemetry.h +++ b/app/telemetry/MavlinkTelemetry.h @@ -83,7 +83,10 @@ class MavlinkTelemetry : public QObject void process_message_timesync(const mavlink_message_t &msg); private: std::unique_ptr m_udp_connection=nullptr; - std::unique_ptr m_tcp_connection=nullptr; + std::unique_ptr m_tcp_connection_wifi_hs=nullptr; + std::unique_ptr m_tcp_connection_eth_hs=nullptr; + std::unique_ptr m_tcp_connection_custom=nullptr; + int64_t m_last_timesync_out_us=0; bool m_fc_found=false; int m_fc_sys_id=-1; @@ -96,9 +99,19 @@ class MavlinkTelemetry : public QObject std::unique_ptr m_heartbeat_thread; std::atomic_bool m_heartbeat_thread_run; std::atomic m_connection_mode; - std::shared_ptr m_connction_manual_tcp_ip; void send_heartbeat_loop(); void perform_connection_management(); + // Weird + std::mutex m_udp_or_tcp_mavlink_message_mutex; +private: + // Set and read from 2 different threads. Why is there no std::atomic> :/ + std::mutex m_connection_manual_tcp_ip_mutex; + std::string m_connction_manual_tcp_ip; + void threadsafe_set_manual_tcp_ip(std::string ip); + std::string threadsafe_get_manual_tcp_ip(); + // Returns true if QOpenHD is running on the ground station itself (e.g. on a rpi, rock) + // and always expects data via localhost::udp only. + bool is_localhost_only_usage(); }; #endif // OHDMAVLINKCONNECTION_H diff --git a/app/telemetry/action/impl/xparam.cpp b/app/telemetry/action/impl/xparam.cpp index 6c8f78c55..4d521786a 100644 --- a/app/telemetry/action/impl/xparam.cpp +++ b/app/telemetry/action/impl/xparam.cpp @@ -281,6 +281,14 @@ std::optional XParam::find_remove_running_command running.server_param_set.resize(response.param_count); running.server_param_set[response.param_index]=response; update_progress_get_all(running); + const int missing=get_missing_count(running.server_param_set); + if(missing==0){ + qDebug()<<"No params missing, total:"<0); const int n_missing=get_missing_count(running_cmd.server_param_set); qDebug()<<"Still missing:"<= running_cmd.server_param_set.size()/2){ + if(n_missing>2 && n_missing >= running_cmd.server_param_set.size()/2){ // A lot are stil missing, request them all again send_param_ext_request_list(running_cmd.base_cmd); }else{ diff --git a/app/telemetry/connection/mavlinkchannel.cpp b/app/telemetry/connection/mavlinkchannel.cpp new file mode 100644 index 000000000..a111c0078 --- /dev/null +++ b/app/telemetry/connection/mavlinkchannel.cpp @@ -0,0 +1,25 @@ +#include "mavlinkchannel.h" + +MavlinkChannel::MavlinkChannel() +{ + +} + +MavlinkChannel &MavlinkChannel::instance() +{ + static MavlinkChannel instance; + return instance; +} + +int MavlinkChannel::get_free_channel() +{ + std::lock_guard lock(m_chann_mutex); + auto ret=m_channel_number; + m_channel_number++; + return ret; +} + +void MavlinkChannel::give_back_channel(int channel) +{ + +} diff --git a/app/telemetry/connection/mavlinkchannel.h b/app/telemetry/connection/mavlinkchannel.h new file mode 100644 index 000000000..565a1610f --- /dev/null +++ b/app/telemetry/connection/mavlinkchannel.h @@ -0,0 +1,21 @@ +#ifndef MAVLINKCHANNEL_H +#define MAVLINKCHANNEL_H + +#include + + + +class MavlinkChannel +{ +public: + MavlinkChannel(); + static MavlinkChannel& instance(); + int get_free_channel(); + void give_back_channel(int channel); +private: + std::mutex m_chann_mutex; + int m_channel_number=0; +}; + + +#endif // MAVLINKCHANNEL_H diff --git a/app/telemetry/connection/tcp_connection.cpp b/app/telemetry/connection/tcp_connection.cpp index 68c76c419..fa058d81e 100644 --- a/app/telemetry/connection/tcp_connection.cpp +++ b/app/telemetry/connection/tcp_connection.cpp @@ -15,6 +15,8 @@ #include +#include "mavlinkchannel.h" + static int linux_tcp_socket_try_connect(const std::string remote_ip, const int remote_port,const int timeout_seconds){ //qDebug()<<"linux_tcp_socket_try_connect:"<(&TCPConnection::receive_until_stopped,this); - return true; - } - return false; } -void TCPConnection::stop_receiving() +bool TCPConnection::is_looping() +{ + return m_receive_thread!=nullptr; +} + + +void TCPConnection::start_looping() { - if(m_receive_thread!=nullptr){ - m_keep_receiving=false; + assert(m_receive_thread==nullptr); + m_keep_receiving=true; + m_receive_thread=std::make_unique(&TCPConnection::loop_connect_receive,this); +} + +void TCPConnection::stop_looping_if() +{ + if(is_looping())stop_looping(); +} + +void TCPConnection::stop_looping() +{ + assert(m_receive_thread!=nullptr); + qDebug()<<"TCPConnection2::stop_receiving begin"; + m_keep_receiving=false; #ifdef __windows__ shutdown(m_socket_fd, SD_BOTH); @@ -134,10 +153,13 @@ void TCPConnection::stop_receiving() #endif m_receive_thread->join(); m_receive_thread=nullptr; - } + qDebug()<<"TCPConnection2::stop_receiving end"; } + + + void TCPConnection::send_message(const mavlink_message_t &msg) { uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; @@ -145,8 +167,12 @@ void TCPConnection::send_message(const mavlink_message_t &msg) if(!m_keep_receiving){ return; // Otherwise sendto blocks } + if(m_socket_fd<0){ + //qDebug()<<"Cannot send message"; + return; + } if(!linux_send_message(m_socket_fd,m_remote_ip,m_remote_port,buffer,buffer_len)){ - qDebug()<<"Cannot send message"; + //qDebug()<<"Cannot send message"; } } @@ -160,9 +186,9 @@ bool TCPConnection::threadsafe_is_alive() void TCPConnection::process_data(const uint8_t *data, int data_len) { m_last_data_ms=QOpenHDMavlinkHelper::getTimeMilliseconds(); + mavlink_message_t msg; for (int i = 0; i < data_len; i++) { - mavlink_message_t msg; - uint8_t res = mavlink_parse_char(1,data[i], &msg, &m_recv_status); + uint8_t res = mavlink_parse_char(m_mav_channel,data[i], &msg, &m_recv_status); if (res) { process_mavlink_message(msg); } @@ -178,13 +204,31 @@ void TCPConnection::process_mavlink_message(mavlink_message_t message) void TCPConnection::receive_until_stopped() { // Enough for MTU 1500 bytes. - uint8_t buffer[2048]; + auto buffer=std::make_unique>(); + buffer->resize(1500); + struct timeval tv; + tv.tv_sec = 3; + tv.tv_usec = 0; + //setsockopt(m_socket_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); while (m_keep_receiving) { - const auto recv_len = recv(m_socket_fd, reinterpret_cast(&buffer), sizeof(buffer), 0); + const auto elapsed_last_message=QOpenHDMavlinkHelper::getTimeMilliseconds()-m_last_data_ms; + if(elapsed_last_message>3*1000){ + qDebug()<<"No message for more than 3 seconds, disconnecting"; + return; + } + const auto recv_len = recvfrom( + m_socket_fd, + (char*)buffer->data(), + buffer->size(), + 0, + nullptr, + nullptr); if (recv_len == 0) { // This can happen when shutdown is called on the socket, // therefore we check _should_exit again. + //qDebug()<<"Got recv_len==0"; + //return; continue; } @@ -193,9 +237,32 @@ void TCPConnection::receive_until_stopped() // therefore be quiet. // LogErr() << "recvfrom error: " << GET_ERROR(errno); // Something went wrong, we should try to re-connect in next iteration. + //qDebug()<<"Got recv_len<0 :"<data(),recv_len); } } +void TCPConnection::loop_connect_receive() +{ + qDebug()<<"TCPConnection2::loop_connect_receive begin"; + while(m_keep_receiving){ + m_socket_fd=linux_tcp_socket_try_connect(m_remote_ip,m_remote_port,3); + if(m_socket_fd>0){ + m_last_data_ms=QOpenHDMavlinkHelper::getTimeMilliseconds(); + receive_until_stopped(); + qDebug()<<"Broke out of receive loop"; + close(m_socket_fd); + m_socket_fd=-1; + }else{ + int count=0; + while(m_keep_receiving && count<3){ + std::this_thread::sleep_for(std::chrono::seconds(1)); + count++; + } + } + } + qDebug()<<"TCPConnection2::loop_connect_receive end"; +} diff --git a/app/telemetry/connection/tcp_connection.h b/app/telemetry/connection/tcp_connection.h index 7bd4a3fa3..08432a462 100644 --- a/app/telemetry/connection/tcp_connection.h +++ b/app/telemetry/connection/tcp_connection.h @@ -1,5 +1,5 @@ -#ifndef TCPCONNECTION_H -#define TCPCONNECTION_H +#ifndef TCPCONNECTION2_H +#define TCPCONNECTION2_H #include "../util/mavlink_include.h" @@ -11,20 +11,24 @@ #include #include -class TCPConnection -{ +// TCP client connection that handles the following edge-case: +// If the server becomes unavailable (no message for more than X seconds) +// disconnect, then continously try re-connecting. +class TCPConnection{ public: typedef std::function MAV_MSG_CB; - TCPConnection(MAV_MSG_CB cb); + TCPConnection(MAV_MSG_CB cb,std::string remote_ip,int remote_port,int mavlink_channel); ~TCPConnection(); - // This returns after 3 max 3 seconds. - // On success, true is returned and the receive thread is started (runs untl stop_receiving is called) - // Otherwise, return false; - bool try_connect_and_receive(const std::string remote_ip,const int remote_port); - // If currently receiving, terminate and clean up - // Otherwise, do nothing - void stop_receiving(); + // Update the remote. Must not be in looping state when called. + void set_remote(std::string remote_ip,int remote_port); + + bool is_looping(); + + void start_looping(); + + void stop_looping(); + void stop_looping_if(); void send_message(const mavlink_message_t& msg); @@ -35,7 +39,7 @@ class TCPConnection void receive_until_stopped(); private: MAV_MSG_CB m_cb; - int m_socket_fd=-1; + std::atomic m_socket_fd=-1; mavlink_status_t m_recv_status{}; std::unique_ptr m_receive_thread=nullptr; std::atomic_int32_t m_last_data_ms=0; @@ -43,6 +47,9 @@ class TCPConnection std::string m_remote_ip; int m_remote_port; std::atomic_bool m_keep_receiving=false; + const int m_mav_channel=2; +private: + void loop_connect_receive(); }; -#endif // TCPCONNECTION_H +#endif // TCPCONNECTION2_H diff --git a/app/telemetry/connection/udp_connection.cpp b/app/telemetry/connection/udp_connection.cpp index d134f0566..d6a41d301 100644 --- a/app/telemetry/connection/udp_connection.cpp +++ b/app/telemetry/connection/udp_connection.cpp @@ -13,6 +13,7 @@ #endif #include +#include "mavlinkchannel.h" #ifdef WINDOWS #define GET_ERROR(_x) WSAGetLastError() @@ -32,18 +33,20 @@ UDPConnection::UDPConnection(const std::string local_ip,const int local_port,MAV UDPConnection::~UDPConnection() { - stop(); + stop_looping_if(); } -void UDPConnection::start() +void UDPConnection::start_looping() { + assert(m_receive_thread==nullptr); m_keep_receiving=true; m_receive_thread=std::make_unique(&UDPConnection::loop_receive,this); } -void UDPConnection::stop() +void UDPConnection::stop_looping() { + assert(m_receive_thread!=nullptr); qDebug()<<"UDP stop - begin"; m_keep_receiving=false; #ifdef __windows__ @@ -99,11 +102,21 @@ bool UDPConnection::threadsafe_is_alive(){ return elapsed <= 3*1000; } +bool UDPConnection::is_looping() +{ + return m_receive_thread!=nullptr; +} + +void UDPConnection::stop_looping_if() +{ + if(is_looping())stop_looping(); +} + void UDPConnection::process_data(const uint8_t *data, int data_len) { + mavlink_message_t msg; for (int i = 0; i < data_len; i++) { - mavlink_message_t msg; - uint8_t res = mavlink_parse_char(0, (uint8_t)data[i], &msg, &m_recv_status); + uint8_t res = mavlink_parse_char(m_mav_channel, (uint8_t)data[i], &msg, &m_recv_status); if (res) { process_mavlink_message(msg); } @@ -165,15 +178,16 @@ void UDPConnection::connect_once() const bool success=setup_socket(); if(success){ // Enough for MTU 1500 bytes. - uint8_t buffer[2048]; + auto buffer=std::make_unique>(); + buffer->resize(1500); while (m_keep_receiving) { struct sockaddr_in src_addr = {}; socklen_t src_addr_len = sizeof(src_addr); const auto recv_len = recvfrom( m_socket_fd, - (char*)buffer, - sizeof(buffer), + (char*)buffer->data(), + buffer->size(), 0, reinterpret_cast(&src_addr), &src_addr_len); @@ -195,7 +209,7 @@ void UDPConnection::connect_once() const int remote_port=ntohs(src_addr.sin_port); set_remote(remote_ip,remote_port); m_last_data_ms=QOpenHDMavlinkHelper::getTimeMilliseconds(); - process_data(buffer,recv_len); + process_data(buffer->data(),recv_len); } } // TODO close socket diff --git a/app/telemetry/connection/udp_connection.h b/app/telemetry/connection/udp_connection.h index c08d25a44..2119b8bc1 100644 --- a/app/telemetry/connection/udp_connection.h +++ b/app/telemetry/connection/udp_connection.h @@ -23,15 +23,18 @@ class UDPConnection UDPConnection(const std::string local_ip,const int local_port,MAV_MSG_CB cb); ~UDPConnection(); - void start(); + void start_looping(); - void stop(); + void stop_looping(); void send_message(const mavlink_message_t& msg); // Returns true if the last received message is not older than X seconds bool threadsafe_is_alive(); + bool is_looping(); + void stop_looping_if(); + private: void process_data(const uint8_t* data,int data_len); void process_mavlink_message(mavlink_message_t msg); @@ -61,6 +64,7 @@ class UDPConnection std::mutex m_remote_nutex; std::optional m_curr_remote; std::atomic_int32_t m_last_data_ms=0; + const int m_mav_channel=0; }; #endif // MUDPLINK_H diff --git a/app/telemetry/models/aohdsystem.cpp b/app/telemetry/models/aohdsystem.cpp index 78253a5fe..93d0bc988 100644 --- a/app/telemetry/models/aohdsystem.cpp +++ b/app/telemetry/models/aohdsystem.cpp @@ -20,6 +20,9 @@ #include <../util/WorkaroundMessageBox.h> #include "../settings/wblinksettingshelper.h" +#include "../settings/mavlinksettingsmodel.h" + +#include "openhd_core/platform.hpp" // From https://netbeez.net/blog/what-is-mcs-index/ static std::vector get_dbm_20mhz(){ @@ -59,6 +62,21 @@ static MonitorModeLinkBitfield parse_monitor_link_bitfield(uint8_t bitfield){ return ret; } +static std::string ohd_version_as_string(uint8_t major,uint8_t minor,uint8_t patch,uint8_t release_type){ + std::stringstream ss; + ss<<(int)major<<"."<<(int)minor<<"."<<(int)patch<<"-evo"; + if(release_type==0){ + ss<<"-release"; + }else if(release_type==1){ + ss<<"-beta"; + }else if(release_type==2){ + ss<<"-alpha"; + }else{ + ss<<"-unknown"; + } + return ss.str(); +} + AOHDSystem::AOHDSystem(const bool is_air,QObject *parent) : QObject{parent},m_is_air(is_air) { @@ -85,14 +103,14 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) qDebug()<<"AOHDSystem::process_message: wron sys id"; return false; } + autofech_params_if_apropriate(); m_last_message_ms=QOpenHDMavlinkHelper::getTimeMilliseconds(); bool consumed=false; switch(msg.msgid){ case MAVLINK_MSG_ID_OPENHD_VERSION_MESSAGE:{ mavlink_openhd_version_message_t parsedMsg; mavlink_msg_openhd_version_message_decode(&msg,&parsedMsg); - QString version(parsedMsg.version); - set_openhd_version(version); + set_openhd_version(ohd_version_as_string(parsedMsg.major,parsedMsg.minor,parsedMsg.patch,parsedMsg.release_type).c_str()); consumed=true; }break; case MAVLINK_MSG_ID_ONBOARD_COMPUTER_STATUS:{ @@ -255,6 +273,11 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) return consumed; } +QString AOHDSystem::get_rate_for_mcs_bw(int mcs, int bw) +{ + return "TODO"; +} + void AOHDSystem::process_onboard_computer_status(const mavlink_onboard_computer_status_t &msg) { set_curr_cpuload_perc(msg.cpu_cores[0]); @@ -273,6 +296,10 @@ void AOHDSystem::process_onboard_computer_status(const mavlink_onboard_computer_ set_ram_total(msg.ram_total); int16_t air_reported_sys_id=msg.fan_speed[0]; set_air_reported_fc_sys_id(air_reported_sys_id); + const uint8_t ohd_platform=msg.link_type[0]; + set_ohd_platform(ohd_platform); + const auto platform_as_str=openhd::x_platform_type_to_string(ohd_platform); + set_ohd_platform_type_as_string(platform_as_str.c_str()); } void AOHDSystem::process_x0(const mavlink_openhd_stats_monitor_mode_wifi_card_t &msg){ @@ -394,7 +421,7 @@ void AOHDSystem::process_x3(const mavlink_openhd_stats_wb_video_air_t &msg){ if(delta>0){ const auto elapsed_since_last=std::chrono::steady_clock::now()-m_last_tx_error_hud_message; if(elapsed_since_last>std::chrono::seconds(3)){ - HUDLogMessagesModel::instance().add_message_warning("TX error (dropped packets)"); + //HUDLogMessagesModel::instance().add_message_warning("TX error (dropped packets)"); m_last_tx_error_hud_message=std::chrono::steady_clock::now(); } set_tx_is_currently_dropping_packets(true); @@ -452,6 +479,46 @@ void AOHDSystem::process_op_mode(const mavlink_openhd_wifbroadcast_gnd_operating set_tx_operating_mode(msg.tx_passive_mode_is_enabled); } +void AOHDSystem::autofech_params_if_apropriate() +{ + if(!m_is_air){ + // Ground - auto-fetch IF ;) + if(m_wb_gnd_operating_mode==0){ + // Link is active + if(!MavlinkSettingsModel::is_air_or_cam_param_busy()){ + // None of the air param set(s) are currently busy + if(!MavlinkSettingsModel::instanceGround().has_params_fetched() && !MavlinkSettingsModel::instanceGround().is_x_busy()){ + qDebug()<<"Init autofetch of groun param set"; + // Ground has neither already fetched all params or is busy fetching already + MavlinkSettingsModel::instanceGround().try_refetch_all_parameters_async(true); + } + } + } + }else{ + // AIR - auto fetch IF ;) + if(!MavlinkSettingsModel::instanceGround().is_x_busy()){ + // Ground is currently not fetching params + if(m_curr_rx_last_packet_status_good){ + // air reports a working uplink + if(!MavlinkSettingsModel::is_air_or_cam_param_busy()){ + // None of the (up to 3) air param set(s) are currently fetching params + if(!MavlinkSettingsModel::instanceAir().has_params_fetched()){ + qDebug()<<"Init autofetch of air param set"; + MavlinkSettingsModel::instanceAir().try_refetch_all_parameters_async(); + }else if(!MavlinkSettingsModel::instanceAirCamera().has_params_fetched()){ + qDebug()<<"Init autofetch of air camera1 param set"; + MavlinkSettingsModel::instanceAirCamera().try_refetch_all_parameters_async(); + }else if(!MavlinkSettingsModel::instanceAirCamera2().has_params_fetched()){ + qDebug()<<"Init autofetch of air camera2 param set"; + MavlinkSettingsModel::instanceAirCamera2().try_refetch_all_parameters_async(); + } + } + } + } + } +} + + void AOHDSystem::update_alive() { // NOTE: Since we are really resourcefully with the link, we consider the system alive if any message coming from it has @@ -491,7 +558,20 @@ void AOHDSystem::update_alive_status_with_hud_message(bool alive) }else{ LogMessagesModel::instanceOHDAir().add_message_debug("QOpenHD",message.str().c_str()); } - + if(alive){ + /*if(m_is_air && !MavlinkSettingsModel::instanceAir().has_params_fetched()){ + MavlinkSettingsModel::instanceAir().try_refetch_all_parameters_async(); + } + if(m_is_air && !MavlinkSettingsModel::instanceAirCamera().has_params_fetched()){ + MavlinkSettingsModel::instanceAirCamera().try_refetch_all_parameters_async(); + } + if(m_is_air && !MavlinkSettingsModel::instanceAirCamera2().has_params_fetched()){ + MavlinkSettingsModel::instanceAirCamera2().try_refetch_all_parameters_async(); + } + if(!m_is_air && !MavlinkSettingsModel::instanceGround().has_params_fetched()){ + MavlinkSettingsModel::instanceGround().try_refetch_all_parameters_async(); + }*/ + } set_is_alive(alive); } } diff --git a/app/telemetry/models/aohdsystem.h b/app/telemetry/models/aohdsystem.h index 4ef6a3854..c44764960 100644 --- a/app/telemetry/models/aohdsystem.h +++ b/app/telemetry/models/aohdsystem.h @@ -63,7 +63,8 @@ class AOHDSystem : public QObject L_RO_PROP(int,curr_space_left_mb,set_curr_space_left_mb,0) L_RO_PROP(int,ram_usage_perc,set_ram_usage_perc,0) L_RO_PROP(int,ram_total,set_ram_total,0) - L_RO_PROP(int,ohd_platform_type,set_ohd_platform,0) + L_RO_PROP(int,ohd_platform_type,set_ohd_platform,-1) + L_RO_PROP(QString,ohd_platform_type_as_string,set_ohd_platform_type_as_string,"N/A"); L_RO_PROP(int,ohd_wifi_type,set_ohd_wifi,0) L_RO_PROP(int,ohd_cam_type,set_ohd_cam,0) L_RO_PROP(int,ohd_sys_type,set_ohd_sys_ident,0) @@ -123,7 +124,9 @@ class AOHDSystem : public QObject // L_RO_PROP(int,air_reported_fc_sys_id,set_air_reported_fc_sys_id,-1) // - L_RO_PROP(bool,dirty_air_has_secondary_cam,set_dirty_air_has_secondary_cam,false) + L_RO_PROP(bool,dirty_air_has_secondary_cam,set_dirty_air_has_secondary_cam,false) +public: + Q_INVOKABLE QString get_rate_for_mcs_bw(int mcs,int bw); private: const bool m_is_air; // either true (for air) or false (for ground) uint8_t get_own_sys_id()const{ @@ -142,6 +145,8 @@ class AOHDSystem : public QObject void process_x4b(const mavlink_openhd_stats_wb_video_ground_fec_performance_t& msg); void process_sys_status1(const mavlink_openhd_sys_status1_t& msg); void process_op_mode(const mavlink_openhd_wifbroadcast_gnd_operating_mode_t& msg); + // When apropriate, auto-fecth params until success + void autofech_params_if_apropriate(); private: std::atomic m_last_heartbeat_ms = -1; std::atomic m_last_message_ms= -1; diff --git a/app/telemetry/models/camerastreammodel.cpp b/app/telemetry/models/camerastreammodel.cpp index 405139bef..ebac49ec4 100644 --- a/app/telemetry/models/camerastreammodel.cpp +++ b/app/telemetry/models/camerastreammodel.cpp @@ -4,14 +4,16 @@ #include "../videostreaming/vscommon/QOpenHDVideoHelper.hpp" #include +#include #include #include +#include "openhd_core/camera.hpp" + static std::string video_codec_to_string(int value){ if(value==0)return "h264"; if(value==1)return "h265"; - if(value==2)return "mjpeg"; return "Unknown"; } @@ -36,20 +38,9 @@ CameraStreamModel &CameraStreamModel::instance(int cam_index) QString CameraStreamModel::camera_type_to_string(int camera_type) { - if(camera_type<=0)return "Unknown"; - if(camera_type==1)return "DUMMY"; - if(camera_type==2)return "RPI_CSI_MMAL"; - if(camera_type==3)return "RPI_CSI_VEYE_V4l2"; - if(camera_type==4)return "RPI_CSI_LIBCAMERA"; - if(camera_type==5)return "JETSON_CSI"; - if(camera_type==6)return "ROCKCHIP_CSI"; - if(camera_type==7)return "ALLWINNER_CSI"; - if(camera_type==8)return "UVC (USB)"; - if(camera_type==9)return "UVC_H264"; - if(camera_type==10)return "IP"; - if(camera_type==11)return "ROCK_HDMI"; - if(camera_type==12)return "CUSTOM UNMANAGED"; - return "ERROR"; + if(camera_type<0)return "N/A"; + auto tmp=XCamera{camera_type,0,""}; + return tmp.cam_type_as_verbose_string().c_str(); } QString CameraStreamModel::camera_status_to_string(int camera_status) @@ -78,6 +69,41 @@ QString CameraStreamModel::camera_recording_mode_to_string(int recording_mode) return "error"; } +bool CameraStreamModel::is_valid_resolution_fps_string(QString input) +{ + auto parsed=parse_video_format(input.toStdString()); + if(parsed.has_value())return true; + return false; +} + +QString CameraStreamModel::get_default_resolution() +{ + auto tmp=XCamera{m_camera_type,0,""}; + auto default_res_fps=tmp.get_default_resolution_fps(); + return default_res_fps.as_string().c_str(); +} + +QStringList CameraStreamModel::get_supported_resolutions() +{ + QStringList ret; + auto tmp_cam=XCamera{m_camera_type,0,""}; + auto tmp=tmp_cam.get_supported_resolutions(); + for(auto& element:tmp){ + ret.push_back(element.as_string().c_str()); + } + return ret; +} + +QString CameraStreamModel::make_resolution_fps_verbose(QString input) +{ + auto parsed=parse_video_format(input.toStdString()); + if(parsed.has_value()){ + return get_verbose_string_of_resolution(parsed.value()).c_str(); + } + qDebug()<<"WEIRD RESOLUTION"< 2.0){ total_dist = total_dist + added_distance_m; diff --git a/app/telemetry/models/openhd_core/README.md b/app/telemetry/models/openhd_core/README.md new file mode 100644 index 000000000..5f214c2a7 --- /dev/null +++ b/app/telemetry/models/openhd_core/README.md @@ -0,0 +1 @@ +This file needs to be kept in sync with openh core ! diff --git a/app/telemetry/models/openhd_core/camera.hpp b/app/telemetry/models/openhd_core/camera.hpp new file mode 100644 index 000000000..1a04163a1 --- /dev/null +++ b/app/telemetry/models/openhd_core/camera.hpp @@ -0,0 +1,327 @@ +// +// Created by consti10 on 12.01.24. +// + +#ifndef OPENHD_CAMERA_HPP +#define OPENHD_CAMERA_HPP + +#include +#include +#include +#include +#include + +/** + * NOTE: This file is copied into QOpenHD to populate the UI. + */ + +// For development, always 'works' since fully emulated in SW. +static constexpr int X_CAM_TYPE_DUMMY_SW = 0; // Dummy sw picture +// Manually feed camera data (encoded,rtp) to openhd. Bitrate control and more +// is not working in this mode, making it only valid for development and in +// extreme cases valid for users that want to use a specific ip camera. +static constexpr int X_CAM_TYPE_EXTERNAL = 2; +// For openhd, this is exactly the same as X_CAM_TYPE_EXTERNAL - only file +// start_ip_cam.txt is created Such that the ip cam service can start forwarding +// data to openhd core. +static constexpr int X_CAM_TYPE_EXTERNAL_IP = 3; +// For development, camera that reads input from a file, and then re-encodes it +// using the platform encoder +static constexpr int X_CAM_TYPE_DEVELOPMENT_FILESRC = 4; +// ... reserved for development / custom cameras + +// OpenHD supports any usb camera outputting raw video (with sw encoding). +// H264 usb cameras are not supported, since in general, they do not support +// changing bitrate/ encoding parameters. +static constexpr int X_CAM_TYPE_USB_GENERIC = 10; +static constexpr int X_CAM_TYPE_USB_INFIRAY = 11; +// ... reserved for future (Thermal) USB cameras + +// +// RPI Specific starts here +// +// As of now, we have mmal only for the geekworm hdmi to csi adapter +static constexpr int X_CAM_TYPE_RPI_MMAL_HDMI_TO_CSI = 20; +// ... 9 reserved for future use +// ... +// RPIF stands for RPI Foundation (aka original rpi foundation cameras) +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V1_OV5647 = 30; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V2_IMX219 = 31; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V3_IMX708 = 32; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_RPIF_HQ_IMX477 = 33; +// .... 5 reserved for future use +// Now to all the rpi libcamera arducam cameras +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_SKYMASTERHDR = 40; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_SKYVISIONPRO = 41; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX477M = 42; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX462 = 43; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX327 = 44; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX290 = 45; +static constexpr int X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX462_LOWLIGHT_MINI = 46; +// ... 13 reserved for future use +static constexpr int X_CAM_TYPE_RPI_V4L2_VEYE_2MP = 60; +static constexpr int X_CAM_TYPE_RPI_V4L2_VEYE_CSIMX307 = 61; +static constexpr int X_CAM_TYPE_RPI_V4L2_VEYE_CSSC132 = 62; +static constexpr int X_CAM_TYPE_RPI_V4L2_VEYE_MVCAM = 63; +// ... 6 reserved for future use +// +// X20 Specific starts here +// +// Right now we only have one camera, but more (might) follow. +static constexpr int X_CAM_TYPE_X20_RUNCAM_NANO = 70; +// ... 9 reserved for future use +// +// ROCK Specific starts here +// +static constexpr int X_CAM_TYPE_ROCK_HDMI_IN = 80; +static constexpr int X_CAM_TYPE_ROCK_IMX219 = 81; +// +// OpenIPC specific starts here +static constexpr int X_CAM_TYPE_OPENIPC_SOMETHING=90; +// + +// ... rest is reserved for future use +// no camera, only exists to have a default value for secondary camera (which is +// disabled by default). NOTE: The primary camera cannot be disabled ! +static constexpr int X_CAM_TYPE_DISABLED = 255; // Max for uint8_t + +static std::string x_cam_type_to_string(int camera_type) { + switch (camera_type) { + case X_CAM_TYPE_DUMMY_SW: + return "DUMMY"; + case X_CAM_TYPE_EXTERNAL: + return "EXTERNAL"; + case X_CAM_TYPE_EXTERNAL_IP: + return "EXTERNAL_IP"; + case X_CAM_TYPE_DEVELOPMENT_FILESRC: + return "DEV_FILESRC"; + case X_CAM_TYPE_USB_GENERIC: + return "USB"; + case X_CAM_TYPE_USB_INFIRAY: + return "INFIRAY"; + // All the rpi stuff begin + case X_CAM_TYPE_RPI_MMAL_HDMI_TO_CSI: + return "MMAL_HDMI"; + case X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V1_OV5647: + return "RPIF_V1_OV5647"; + case X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V2_IMX219: + return "RPIF_V2_IMX219"; + case X_CAM_TYPE_RPI_LIBCAMERA_RPIF_V3_IMX708: + return "RPIF_V3_IMX708"; + case X_CAM_TYPE_RPI_LIBCAMERA_RPIF_HQ_IMX477: + return "RPIF_HQ_IMX477"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_SKYMASTERHDR: + return "ARDUCAM_SKYMASTERHDR"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_SKYVISIONPRO: + return "ARDUCAM_SKYVISIONPRO"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX477M: + return "ARDUCAM_IMX477M"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX462: + return "ARDUCAM_IMX462"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX327: + return "ARDUCAM_IMX327"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX290: + return "ARDUCAM_IMX290"; + case X_CAM_TYPE_RPI_LIBCAMERA_ARDUCAM_IMX462_LOWLIGHT_MINI: + return "ARDUCAM_IMX462_LOWLIGHT_MINI"; + case X_CAM_TYPE_RPI_V4L2_VEYE_2MP: + return "VEYE_2MP"; + case X_CAM_TYPE_RPI_V4L2_VEYE_CSIMX307: + return "VEYE_IMX307"; + case X_CAM_TYPE_RPI_V4L2_VEYE_CSSC132: + return "VEYE_CSSC132"; + case X_CAM_TYPE_RPI_V4L2_VEYE_MVCAM: + return "VEYE_MVCAM"; + // All the x20 begin + case X_CAM_TYPE_X20_RUNCAM_NANO: + return "X20_RUNCAM_NANO"; + // All the rock begin + case X_CAM_TYPE_ROCK_HDMI_IN: + return "ROCK_HDMI_IN"; + case X_CAM_TYPE_ROCK_IMX219: + return "ROCK_IMX219"; + case X_CAM_TYPE_DISABLED: + return "DISABLED"; + case X_CAM_TYPE_OPENIPC_SOMETHING: + return "OPENIPC_X"; + default: + break; + } + std::stringstream ss; + ss << "UNKNOWN (" << camera_type << ")"; + return ss.str(); +}; + +struct ResolutionFramerate { + int width_px; + int height_px; + int fps; + std::string as_string()const{ + std::stringstream ss; + ss<= 30 && camera_type < 60; + } + bool requires_rpi_veye_pipeline() const { + return camera_type >= 60 && camera_type < 70; + } + bool requires_x20_cedar_pipeline() const { + return camera_type >= 70 && camera_type < 80; + } + bool requires_rockchip_mpp_pipeline() const { + return camera_type >= 80 && camera_type < 90; + } + std::string cam_type_as_verbose_string() const { + return x_cam_type_to_string(camera_type); + } + bool is_camera_type_usb_infiray() const { + return camera_type == X_CAM_TYPE_USB_INFIRAY; + }; + // Returns a list of known supported resolution(s). + // They should be ordered in ascending resolution / framerate + // Must always return at least one resolution + // Might not return all resolutions a camera supports per HW + // (In qopenhd, we have the experiment checkbox, where the user can enter + // anything he likes) + std::vector get_supported_resolutions() const { + if (requires_rpi_veye_pipeline()) { + // Veye camera(s) only do 1080p30 + return {ResolutionFramerate{1920, 1080, 30}}; + } else if (requires_x20_cedar_pipeline()) { + // also easy, 720p60 only (for now) + return {ResolutionFramerate{1280, 720, 60}}; + } else if (camera_type == X_CAM_TYPE_USB_INFIRAY) { + return {ResolutionFramerate{384,292,25}}; + } else if(camera_type==X_CAM_TYPE_USB_GENERIC){ + // Return what's most likely going to work + return {ResolutionFramerate{640, 480, 30}}; + } else if (requires_rpi_libcamera_pipeline()) { + std::vector ret; + ret.push_back(ResolutionFramerate{640, 480, 60}); + ret.push_back(ResolutionFramerate{1280, 720, 60}); + ret.push_back(ResolutionFramerate{1920, 1080, 30}); + return ret; + } else if (camera_type == X_CAM_TYPE_RPI_MMAL_HDMI_TO_CSI) { + std::vector ret; + // 720p60 is the most commonly used / works in a lot of scenarios + ret.push_back(ResolutionFramerate{1280, 720, 30}); + ret.push_back(ResolutionFramerate{1920, 1080, 25}); + ret.push_back(ResolutionFramerate{1280, 720, 60}); + return ret; + }else if(camera_type==X_CAM_TYPE_DUMMY_SW){ + std::vector ret; + ret.push_back(ResolutionFramerate{640,480,30}); + ret.push_back(ResolutionFramerate{1280,720,30}); + ret.push_back(ResolutionFramerate{1280,720,60}); + return ret; + }else if(camera_type==X_CAM_TYPE_DEVELOPMENT_FILESRC){ + std::vector ret; + ret.push_back(ResolutionFramerate{848,480,60}); + ret.push_back(ResolutionFramerate{1280,720,60}); + ret.push_back(ResolutionFramerate{1920,1080,60}); + return ret; + } + // Not mapped yet + // return something that might work or might not work + return {ResolutionFramerate{640, 480, 30}}; + } + // We default to the last supported resolution + [[nodiscard]] ResolutionFramerate get_default_resolution_fps() const { + auto supported_resolutions=get_supported_resolutions(); + return supported_resolutions.at(supported_resolutions.size()-1); + } +}; + +static bool is_valid_primary_cam_type(int cam_type) { + if (cam_type >= 0 && cam_type < X_CAM_TYPE_DISABLED) return true; + return false; +} +static bool is_valid_secondary_cam_type(int cam_type) { + if (cam_type == X_CAM_TYPE_DUMMY_SW || + cam_type == X_CAM_TYPE_USB_INFIRAY || + cam_type==X_CAM_TYPE_USB_GENERIC || + cam_type == X_CAM_TYPE_EXTERNAL || + cam_type == X_CAM_TYPE_EXTERNAL_IP || + cam_type == X_CAM_TYPE_DISABLED) { + return true; + } + return false; +} + +static bool is_rpi_csi_camera(int cam_type) { + return cam_type >= 10 && cam_type <= 59; +} + +static bool supports_rotation(int cam_type){ + return is_rpi_csi_camera(cam_type); +} + +static bool is_usb_camera(int cam_type){ + return cam_type>=10 && cam_type<19; +} + +// Takes a string in the from {width}x{height}@{framerate} +// e.g. 1280x720@30 +static std::optional parse_video_format(const std::string& videoFormat){ + if (videoFormat.size() <= 5) { + return std::nullopt; + } + ResolutionFramerate tmp_video_format{0, 0, 0}; + const std::regex reg{R"((\d*)x(\d*)\@(\d*))"}; + std::smatch result; + if (std::regex_search(videoFormat, result, reg)) { + if (result.size() == 4) { + // openhd::log::get_default()->debug("result[0]=["+result[0].str()+"]"); + tmp_video_format.width_px = atoi(result[1].str().c_str()); + tmp_video_format.height_px = atoi(result[2].str().c_str()); + tmp_video_format.fps = atoi(result[3].str().c_str()); + return tmp_video_format; + } + } + return std::nullopt; +} + +// +// Used in QOpenHD UI +// +static std::string get_verbose_string_of_resolution(const ResolutionFramerate& resolution_framerate){ + if(resolution_framerate.width_px==0 && resolution_framerate.height_px==0 && resolution_framerate.fps==0){ + return "AUTO"; + } + std::stringstream ss; + if(resolution_framerate.width_px==640 && resolution_framerate.height_px==480){ + ss<<"VGA 4:3"; + }else if(resolution_framerate.width_px==848 && resolution_framerate.height_px==480){ + ss<<"VGA 16:9"; + }else if(resolution_framerate.width_px==896 && resolution_framerate.height_px== 504){ + ss<<"SD 16:9"; + }else if(resolution_framerate.width_px==1280 && resolution_framerate.height_px==720){ + ss<<"HD 16:9"; + }else if(resolution_framerate.width_px==1920 && resolution_framerate.height_px==1080){ + ss<<"FHD 16:9"; + }else if(resolution_framerate.width_px==2560 && resolution_framerate.height_px==1440){ + ss<<"2K 16:9"; + }else{ + ss< + +namespace openhd{ + +// When this one shows up a bit more work has to be done to run openhd on the platform (probably) ;) +static constexpr int X_PLATFORM_TYPE_UNKNOWN=0; +// Generic X86 +static constexpr int X_PLATFORM_TYPE_X86=1; +// Numbers 10..20 are reserved for rpi +// Right now we are only interested if it is an RPI of the +// generation RPI 4 / RPI CM4 or the generation before - +// NOTE: RPI 5 is currently not supported due to the complete lack of suitable HW acceleration +static constexpr int X_PLATFORM_TYPE_RPI_OLD=10; +static constexpr int X_PLATFORM_TYPE_RPI_4=11; +static constexpr int X_PLATFORM_TYPE_RPI_CM4=12; +static constexpr int X_PLATFORM_TYPE_RPI_5=12; + +// Numbers 20..30 are reserved for rockchip +static constexpr int X_PLATFORM_TYPE_ROCKCHIP_RK3566_RADXA_ZERO3W=20; // Zero 3 W +static constexpr int X_PLATFORM_TYPE_ROCKCHIP_RK3588_RADXA_ROCK5=21; // ROCK 5 +static constexpr int X_PLATFORM_TYPE_ROCKCHIP_RV1126_UNDEFINED=22; // FUTRE + +// Numbers 30..35 are reserved for allwinner +static constexpr int X_PLATFORM_TYPE_ALWINNER_X20=30; + +// @Buldo is working on openipc / sigmastar, 36..40 +static constexpr int X_PLATFORM_TYPE_SIGMASTAR_UNDEFINED=36; + +std::string x_platform_type_to_string(int platform_type) { + switch (platform_type) { + case X_PLATFORM_TYPE_UNKNOWN:return "UNKNOWN"; + case X_PLATFORM_TYPE_X86:return "X86"; + case X_PLATFORM_TYPE_RPI_OLD: return "RPI<=3"; + case X_PLATFORM_TYPE_RPI_4: return "RPI 4"; + case X_PLATFORM_TYPE_RPI_5: return "RPI 5"; + // RPI END + case X_PLATFORM_TYPE_ROCKCHIP_RK3566_RADXA_ZERO3W: return "RADXA ZERO3W"; + case X_PLATFORM_TYPE_ROCKCHIP_RK3588_RADXA_ROCK5: return "RADXA ROCK5"; + case X_PLATFORM_TYPE_ROCKCHIP_RV1126_UNDEFINED: return "RV1126 UNDEFINED"; + // ROCK END + case X_PLATFORM_TYPE_ALWINNER_X20: return "X20"; + case X_PLATFORM_TYPE_SIGMASTAR_UNDEFINED: return "SIGMASTAR UNDEFINED"; + default: + break; + } + return "ERR-UNDEFINED"; +} +} + + + +#endif // PLATFORM_HPP diff --git a/app/telemetry/models/wificard.cpp b/app/telemetry/models/wificard.cpp index 5f942ba22..618676c74 100644 --- a/app/telemetry/models/wificard.cpp +++ b/app/telemetry/models/wificard.cpp @@ -27,6 +27,9 @@ static std::string wifi_card_type_to_string(const int card_type) { case 8: return "BROADCOM"; case 9: + return "8852BU"; + case 10: + return "EMULATED"; default: return "UNKNOWN"; } diff --git a/app/telemetry/settings/documentedparam.cpp b/app/telemetry/settings/documentedparam.cpp index 3647d3ae7..2f7b9aa3d 100644 --- a/app/telemetry/settings/documentedparam.cpp +++ b/app/telemetry/settings/documentedparam.cpp @@ -196,7 +196,20 @@ static std::vector> get_parameters_list(){ "(similar to how flight modes work)." ); append_only_documented(ret,openhd::WB_FREQUENCY,"!!!Editing this param manually without care will result in a broken link!!!"); - append_only_documented(ret,openhd::WB_MAX_FEC_BLOCK_SIZE_FOR_PLATFORM,"Developer only, FEC auto internal."); + { + auto default_values=std::vector{ + {"AUTO -1",-1}, + {"LOW 20",20}, + {"30",30}, + {"40",40}, + {"50",50}, + {"100",100}, + {"128",128}, + }; + append_int(ret,openhd::WB_MAX_FEC_BLOCK_SIZE_FOR_PLATFORM, + ImprovedIntSetting(-1,128,default_values), + "Developer only, max FEC block size."); + } const auto descr_wifi_card="Detected wifi card type used for wifibroadcast."; append_documented_read_only(ret,"WIFI_CARD0",descr_wifi_card); append_documented_read_only(ret,"WIFI_CARD1",descr_wifi_card); @@ -204,6 +217,8 @@ static std::vector> get_parameters_list(){ append_documented_read_only(ret,"WIFI_CARD3",descr_wifi_card); append_documented_read_only(ret,"HOTSPOT_CARD","Detected card for wifi hotspot"); append_documented_read_only(ret,"WB_N_RX_CARDS","TODO"); + append_only_documented(ret,"FC_BATT_N_CELLS","Stored locally on your air unit. If set to a nonzero value, overwrites the batt n cells of any connected GS (QOpenHD)" + "such that you don't have to change the value when swapping around planes."); // ------------------------------------------------------------------------------------------------------------------------------------------------------- // video / camera parameters // ------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -221,49 +236,9 @@ static std::vector> get_parameters_list(){ //"1440x1080@30", "1920x1080@30", }; - append_string(ret,"V_FORMAT",ImprovedStringSetting::create_from_keys_only(choices_video_res_framerate), + append_string(ret,"RESOLUTION_FPS",ImprovedStringSetting::create_from_keys_only(choices_video_res_framerate), "Video WIDTHxHEIGHT@FPS. You can enter any value you want here, but if you select a video format that is not supported by your camera, the video stream will stop"); } - { - // Needs to match OpenHD - // MMAL = 0, // raspivid / gst-rpicamsrc - // IMX462 Low Light Mini, // "normal" libcamera, explicitly set to Arducam_piviarity detection only NEEDS CUSTOM LIBCAMERA - // SkyMaster HDR, // "normal" libcamera, explicitly set to imx708 detection only - // SkyVision Pro, // "normal" libcamera, explicitly set to imx519 detection only NEEDS CUSTOM LIBCAMERA - // LIBCAMERA_IMX477M, // "normal" libcamera, explicitly set to imx477 detection only NEEDS CUSTOM TUNING FILE - // LIBCAMERA_IMX477, // "normal" libcamera, explicitly set to imx477 detection only - // LIBCAMERA_IMX462, // "normal" libcamera, explicitly set to imx462 detection only - // LIBCAMERA_IMX327, // "normal" libcamera, explicitly set to imx327 detection only - // LIBCAMERA_IMX290, // "normal" libcamera, explicitly set to imx290 detection only - // LIBCAMERA_AUTO, // standart libcamera with autodetect - // LIBCAMERA_ARDUCAM_AUTO, // pivariety libcamera (arducam special)NEEDS CUSTOM LIBCAMERA - // VEYE_2MP_CAMERAS // Veye IMX327 (never versions), VEYE series with 200W resolution - // VEYE_CSIMX307, // Veye IMX307 - // VEYE_CSSC132, //Veye SC132 - // VEYE_MVCAM, // Veye MV Cameras - auto cam_config_items=std::vector{ - "Legacy(MMAL)", - "IMX462 Low Light Mini", - "SkyMaster HDR (708)", - "SkyVision Pro (519)", - "LIBCAMERA_IMX477M", - "LIBCAMERA_IMX477", - "LIBCAMERA_IMX462", - "LIBCAMERA_IMX327", - "LIBCAMERA_IMX290", - "LIBCAMERA_AUTO", - "LIBCAMERA_ARDUCAM_AUTO", - "VEYE_2MP_CAMERAS", - "VEYE_CSIMX307", - "VEYE_CSSC132", - "VEYE_MVCAM" - }; - append_int(ret,"V_OS_CAM_CONFIG", - ImprovedIntSetting::createEnum(cam_config_items), - "If your connected CSI camera is not detected (e.g. you see a dummy camera stream) you need to select the apropriate config here. " - "Air will automatically reboot when you change this parameter" - ); - } append_int(ret,"VIDEO_CODEC", //NOTE: MJPEG has been removed intentionally, since we are going to eventually remove support for it in //favour of h264 @@ -272,12 +247,12 @@ static std::vector> get_parameters_list(){ "Video codec. If your camera/ground station does not support HW accelerated encoding/decoding of the selected codec,it'l default to SW encode/decode. " "A reboot (air&ground) is recommended after changing this parameter." ); - append_int(ret,"V_AIR_RECORDING", + append_int(ret,"AIR_RECORDING_E", ImprovedIntSetting::createEnum( std::vector{"DISABLE","ENABLE","AUTO(armed)"}), "Record video data locally on your air unit. You can find the files under /home/openhd/Videos on the SD card and/or download them via the web ui." "When AUTO is set, air recording automatically starts (and stops) when you arm/disarm your drone (requires inav / ardupilot FC)." ); - append_int(ret,"V_E_STREAMING", + append_int(ret,"STREAMING_E", ImprovedIntSetting::createEnumEnableDisable(), "Enable / disable streaming for this camera. Note that this setting is persistent at run time - once you disable streaming for a camera, you won't have video" " until you re-enable streaming or reboot your air unit. On by default" @@ -292,14 +267,12 @@ static std::vector> get_parameters_list(){ {"14MBit/s (high)",14}, {"18MBit/s (high)",18}, }; - append_int(ret,"V_BITRATE_MBITS", + append_int(ret,"BITRATE_MBITS", ImprovedIntSetting(1,100,default_values), "Camera encoder bitrate, does not include FEC overhead. " "!! If variable bitrate is enabled (recommended), this value is ignored.!! Otherwise, you can manually set a fixed camera/encoder bitrate here. " "NOTE: If you are using a camera not listed on the OpenHD recommended cameras list, the bitrate might be fixed by the vendor and not changeable." ); - append_only_documented(ret,"V_MJPEG_QUALITY", - "Active if video codec== mjpeg. MJPEG has no encoder bitrate, only an arbitratry quality parameter (0..100)"); } { auto default_values=std::vector{ @@ -311,12 +284,12 @@ static std::vector> get_parameters_list(){ {"15 (medium recovery)",15}, {"20 (bad recovery)",20}, }; - append_int(ret,"V_KEYFRAME_I", + append_int(ret,"KEYFRAME_I", ImprovedIntSetting(0,100,default_values), "Keyframe / instantaneous decode refresh interval, in frames. E.g. if set to 15, every 15th frame will be a key frame. Higher values result in better image compression, but increase the likeliness of microfreezes." ); } - append_int(ret,"V_FORCE_SW_ENC", + append_int(ret,"FORCE_SW_ENC", ImprovedIntSetting::createEnumEnableDisable(), "Force SW encode for the given camera, only enable if your camera supports outputting an appropriate raw format." ); @@ -326,24 +299,27 @@ static std::vector> get_parameters_list(){ append_only_documented(ret,"V_PRIMARY_PERC", "If Variable bitrate is enabled,your primary camera is given that much percentage of the total available link bandwidth. " "The rest is given to the secondary camera. Default to 60% (60:40 split)."); - append_int(ret,"V_HORIZ_FLIP", - ImprovedIntSetting::createEnumEnableDisable(), - "Flip video horizontally" - ); - append_int(ret,"V_VERT_FLIP", - ImprovedIntSetting::createEnumEnableDisable(), - "Flip video vertically" + + append_int(ret,"ROTATION_FLIP", + ImprovedIntSetting(-1,2130706433,{ + ImprovedIntSetting::Item{"NONE",0}, + ImprovedIntSetting::Item{"VFLIP°",1}, + ImprovedIntSetting::Item{"HFLIP",2}, + ImprovedIntSetting::Item{"BOTH",3} + }), + "Flip video vertically / horizontally (ROTATE)" ); - append_int(ret,"V_CAM_ROT_DEG", + append_int(ret,"ROTATION_DEG", ImprovedIntSetting(0,270,{ ImprovedIntSetting::Item{"0°(disable)",0}, - ImprovedIntSetting::Item{"90 (mmal only)°",90}, + //ImprovedIntSetting::Item{"90 (mmal only)°",90}, ImprovedIntSetting::Item{"180°",180}, - ImprovedIntSetting::Item{"270°(mmal only)",270}}), - "Rotate video by 90 degree increments" + //ImprovedIntSetting::Item{"270°(mmal only)",270} + }), + "Rotate video" ); - append_int(ret,"V_INTRA_REFRESH", + append_int(ret,"INTRA_REFRESH", ImprovedIntSetting(-1,2130706433,{ ImprovedIntSetting::Item{"NONE",-1}, ImprovedIntSetting::Item{"CYCLIC",0}, @@ -353,14 +329,8 @@ static std::vector> get_parameters_list(){ }), "Experimental,Default NONE, Type of Intra Refresh to use" ); - append_int(ret,"V_N_CAMERAS", - ImprovedIntSetting(1,2,{ImprovedIntSetting::Item{"SINGLE (default)",1},ImprovedIntSetting::Item{"DUALCAM",2}}), - "Configure openhd for single / dualcam usage. The air unit will wait for a specific amount of time until it has found that many camera(s)," - " if it cannot find enough camera(s) it creates as many dummy camera(s) as needec instead.", - true - ); - append_only_documented(ret,"V_BRIGHTNESS","Image capture brightness, [0..100], default 50. Increase for a brighter Image. However, if available, it is recommended to tune AWB or EXP instead."); - append_only_documented(ret,"V_ISO","ISO value to use (0 = Auto)"); + append_only_documented(ret,"BRIGHTNESS","Image capture brightness, [0..200], default 100. Increase for a brighter Image. However, if available, it is recommended to tune AWB or EXP instead."); + append_only_documented(ret,"ISO","ISO value to use (0 = Auto)"); { // rpicamsrc only for now @@ -394,15 +364,15 @@ static std::vector> get_parameters_list(){ auto values_metering_mode=std::vector{ "AVERAGE","SPOT","BACKLIST","MATRIX" }; - append_int(ret,"V_AWB_MODE", + append_int(ret,"AWB_MODE", ImprovedIntSetting::createEnum(gst_awb_modes), "AWB Automatic white balance mode" ); - append_int(ret,"V_EXP_MODE", + append_int(ret,"EXP_MODE", ImprovedIntSetting::createEnum(gst_exposure_modes), "EXP Exposure mode" ); - append_int(ret,"V_METERING_MODE", + append_int(ret,"METERING_MODE", ImprovedIntSetting::createEnum(values_metering_mode), "Camera exposure metering mode to use. Default average." ); @@ -494,9 +464,6 @@ static std::vector> get_parameters_list(){ " - even with encryption disabled, it is not easy for an attacker to listen in on your openhd video " "(and impossible to attack your video due to always on secure packet validation)." ); - append_documented_read_only(ret,"V_CAM_TYPE","Detected camera type"); - append_documented_read_only(ret,"V_CAM_SENSOR","Detected camera sensor (might not work)"); - append_documented_read_only(ret,"V_CAM_NAME","Detected camera name (might not work)"); } // ------------------------------------------------------------------------------------------------------------------------------------------------------- // Other stuff @@ -583,6 +550,27 @@ static std::vector> get_parameters_list(){ "Change which joystick 'channel' is taken for each RC channel. This is a list of numbers, where each number X at position N means take joystick input nr X for channel N." " For example, 1,4,... means take channel number 1 for the first channel, and channel number 4 for the second channel. Needs to have ! all! 18 channel elements seperated by a ','"); } + { + auto infiray_colorpalete_items=std::vector{ + {"CMD Shutter calibration",32768}, + {"CMD YUYV output",32773}, + {"CMD save configurations",33022}, + // Color palete + {"White Hot",34816}, + {"Black Hot",34817}, + {"Iron Rainbow",34818}, + {"Lava",34819}, + {"Rainbow2",34820}, + {"Rainbow3",34821}, + {"Red Hot",34822}, + {"Iron gray",34823}, + {"HCR1",34824}, + {"HCR2",34825}, + {"Black hot 2",34826}, + }; + append_int(ret,"COLOR_PALETE",ImprovedIntSetting(34816,34826,infiray_colorpalete_items), + "Infiray thermal colors"); + } append_only_documented(ret,"RC_UPDATE_HZ", "Specify the update rate of RC over wifibroadcast. A higher update rate gives lower RC latency, but takes more bandwidth away from the downlink." "No effect if joy rc is disabled."); @@ -720,6 +708,32 @@ static std::map get_whitelisted_params() ret[openhd::WB_ENABLE_STBC]=nullptr; // Whitelisted since normally it should not be changed / has no effect anyways ret[openhd::WB_ENABLE_SHORT_GUARD]=nullptr; + // + ret["WB_V_FEC_PERC"]=nullptr; + ret["WB_V_RATE_PERC"]=nullptr; + ret["VARIABLE_BITRATE"]=nullptr; + // + ret["TYPE_CAM0"]=nullptr; + ret["TYPE_CAM1"]=nullptr; + // The actual 'camera' parameters + // Whitelisted since r.n we don't really know + // what happens on a platform if h265 is selected + ret["VIDEO_CODEC"]=nullptr; + ret["AIR_RECORDING"]=nullptr; + ret["FORCE_SW_ENC"]=nullptr; + ret["BITRATE_MBITS"]=nullptr; + ret["KEYFRAME_I"]=nullptr; + ret["INTRA_REFRESH"]=nullptr; + ret["N_SLICES"]=nullptr; + // + + ret["STREAMING_E"]=nullptr; + + + ret[""]=nullptr; + ret[""]=nullptr; + ret[""]=nullptr; + ret[""]=nullptr; //ret[""]=nullptr; return ret; } diff --git a/app/telemetry/settings/mavlinksettingsmodel.cpp b/app/telemetry/settings/mavlinksettingsmodel.cpp index ba62a07d3..3d9f032ac 100644 --- a/app/telemetry/settings/mavlinksettingsmodel.cpp +++ b/app/telemetry/settings/mavlinksettingsmodel.cpp @@ -11,6 +11,10 @@ #include #include "../action/impl/xparam.h" +// Dirty +#include "../models/openhd_core/camera.hpp" +#include "../models/aohdsystem.h" +#include "../util/WorkaroundMessageBox.h" MavlinkSettingsModel &MavlinkSettingsModel::instanceAirCamera() { @@ -35,11 +39,14 @@ MavlinkSettingsModel &MavlinkSettingsModel::instanceGround() return *instanceGround; } -/*MavlinkSettingsModel &MavlinkSettingsModel::instanceFC() +bool MavlinkSettingsModel::system_is_alive() { - static MavlinkSettingsModel* instanceFc=new MavlinkSettingsModel(1,1); - return *instanceFc; -}*/ + if(m_sys_id==OHD_SYS_ID_AIR){ + return AOHDSystem::instanceAir().is_alive(); + } + return AOHDSystem::instanceGround().is_alive(); +} + bool MavlinkSettingsModel::is_param_whitelisted(const std::string param_id)const { @@ -67,6 +74,22 @@ MavlinkSettingsModel::MavlinkSettingsModel(uint8_t sys_id,uint8_t comp_id,QObjec //m_data.push_back({"VIDEO_HEIGHT",1}); //m_data.push_back({"VIDEO_FPS",1}); connect(this, &MavlinkSettingsModel::signal_ui_thread_replace_param_set, this, &MavlinkSettingsModel::ui_thread_replace_param_set); + // + //connect(this, &MavlinkSettingsModel::signalCallResultCb, this, &MavlinkSettingsModel::do_not_call_me_signalCallResultCb); +} + +bool MavlinkSettingsModel::is_air_or_cam_param_busy() +{ + if(MavlinkSettingsModel::instanceAir().is_x_busy()){ + return true; + } + if(MavlinkSettingsModel::instanceAirCamera().is_x_busy()){ + return true; + } + if(MavlinkSettingsModel::instanceAirCamera2().is_x_busy()){ + return true; + } + return false; } void MavlinkSettingsModel::set_ready() @@ -74,6 +97,11 @@ void MavlinkSettingsModel::set_ready() m_is_ready=true; } +bool MavlinkSettingsModel::is_x_busy() +{ + return m_is_currently_busy; +} + void MavlinkSettingsModel::try_refetch_all_parameters_async(bool log_result) { qDebug()<<"MavlinkSettingsModel::try_fetch_all_parameters()"; @@ -98,11 +126,12 @@ void MavlinkSettingsModel::try_refetch_all_parameters_async(bool log_result) const auto param_set=result.param_set; remove_and_replace_param_set(param_set); if(log_result){ - QOpenHD::instance().show_toast("Fetch all success",false); + QOpenHD::instance().show_toast("Fetch all parameters success",false); } + set_has_params_fetched(true); }else{ if(log_result){ - QOpenHD::instance().show_toast("Fetch all failed, is your uplink working ? Use the status view for more info..",true); + QOpenHD::instance().show_toast("Fetch all parameters failed, is your uplink working ? Use the status view for more info..",true); } } m_is_currently_busy=false; @@ -143,6 +172,58 @@ MavlinkSettingsModel::SetParamResult MavlinkSettingsModel::try_set_param_string_ return result ? SetParamResult::SUCCESS : SetParamResult::NO_CONNECTION; } +void MavlinkSettingsModel::try_set_param_int_async(const QString param_id, int value,bool log_result) +{ + if(m_is_currently_busy){ + qDebug()<<"BUSY"; + finalize_update_param(param_id,static_cast(value),false,log_result); + return; + } + auto command=XParam::create_cmd_set_int(m_sys_id,m_comp_id,param_id.toStdString(),value); + m_is_currently_busy=true; + set_ui_is_busy(true); + XParam::SET_PARAM_RESULT_CB imp_cb=[this,value,param_id,log_result](XParam::SetParamResult result){ + if(result.is_accepted()){ + // success, update the cached param + MavlinkSettingsModel::SettingData tmp{param_id,value}; + updateData(std::nullopt,tmp); + }else{ + // FAILURE + } + qDebug()<<"Result:"<(value),result.is_accepted(),log_result); + }; + XParam::instance().try_set_param_async(command,imp_cb,nullptr,std::chrono::milliseconds(300),10); +} + +void MavlinkSettingsModel::try_set_param_string_async(const QString param_id,QString value,bool log_result) +{ + if(m_is_currently_busy){ + qDebug()<<"BUSY"; + finalize_update_param(param_id,value.toStdString(),false,log_result); + return; + } + auto command=XParam::create_cmd_set_string(m_sys_id,m_comp_id,param_id.toStdString(),value.toStdString()); + m_is_currently_busy=true; + set_ui_is_busy(true); + XParam::SET_PARAM_RESULT_CB imp_cb=[this,value,param_id,log_result](XParam::SetParamResult result){ + if(result.is_accepted()){ + // success, update the cached param + MavlinkSettingsModel::SettingData tmp{param_id,value.toStdString()}; + updateData(std::nullopt,tmp); + }else{ + // FAILURE + } + qDebug()<<"Result:"<(data.value).c_str()); } else if (role==ExtraValueRole){ if(std::holds_alternative(data.value)){ + if(data.unique_id=="CAMERA_TYPE"){ + auto value=std::get(data.value); + XCamera tmp{value,0,""}; + return tmp.cam_type_as_verbose_string().c_str(); + } auto value=std::get(data.value); return int_enum_get_readable(data.unique_id,value); } @@ -228,6 +314,8 @@ QVariant MavlinkSettingsModel::data(const QModelIndex &index, int role) const return ret; } else if(role ==ReadOnlyRole){ return DocumentedParam::read_only(data.unique_id.toStdString()); + }else if(role == WhitelistedRole){ + return DocumentedParam::is_param_whitelisted(data.unique_id.toStdString()); } else return QVariant(); @@ -241,7 +329,8 @@ QHash MavlinkSettingsModel::roleNames() const {ExtraValueRole, "extraValue"}, {ValueTypeRole,"valueType"}, {ShortDescriptionRole,"shortDescription"}, - {ReadOnlyRole,"read_only"} + {ReadOnlyRole,"read_only"}, + {WhitelistedRole,"whitelisted"} }; return mapping; } @@ -260,6 +349,7 @@ void MavlinkSettingsModel::removeData(int row) void MavlinkSettingsModel::updateData(std::optional row_opt, SettingData new_data) { + perform_dirty_actions(new_data); int row=-1; if(row_opt.has_value()){ row=row_opt.value(); @@ -287,6 +377,7 @@ void MavlinkSettingsModel::updateData(std::optional row_opt, SettingData ne void MavlinkSettingsModel::addData(MavlinkSettingsModel::SettingData data) { + perform_dirty_actions(data); if(is_param_whitelisted(data.unique_id.toStdString())){ // never add whitelisted params to the simple model, they need synchronization return; @@ -466,6 +557,52 @@ bool MavlinkSettingsModel::get_param_requires_manual_reboot(QString param_id) return DocumentedParam::requires_reboot(param_id.toStdString()); } +bool MavlinkSettingsModel::param_int_exists(QString param_id) +{ + //qDebug()<<"Size:"<(tmp.value)){ + return true; + } + } + qDebug()<<"int Param:"+param_id<<" does not exist"; + return false; +} + +bool MavlinkSettingsModel::param_string_exists(QString param_id) +{ + for(const auto& tmp:m_data){ + if(tmp.unique_id==param_id && std::holds_alternative(tmp.value)){ + return true; + } + } + qDebug()<<"string Param:"+param_id<<" does not exist"; + return false; +} + +int MavlinkSettingsModel::get_cached_int(QString param_id) +{ + for(const auto& tmp:m_data){ + if(tmp.unique_id.compare(param_id)==0 && std::holds_alternative(tmp.value)){ + return std::get(tmp.value); + } + } + qDebug()<(tmp.value)){ + return std::get(tmp.value).c_str(); + } + } + qDebug()< ¶m_set) { // We might not be called from the UI thread, which is why we do the signal hoop to schedule something on @@ -507,3 +644,43 @@ void MavlinkSettingsModel::ui_thread_replace_param_set(QtParamSet qt_param_set) addData(data); } } + +void MavlinkSettingsModel::finalize_update_param(QString param_id,std::variant value, bool success,bool log_result) +{ + set_last_updated_param_id(param_id); + set_last_updated_param_success(success); + if(log_result){ + std::stringstream ss; + ss<<"Update "<(value)){ + ss<(value); + }else{ + ss<(value); + } + if(success){ + ss<<" success"; + }else{ + ss<<" failure"; + } + QOpenHD::instance().show_toast(ss.str().c_str()); + } + set_update_count(m_update_count+1); + qDebug()<<"Update count:"<(data.value)){ + const auto int_value=std::get(data.value); + if(int_value>0){ + QSettings settings; + const auto settings_key="vehicle_battery_n_cells"; + const int vehicle_battery_n_cells=settings.value(settings_key,3).toInt(); + if(vehicle_battery_n_cells!=int_value){ + settings.setValue(settings_key,int_value); + QOpenHD::instance().show_toast("Updated N Cells from air unit"); + } + } + } +} diff --git a/app/telemetry/settings/mavlinksettingsmodel.h b/app/telemetry/settings/mavlinksettingsmodel.h index e84236e68..036f7c469 100644 --- a/app/telemetry/settings/mavlinksettingsmodel.h +++ b/app/telemetry/settings/mavlinksettingsmodel.h @@ -7,9 +7,11 @@ #include #include #include +#include #include "../util/mavlink_include.h" #include "../../../lib/lqtutils_master/lqtutils_prop.h" +#include "../action/impl/xparam.h" // A QT wrapper around the mavlink extended / non-extended parameters protocoll on the client // (the side that changes parameter(s) provided by a specific system & component). @@ -35,12 +37,19 @@ class MavlinkSettingsModel : public QAbstractListModel // (aka what for example mission planner would show) //static MavlinkSettingsModel& instanceFC(); explicit MavlinkSettingsModel(uint8_t sys_id,uint8_t comp_id,QObject *parent = nullptr); + // + static bool is_air_or_cam_param_busy(); public: L_RO_PROP(int, curr_get_all_progress_perc,set_curr_get_all_progress_perc,-1); // NOTE: This is only for the UI, not for c++ usage (non-atomic) - L_RO_PROP(bool,ui_is_busy,set_ui_is_busy,false) + L_RO_PROP(bool,ui_is_busy,set_ui_is_busy,false); + L_RO_PROP(bool,has_params_fetched,set_has_params_fetched,false); + L_RO_PROP(int,update_count,set_update_count,0); + L_RO_PROP(QString,last_updated_param_id,set_last_updated_param_id,""); + L_RO_PROP(bool,last_updated_param_success,set_last_updated_param_success,false); public: void set_ready(); + bool is_x_busy(); public: // callable from QT. // async with progress bar and result being prompted to the user @@ -70,7 +79,9 @@ class MavlinkSettingsModel : public QAbstractListModel ShortDescriptionRole, // Weather this parameter is read-only (we repurpose the malink parameter protocoll in this regard here) // Default false, only if a parameter is in the read-only whitelist it is marked as read-only - ReadOnlyRole + ReadOnlyRole, + // Weather this parameter is whitelisted + WhitelistedRole }; int rowCount(const QModelIndex& parent= QModelIndex()) const override; QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const override; @@ -100,6 +111,10 @@ public slots: static std::string set_param_result_as_string(const SetParamResult& res); SetParamResult try_set_param_int_impl(const QString param_id,int value); SetParamResult try_set_param_string_impl(const QString param_id,QString value); +public: + Q_INVOKABLE void try_set_param_int_async(const QString param_id,int value,bool log_result=false); + Q_INVOKABLE void try_set_param_string_async(const QString param_id,QString value,bool log_result=false); + Q_INVOKABLE bool system_is_alive(); public: struct ParamIntEnum{ bool valid; @@ -142,7 +157,13 @@ public slots: Q_INVOKABLE QString get_warning_before_safe(QString param_id); Q_INVOKABLE bool get_param_requires_manual_reboot(QString param_id); -private: +public: + // Returns true if the given (int,string) param exists + Q_INVOKABLE bool param_int_exists(QString param_id); + Q_INVOKABLE bool param_string_exists(QString param_id); + Q_INVOKABLE int get_cached_int(QString param_id); + Q_INVOKABLE QString get_cached_string(QString param_id); +private:; void remove_and_replace_param_set(const std::vector& param_set); public: struct QtParamValue{ @@ -160,6 +181,9 @@ public slots: private: std::atomic m_is_ready=false; std::atomic_bool m_is_currently_busy=false; +private: + void finalize_update_param(QString param_id,std::variant value, bool success,bool log_result); + void perform_dirty_actions(const MavlinkSettingsModel::SettingData& data); }; Q_DECLARE_METATYPE(MavlinkSettingsModel::ParamIntEnum); diff --git a/app/telemetry/settings/wblinksettingshelper.cpp b/app/telemetry/settings/wblinksettingshelper.cpp index a0acdcefb..afd9c1bba 100644 --- a/app/telemetry/settings/wblinksettingshelper.cpp +++ b/app/telemetry/settings/wblinksettingshelper.cpp @@ -196,14 +196,14 @@ int WBLinkSettingsHelper::change_param_air_and_ground_blocking(QString param_id, // we have changed the value on air, now change the ground // It is highly unlikely that fauls - if it does, we have an issue ! // But since qopenhd <-> openhd ground is either localhost or tcp, that should never be an issue - const auto command_gnd=XParam::create_cmd_set_int(OHD_SYS_ID_GROUND,OHD_COMP_ID_LINK_PARAM,param_id.toStdString(),value); + /*const auto command_gnd=XParam::create_cmd_set_int(OHD_SYS_ID_GROUND,OHD_COMP_ID_LINK_PARAM,param_id.toStdString(),value); const bool ground_success=XParam::instance().try_set_param_blocking(command_gnd,std::chrono::milliseconds(200),5); if(!ground_success){ std::stringstream ss; ss<<"Cannot change "<(keyframe_interval),"KEYFRAME"); + change_param_air_async(OHD_COMP_ID_AIR_CAMERA_PRIMARY,"KEYFRAME_I",static_cast(keyframe_interval),"KEYFRAME"); } void WBLinkSettingsHelper::set_param_video_resolution_framerate_async(bool primary,QString res_str) { const std::string value=res_str.toStdString(); const auto comp_id = primary ? OHD_COMP_ID_AIR_CAMERA_PRIMARY : OHD_COMP_ID_AIR_CAMERA_SECONDARY; - change_param_air_async(comp_id,"V_FORMAT",value,"VIDEO FORMAT"); + change_param_air_async(comp_id,"RESOLUTION_FPS",value,"VIDEO FORMAT"); } void WBLinkSettingsHelper::set_param_fec_percentage_async(int percent) { diff --git a/app/telemetry/settings/wifi_channel.h b/app/telemetry/settings/wifi_channel.h index 3319ec25d..7f03a8c37 100644 --- a/app/telemetry/settings/wifi_channel.h +++ b/app/telemetry/settings/wifi_channel.h @@ -5,16 +5,16 @@ #ifndef OPENHD_OPENHD_OHD_INTERFACE_INC_WIFI_CHANNEL_H_ #define OPENHD_OPENHD_OHD_INTERFACE_INC_WIFI_CHANNEL_H_ +#include #include #include -#include #include - +//#include "openhd_spdlog.h" namespace OHDUtil{ template static void vec_append(std::vector& dest, const std::vector& src) { - dest.insert(dest.end(), src.begin(), src.end()); + dest.insert(dest.end(), src.begin(), src.end()); } } @@ -23,7 +23,8 @@ static void vec_append(std::vector& dest, const std::vector& src) { // https://en.wikipedia.org/wiki/List_of_WLAN_channels // https://www.arubanetworks.com/vrd/OutdoorMIMOVRD/wwhelp/wwhimpl/common/html/wwhelp.htm#context=OutdoorMIMOVRD&file=AppA.html -// NOTE: DO NOT USE CHANNEL NUMBERS ANYWHERE IN CODE - USE FREQUENCIES IN MHZ, SINCE THEY ARE UNIQUE +// NOTE: DO NOT USE CHANNEL NUMBERS ANYWHERE IN CODE - USE FREQUENCIES IN MHZ, +// SINCE THEY ARE UNIQUE namespace openhd { enum class WifiSpace { G2_4, G5_8 }; @@ -31,222 +32,255 @@ enum class WifiSpace { G2_4, G5_8 }; // Wifi channel and the corresponding frequency, in mHz. // "standard" : listed under wikipedia or not. struct WifiChannel { - // frequency in mhz, recommended to use - uint32_t frequency; - // channel corresponding to this frequency, error-prone compared to the frequency since often defined incorrectly and/or for the non-standard AR9271 frequencies there is no standard channel number - int channel; - // weather this is a channel in the 2.4G space or 5.8G space - WifiSpace space; - // weather this channel is listed under wikipedia or not. - // Channels not listed under wikipedia might still work on some cards, given the driver has been modified. generally, they are not legally usable in most countries though. - bool is_standard; - // Weather this channel is legal in at least one country - bool is_legal_at_least_one_country; - // Weather it is legal in at least one country to use 40Mhz on this wifi channel - bool is_legal_any_country_40Mhz; - // Weather we (should / have to, otherwise driver crashes) use HT40+ or HT40- when doing 40Mhz on this channel - bool in_40Mhz_ht40_plus; - // weather this channel is used by openhd or not. - [[nodiscard]] std::string to_string() const { - std::stringstream ss; - ss << (int)frequency << "Mhz [" << channel << "] " - << (space == WifiSpace::G2_4 ? "2.4G" : "5.8G") - << (is_standard ? "" : "nonstandard"); - return ss.str(); - } + // frequency in mhz, recommended to use + uint32_t frequency; + // channel corresponding to this frequency, error-prone compared to the + // frequency since often defined incorrectly and/or for the non-standard + // AR9271 frequencies there is no standard channel number + int channel; + // weather this is a channel in the 2.4G space or 5.8G space + WifiSpace space; + // weather this channel is listed under wikipedia or not. + // Channels not listed under wikipedia might still work on some cards, given + // the driver has been modified. generally, they are not legally usable in + // most countries though. + bool is_standard; + // Weather this channel is legal in at least one country + bool is_legal_at_least_one_country; + // Weather it is legal in at least one country to use 40Mhz on this wifi + // channel + bool is_legal_any_country_40Mhz; + // Weather we (should / have to, otherwise driver crashes) use HT40+ or HT40- + // when doing 40Mhz on this channel + bool in_40Mhz_ht40_plus; + // weather this channel is used by openhd or not. + [[nodiscard]] std::string to_string() const { + std::stringstream ss; + ss << (int)frequency << "Mhz [" << channel << "] " + << (space == WifiSpace::G2_4 ? "2.4G" : "5.8G") + << (is_standard ? "" : "nonstandard"); + return ss.str(); + } }; static std::vector get_channels_2G() { - return std::vector{ - // These are not valid 2.4G wifi channel(s) but some cards aparently can do them, too From https://github.com/OpenHD/linux/blob/092115ae6a980feaa09722690891d99da3afb55c/drivers/net/wireless/ath/ath9k/common-init.c#L39 - // NOTE: In OpenHD we never use the channel number (since it is prone to errors, even in the linux kernel) but rather use the frequency in mhz, which is well-defined. Also read https://yo3iiu.ro/blog/?p=1301 - // NOTE: OpenHD does not use channel width <20Mhz, so we ignore a couple of channels here - WifiChannel{2312, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2317, -1, WifiSpace::G2_4, false}, - //WifiChannel{2322, -1, WifiSpace::G2_4, false}, - //WifiChannel{2327, -1, WifiSpace::G2_4, false}, - WifiChannel{2332, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2337, -1, WifiSpace::G2_4, false}, - //WifiChannel{2342, -1, WifiSpace::G2_4, false}, - //WifiChannel{2347, -1, WifiSpace::G2_4, false}, - WifiChannel{2352, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2357, -1, WifiSpace::G2_4, false}, - //WifiChannel{2362, -1, WifiSpace::G2_4, false}, - //WifiChannel{2367, -1, WifiSpace::G2_4, false}, - WifiChannel{2372, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2377, -1, WifiSpace::G2_4, false}, - //WifiChannel{2382, -1, WifiSpace::G2_4, false}, - //WifiChannel{2387, -1, WifiSpace::G2_4, false}, - WifiChannel{2392, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2397, -1, WifiSpace::G2_4, false}, - //WifiChannel{2402, -1, WifiSpace::G2_4, false}, - //WifiChannel{2407, -1, WifiSpace::G2_4, false}, - // Now to the standard Wi-Fi channel(s) - // https://en.wikipedia.org/wiki/List_of_WLAN_channels#2.4_GHz_(802.11b/g/n/ax) - WifiChannel{2412, 1, WifiSpace::G2_4, true, true, true, true}, - //WifiChannel{2417, 2, WifiSpace::G2_4, true}, - //WifiChannel{2422, 3, WifiSpace::G2_4, true}, - //WifiChannel{2427, 4, WifiSpace::G2_4, true}, - WifiChannel{2432, 5, WifiSpace::G2_4, true, true, true, false}, - //WifiChannel{2437, 6, WifiSpace::G2_4, true}, - //WifiChannel{2442, 7, WifiSpace::G2_4, true}, - //WifiChannel{2447, 8, WifiSpace::G2_4, true}, - WifiChannel{2452, 9, WifiSpace::G2_4, true, true, true, true}, - //WifiChannel{2457, 10, WifiSpace::G2_4, true}, - //WifiChannel{2462, 11, WifiSpace::G2_4, true}, - //WifiChannel{2467, 12, WifiSpace::G2_4, true}, - WifiChannel{2472, 13, WifiSpace::G2_4, true, true, true, false}, - // until here it is consistent (5Mhz increments) - // this one is neither allowed in EU nor USA - // (only in Japan under 11b) - WifiChannel{2484, 14, WifiSpace::G2_4, true, true, false}, - // and these are all not valid wlan channels, but the AR9271 can do them anyways - //WifiChannel{2487, -1, WifiSpace::G2_4, false}, - //WifiChannel{2489, -1, WifiSpace::G2_4, false}, - WifiChannel{2492, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2494, -1, WifiSpace::G2_4, false}, - //WifiChannel{2497, -1, WifiSpace::G2_4, false}, - //WifiChannel{2499, -1, WifiSpace::G2_4, false}, - WifiChannel{2512, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2532, -1, WifiSpace::G2_4, false}, - //WifiChannel{2572, -1, WifiSpace::G2_4, false}, - //WifiChannel{2592, -1, WifiSpace::G2_4, false}, - WifiChannel{2612, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2632, -1, WifiSpace::G2_4, false}, - //WifiChannel{2652, -1, WifiSpace::G2_4, false}, - //WifiChannel{2672, -1, WifiSpace::G2_4, false}, - WifiChannel{2692, -1, WifiSpace::G2_4, false, false, false}, - WifiChannel{2712, -1, WifiSpace::G2_4, false, false, false}, - //WifiChannel{2732, -1, WifiSpace::G2_4, false}, - }; + return std::vector{ + // These are not valid 2.4G wifi channel(s) but some cards aparently can + // do them, too From + // https://github.com/OpenHD/linux/blob/092115ae6a980feaa09722690891d99da3afb55c/drivers/net/wireless/ath/ath9k/common-init.c#L39 + // NOTE: In OpenHD we never use the channel number (since it is prone to + // errors, even in the linux kernel) but rather use the frequency in mhz, + // which is well-defined. Also read https://yo3iiu.ro/blog/?p=1301 + // NOTE: OpenHD does not use channel width <20Mhz, so we ignore a couple + // of channels here + WifiChannel{2312, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2317, -1, WifiSpace::G2_4, false}, + // WifiChannel{2322, -1, WifiSpace::G2_4, false}, + // WifiChannel{2327, -1, WifiSpace::G2_4, false}, + WifiChannel{2332, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2337, -1, WifiSpace::G2_4, false}, + // WifiChannel{2342, -1, WifiSpace::G2_4, false}, + // WifiChannel{2347, -1, WifiSpace::G2_4, false}, + WifiChannel{2352, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2357, -1, WifiSpace::G2_4, false}, + // WifiChannel{2362, -1, WifiSpace::G2_4, false}, + // WifiChannel{2367, -1, WifiSpace::G2_4, false}, + WifiChannel{2372, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2377, -1, WifiSpace::G2_4, false}, + // WifiChannel{2382, -1, WifiSpace::G2_4, false}, + // WifiChannel{2387, -1, WifiSpace::G2_4, false}, + WifiChannel{2392, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2397, -1, WifiSpace::G2_4, false}, + // WifiChannel{2402, -1, WifiSpace::G2_4, false}, + // WifiChannel{2407, -1, WifiSpace::G2_4, false}, + // Now to the standard Wi-Fi channel(s) + // https://en.wikipedia.org/wiki/List_of_WLAN_channels#2.4_GHz_(802.11b/g/n/ax) + WifiChannel{2412, 1, WifiSpace::G2_4, true, true, true, true}, + // WifiChannel{2417, 2, WifiSpace::G2_4, true}, + // WifiChannel{2422, 3, WifiSpace::G2_4, true}, + // WifiChannel{2427, 4, WifiSpace::G2_4, true}, + WifiChannel{2432, 5, WifiSpace::G2_4, true, true, true, false}, + // WifiChannel{2437, 6, WifiSpace::G2_4, true}, + // WifiChannel{2442, 7, WifiSpace::G2_4, true}, + // WifiChannel{2447, 8, WifiSpace::G2_4, true}, + WifiChannel{2452, 9, WifiSpace::G2_4, true, true, true, true}, + // WifiChannel{2457, 10, WifiSpace::G2_4, true}, + // WifiChannel{2462, 11, WifiSpace::G2_4, true}, + // WifiChannel{2467, 12, WifiSpace::G2_4, true}, + WifiChannel{2472, 13, WifiSpace::G2_4, true, true, true, false}, + // until here it is consistent (5Mhz increments) + // this one is neither allowed in EU nor USA + // (only in Japan under 11b) + WifiChannel{2484, 14, WifiSpace::G2_4, true, true, false}, + // and these are all not valid wlan channels, but the AR9271 can do them + // anyways + // WifiChannel{2487, -1, WifiSpace::G2_4, false}, + // WifiChannel{2489, -1, WifiSpace::G2_4, false}, + WifiChannel{2492, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2494, -1, WifiSpace::G2_4, false}, + // WifiChannel{2497, -1, WifiSpace::G2_4, false}, + // WifiChannel{2499, -1, WifiSpace::G2_4, false}, + WifiChannel{2512, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2532, -1, WifiSpace::G2_4, false}, + // WifiChannel{2572, -1, WifiSpace::G2_4, false}, + // WifiChannel{2592, -1, WifiSpace::G2_4, false}, + WifiChannel{2612, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2632, -1, WifiSpace::G2_4, false}, + // WifiChannel{2652, -1, WifiSpace::G2_4, false}, + // WifiChannel{2672, -1, WifiSpace::G2_4, false}, + WifiChannel{2692, -1, WifiSpace::G2_4, false, false, false}, + WifiChannel{2712, -1, WifiSpace::G2_4, false, false, false}, + // WifiChannel{2732, -1, WifiSpace::G2_4, false}, + }; } - -static std::vector get_channels_5G(){ - return std::vector{ - // https://en.wikipedia.org/wiki/List_of_WLAN_channels#5_GHz_(802.11a/h/j/n/ac/ax) - WifiChannel{5180, 36, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5200, 40, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5220, 44, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5240, 48, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5260, 52, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5280, 56, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5300, 60, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5320, 64, WifiSpace::G5_8, true, true, true, false}, - // These channel(s) are not valid Wi-Fi channels in all countries - WifiChannel{5340, 68, WifiSpace::G5_8, true, false, false}, // (Only on 20Mhz allowed in some) - WifiChannel{5360, 72, WifiSpace::G5_8, true, false, false}, - WifiChannel{5380, 76, WifiSpace::G5_8, true, false, false}, - WifiChannel{5400, 80, WifiSpace::G5_8, true, false, false}, - WifiChannel{5420, 84, WifiSpace::G5_8, true, false, false}, - WifiChannel{5440, 88, WifiSpace::G5_8, true, false, false}, - WifiChannel{5460, 92, WifiSpace::G5_8, true, false, false}, - WifiChannel{5480, 96, WifiSpace::G5_8, true, false, false}, - // part often disabled end - WifiChannel{5500, 100, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5520, 104, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5540, 108, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5560, 112, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5580, 116, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5600, 120, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5620, 124, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5640, 128, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5660, 132, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5680, 136, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5700, 140, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5720, 144, WifiSpace::G5_8, true, true, true, false}, - // There is a gap not usable in between 5720 and 5745 Mhz - // For some reason, there is a 25Mhz jump here. Weird stuff ... - WifiChannel{5745, 149, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5765, 153, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5785, 157, WifiSpace::G5_8, true, true, true, true}, - WifiChannel{5805, 161, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5825, 165, WifiSpace::G5_8, true, true, true, true}, - // starting from here, often disabled territory begins again - WifiChannel{5845, 169, WifiSpace::G5_8, true, true, true, false}, - WifiChannel{5865, 173, WifiSpace::G5_8, true, true, true, true}, - // This one (177) is listed in wikipedia, but not valid in any country - but it works on rtl8812bu - WifiChannel{5885, 177, WifiSpace::G5_8, true, true, true, false}, - // this one does not work on bu, au no idea - for now, hide it - WifiChannel{5905, 181, WifiSpace::G5_8, true, false, false, true}, - }; +static std::vector get_channels_5G() { + return std::vector{ + // https://en.wikipedia.org/wiki/List_of_WLAN_channels#5_GHz_(802.11a/h/j/n/ac/ax) + WifiChannel{5180, 36, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5200, 40, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5220, 44, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5240, 48, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5260, 52, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5280, 56, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5300, 60, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5320, 64, WifiSpace::G5_8, true, true, true, false}, + // These channel(s) are not valid Wi-Fi channels in all countries + // below only on 20Mhz allowed in some) + WifiChannel{5340, 68, WifiSpace::G5_8, true, false,false, false}, + WifiChannel{5360, 72, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5380, 76, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5400, 80, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5420, 84, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5440, 88, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5460, 92, WifiSpace::G5_8, true, false, false,false}, + WifiChannel{5480, 96, WifiSpace::G5_8, true, false, false,false}, + // part often disabled end + WifiChannel{5500, 100, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5520, 104, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5540, 108, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5560, 112, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5580, 116, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5600, 120, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5620, 124, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5640, 128, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5660, 132, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5680, 136, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5700, 140, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5720, 144, WifiSpace::G5_8, true, true, true, false}, + // There is a gap not usable in between 5720 and 5745 Mhz + // For some reason, there is a 25Mhz jump here. Weird stuff ... + WifiChannel{5745, 149, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5765, 153, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5785, 157, WifiSpace::G5_8, true, true, true, true}, + WifiChannel{5805, 161, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5825, 165, WifiSpace::G5_8, true, true, true, true}, + // starting from here, often disabled territory begins again + WifiChannel{5845, 169, WifiSpace::G5_8, true, true, true, false}, + WifiChannel{5865, 173, WifiSpace::G5_8, true, true, true, true}, + // This one (177) is listed in wikipedia, but not valid in any country - + // but it works on rtl8812bu + WifiChannel{5885, 177, WifiSpace::G5_8, true, true, true, false}, + // this one does not work on bu, au no idea - for now, hide it + WifiChannel{5905, 181, WifiSpace::G5_8, true, false, false, true}, + }; }; // Returns all Wi-Fi channels 5G that are legal in any country -static std::vector get_channels_5G_legal_at_least_one_country(){ - const auto channels=get_channels_5G(); - std::vector ret; - for(auto& channel:channels){ - if(channel.is_legal_at_least_one_country){ - ret.push_back(channel); - } +static std::vector get_channels_5G_legal_at_least_one_country() { + const auto channels = get_channels_5G(); + std::vector ret; + for (auto& channel : channels) { + if (channel.is_legal_at_least_one_country) { + ret.push_back(channel); } - return ret; + } + return ret; } // Returns all Wi-Fi channels 2.4G that are legal in at least one country -static std::vector get_channels_2G_legal_at_least_one_country(){ - const auto channels=get_channels_2G(); - std::vector ret; - for(auto& channel:channels){ - if(channel.is_legal_at_least_one_country){ - ret.push_back(channel); - } +static std::vector get_channels_2G_legal_at_least_one_country() { + const auto channels = get_channels_2G(); + std::vector ret; + for (auto& channel : channels) { + if (channel.is_legal_at_least_one_country) { + ret.push_back(channel); } - return ret; + } + return ret; } - static std::vector get_all_channels_2G_5G() { - std::vector channels{}; - OHDUtil::vec_append(channels, get_channels_2G()); - OHDUtil::vec_append(channels, get_channels_5G()); - return channels; + std::vector channels{}; + OHDUtil::vec_append(channels, get_channels_2G()); + OHDUtil::vec_append(channels, get_channels_5G()); + return channels; } -static std::vector get_all_channel_frequencies(const std::vector& channels) { - std::vector frequencies{}; - frequencies.reserve(channels.size()); - for (const auto& channel : channels) { - frequencies.push_back(channel.frequency); - } - return frequencies; +static std::vector get_all_channel_frequencies( + const std::vector& channels) { + std::vector frequencies{}; + frequencies.reserve(channels.size()); + for (const auto& channel : channels) { + frequencies.push_back(channel.frequency); + } + return frequencies; } // Returns the corresponding Wi-Fi Channel if there is one -// since the mavlink setting is an int, this might not always be possible (and the frequency is then 100% not possible) and therefore should be discarded / fixed -static std::optional channel_from_frequency(uint32_t frequency) { - const auto channels = get_all_channels_2G_5G(); - for (const auto& channel : channels) { - if (channel.frequency == frequency) { - return channel; - } +// since the mavlink setting is an int, this might not always be possible (and +// the frequency is then 100% not possible) and therefore should be discarded / +// fixed +static std::optional channel_from_frequency( + uint32_t frequency) { + const auto channels = get_all_channels_2G_5G(); + for (const auto& channel : channels) { + if (channel.frequency == frequency) { + return channel; } - return std::nullopt; + } + return std::nullopt; } -static WifiSpace get_space_from_frequency(uint32_t frequency){ - auto channel= channel_from_frequency(frequency); - if(!channel.has_value()){ - return WifiSpace::G5_8; - } - return channel.value().space; +static WifiSpace get_space_from_frequency(uint32_t frequency) { + auto channel = channel_from_frequency(frequency); + if (!channel.has_value()) { + //openhd::log::get_default()->warn("Invalid frequency {}, assuming 5G", + // frequency); + return WifiSpace::G5_8; + } + return channel.value().space; } -static std::vector frequencies_to_channels(const std::vector& frequencies){ - std::vector ret; - auto channels=get_all_channels_2G_5G(); - for(const auto freq:frequencies){ - auto tmp= channel_from_frequency(freq); - if(tmp.has_value()){ - ret.push_back(tmp.value()); - } +static std::vector frequencies_to_channels( + const std::vector& frequencies) { + std::vector ret; + auto channels = get_all_channels_2G_5G(); + for (const auto freq : frequencies) { + auto tmp = channel_from_frequency(freq); + if (tmp.has_value()) { + ret.push_back(tmp.value()); } - return ret; + } + return ret; } -static std::vector get_openhd_channels_1_to_5(){ - std::vector frequencies={5700,5745,5785,5825,5865}; - return frequencies_to_channels(frequencies); +static std::vector get_openhd_channels_1_to_5() { + std::vector frequencies = {5700, 5745, 5785, 5825, 5865}; + return frequencies_to_channels(frequencies); } +static std::vector filter_ht40plus_only( + const std::vector& frequencies) { + std::vector ret; + for (const auto& freq : frequencies) { + auto tmp = openhd::channel_from_frequency(freq); + if (tmp.has_value()) { + if (tmp->in_40Mhz_ht40_plus) { + ret.push_back(tmp.value()); + } + } + } + return ret; } + +} // namespace openhd #endif // OPENHD_OPENHD_OHD_INTERFACE_INC_WIFI_CHANNEL_H_ diff --git a/app/telemetry/telemetry.pri b/app/telemetry/telemetry.pri index 468f00337..66bd7e8c6 100644 --- a/app/telemetry/telemetry.pri +++ b/app/telemetry/telemetry.pri @@ -8,6 +8,7 @@ SOURCES += \ $$PWD/action/fcmissionhandler.cpp \ $$PWD/action/fcmsgintervalhandler.cpp \ $$PWD/action/ohdaction.cpp \ + $$PWD/connection/mavlinkchannel.cpp \ $$PWD/connection/tcp_connection.cpp \ $$PWD/connection/udp_connection.cpp \ $$PWD/models/fcmapmodel.cpp \ @@ -34,7 +35,9 @@ HEADERS += \ $$PWD/action/fcmissionhandler.h \ $$PWD/action/fcmsgintervalhandler.h \ $$PWD/action/ohdaction.h \ + $$PWD/connection/mavlinkchannel.h \ $$PWD/connection/tcp_connection.h \ + $$PWD/models/openhd_core/platform.hpp \ $$PWD/settings/documentedparam.h \ $$PWD/action/impl/xparam.h \ $$PWD/settings/frequencyhelper.h \ @@ -58,6 +61,7 @@ HEADERS += \ app/telemetry/settings/mavlinksettingsmodel.h \ app/telemetry/models/fcmavlinksystem.h \ app/telemetry/models/fcmavlinkmissionitemsmodel.h \ + app/telemetry/models/openhd_core/camera.hpp \ WindowsBuild{ LIBS += -lws2_32 diff --git a/app/util/qopenhd.cpp b/app/util/qopenhd.cpp index c996473ba..c3cad5649 100644 --- a/app/util/qopenhd.cpp +++ b/app/util/qopenhd.cpp @@ -268,6 +268,15 @@ bool QOpenHD::is_android() #endif } +bool QOpenHD::is_windows() +{ +#ifdef __windows__ + return true; +#else + return false; +#endif +} + void QOpenHD::android_open_tethering_settings() { #ifdef __android__ @@ -356,6 +365,14 @@ void QOpenHD::show_toast(QString message,bool long_toast) emit signal_toast_add(message,long_toast); } +void QOpenHD::set_busy_for_milliseconds(int milliseconds,QString reason) +{ + set_is_busy(true); + set_busy_reason(reason); + const int timeout_ms = milliseconds; + QTimer::singleShot(timeout_ms, this, &QOpenHD::handle_busy_timeout); +} + void QOpenHD::handle_toast_timeout() { @@ -369,6 +386,12 @@ void QOpenHD::handle_toast_timeout() } } +void QOpenHD::handle_busy_timeout() +{ + set_is_busy(false); + set_busy_reason(""); +} + void QOpenHD::do_not_call_toast_add(QString text,bool long_toast) { qDebug()<<"do_not_call_toast_add"< m_toast_message_queue; void handle_toast_timeout(); + void handle_busy_timeout(); public: signals: void signal_toast_add(QString text,bool long_toast); diff --git a/app/videostreaming/android/lowlagdecoder.cpp b/app/videostreaming/android/lowlagdecoder.cpp index c88f6aa3a..e97aa5cf6 100644 --- a/app/videostreaming/android/lowlagdecoder.cpp +++ b/app/videostreaming/android/lowlagdecoder.cpp @@ -11,7 +11,7 @@ using namespace std::chrono; -static void h264_configureAMediaFormat(KeyFrameFinder& kff,AMediaFormat* format){ +static void h264_configureAMediaFormat(CodecConfigFinder& kff,AMediaFormat* format){ const auto sps=kff.getCSD0(); const auto pps=kff.getCSD1(); const auto videoWH= sps.sps_get_width_height(); @@ -27,15 +27,15 @@ static void h264_configureAMediaFormat(KeyFrameFinder& kff,AMediaFormat* format) //AMediaFormat_setInt32(decoder.format,AMEDIAFORMAT_KEY_PRIORITY,0); //writeAndroidPerformanceParams(format); } -static void h265_configureAMediaFormat(KeyFrameFinder& kff,AMediaFormat* format){ +static void h265_configureAMediaFormat(CodecConfigFinder& kff,AMediaFormat* format){ std::vector buff={}; const auto SPS=kff.getCSD0(); const auto PPS=kff.getCSD1(); const auto VPS=kff.getVPS(); buff.reserve(SPS.getSize()+PPS.getSize()+VPS.getSize()); - KeyFrameFinder::appendNaluData(buff,VPS); - KeyFrameFinder::appendNaluData(buff,SPS); - KeyFrameFinder::appendNaluData(buff,PPS); + CodecConfigFinder::appendNaluData(buff,VPS); + CodecConfigFinder::appendNaluData(buff,SPS); + CodecConfigFinder::appendNaluData(buff,PPS); const auto videoWH= std::array{1280,720}; AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_WIDTH,videoWH[0]); AMediaFormat_setInt32(format,AMEDIAFORMAT_KEY_HEIGHT,videoWH[1]); @@ -117,7 +117,7 @@ void LowLagDecoder::interpretNALU(const NALU& nalu){ if(inputPipeClosed){ //A feedD thread (e.g. file or udp) thread might be running even tough no output surface was set //But at least we can buffer the sps/pps data - mKeyFrameFinder.saveIfKeyFrame(nalu); + mKeyFrameFinder.save_if_config(nalu); return; } if(decoder.configured){ @@ -132,8 +132,8 @@ void LowLagDecoder::interpretNALU(const NALU& nalu){ }else{ //Store sps,pps, vps(H265 only) // As soon as enough data has been buffered to initialize the decoder,do so. - mKeyFrameFinder.saveIfKeyFrame(nalu); - if(mKeyFrameFinder.allKeyFramesAvailable(IS_H265)){ + mKeyFrameFinder.save_if_config(nalu); + if(mKeyFrameFinder.all_config_available(IS_H265)){ configureStartDecoder(); } } diff --git a/app/videostreaming/android/lowlagdecoder.h b/app/videostreaming/android/lowlagdecoder.h index 1fdecf4e5..9aa296c44 100644 --- a/app/videostreaming/android/lowlagdecoder.h +++ b/app/videostreaming/android/lowlagdecoder.h @@ -11,7 +11,7 @@ #include #include -#include +#include #include "../../common/TimeHelper.hpp" @@ -112,7 +112,7 @@ class LowLagDecoder { static constexpr auto TIME_BETWEEN_LOGS=std::chrono::seconds(5); static constexpr int64_t BUFFER_TIMEOUT_US=35*1000; //40ms (a little bit more than 32 ms (==30 fps)) private: - KeyFrameFinder mKeyFrameFinder; + CodecConfigFinder mKeyFrameFinder; bool IS_H265= false; }; diff --git a/app/videostreaming/avcodec/avcodec_decoder.cpp b/app/videostreaming/avcodec/avcodec_decoder.cpp index a67945f9a..6f6995108 100644 --- a/app/videostreaming/avcodec/avcodec_decoder.cpp +++ b/app/videostreaming/avcodec/avcodec_decoder.cpp @@ -775,7 +775,11 @@ void AVCodecDecoder::open_and_decode_until_error_custom_rtp(const QOpenHDVideoHe return; } qDebug()<<"AVCodecDecoder::open_and_decode_until_error_custom_rtp()-begin loop"; +#ifdef HACK_RAW + m_rtp_receiver=std::make_unique(stream_config.udp_rtp_input_port,stream_config.udp_rtp_input_ip_address,stream_config.video_codec==1,settings.generic.dev_feed_incomplete_frames_to_decoder); +#else m_rtp_receiver=std::make_unique(stream_config.udp_rtp_input_port,stream_config.udp_rtp_input_ip_address,stream_config.video_codec==1,settings.generic.dev_feed_incomplete_frames_to_decoder); +#endif reset_before_decode_start(); DecodingStatistcs::instance().set_decoding_type(selected_decoding_type.c_str()); @@ -812,6 +816,7 @@ void AVCodecDecoder::open_and_decode_until_error_custom_rtp(const QOpenHDVideoHe // No buff after X seconds continue; } + //qDebug()<<"Got frame"; //qDebug()<<"Got decode data (after keyframe)"; pkt->data=(uint8_t*)buf->get_nal().getData(); pkt->size=buf->get_nal().getSize(); diff --git a/app/videostreaming/avcodec/avcodec_decoder.h b/app/videostreaming/avcodec/avcodec_decoder.h index 761f6d5f7..b7d603369 100644 --- a/app/videostreaming/avcodec/avcodec_decoder.h +++ b/app/videostreaming/avcodec/avcodec_decoder.h @@ -17,9 +17,15 @@ #include #include +#include + //exp //#include "drm_kms/drmprime_out.h" +// DIRTY +//#define HACK_RAW + + /** * Decoding and display of primary video on all platforms except android * NOTE: On rpi, we actually don't use avcodec, but the decode service workaround (mmal) @@ -95,7 +101,11 @@ class AVCodecDecoder : public QObject private: void fetch_frame_or_feed_input_packet(); private: - std::unique_ptr m_rtp_receiver=nullptr; +#ifdef HACK_RAW + std::unique_ptr m_rtp_receiver=nullptr; +#else + std::unique_ptr m_rtp_receiver=nullptr; +#endif private: // Custom rtp parse (and therefore limited to h264 and h265) // AND always goes the avcodec decode route (SW decode or avcodec mmal decode). diff --git a/app/videostreaming/vscommon/custom/rawreceiver.cpp b/app/videostreaming/vscommon/custom/rawreceiver.cpp new file mode 100644 index 000000000..56a3761ff --- /dev/null +++ b/app/videostreaming/vscommon/custom/rawreceiver.cpp @@ -0,0 +1,199 @@ +#include "rawreceiver.h" + +#include +#include "QOpenHDVideoHelper.hpp" +#include "decodingstatistcs.h" + +RawReceiver::RawReceiver(int port,std::string ip,bool is_h265,bool unused): + is_h265(is_h265) +{ + m_keyframe_finder=std::make_unique(); + auto udp_config=UDPReceiver::Configuration{std::nullopt,port}; + udp_config.opt_os_receive_buff_size=UDPReceiver::BIG_UDP_RECEIVE_BUFFER_SIZE; + udp_config.set_sched_param_max_realtime=true; + udp_config.enable_nonblocking=false; + m_udp_receiver=std::make_unique("V_REC",udp_config,[this](const uint8_t *payload, const std::size_t payloadSize){ + this->udp_raw_data_callback(payload,payloadSize); + }); + m_udp_receiver->startReceiving(); +} + +RawReceiver::~RawReceiver() +{ + if(m_udp_receiver){ + m_udp_receiver->stopReceiving(); + } +} + +std::shared_ptr RawReceiver::get_next_frame(std::optional timeout) +{ + //qDebug()<<"get_data size_estimate:"< > RawReceiver::get_config_data() +{ + std::lock_guard lock(m_data_mutex); + if(m_keyframe_finder->all_config_available(is_h265)){ + return m_keyframe_finder->get_config_data(is_h265); + } + return nullptr; +} + +std::array RawReceiver::sps_get_width_height() +{ + std::lock_guard lock(m_data_mutex); + return m_keyframe_finder->sps_get_width_height(); +} + +static std::string bytes_as_string(const uint8_t* data, int data_len) { + std::stringstream ss; + ss << "["; + for(int i=0;i= NALU::NALU_MAXLEN - 1) { + qDebug()<<"Exceeded nal size"; + // This should never happen, but rather continue parsing than + // possibly raising an 'memory access' exception + nalu_data_position = 0; + } + // Handles 0,0,1 and 0,0,0,1 start codes, as well as basic stream corruption errors. + int parsed_prefix_len; + switch (nalu_search_state) { + case 0: + case 1: + if (data[i] == 0) + nalu_search_state++; + else + nalu_search_state = 0; + break; + case 2: + case 3: + if (data[i]==0){ + if(nalu_search_state==3){ + // Ignore this 0 + nalu_data_position--; + } + // as long as only zeroes come in, we don't have to do anything - I don't think 0000001 should ever happen, but could be a valid start code + nalu_search_state=3; + }else if(data[i]==1){ + parsed_prefix_len = nalu_search_state==3 ? 4 : 3; + if(NALU::has_valid_prefix(nalu_data) && nalu_data_position>4){ + raw_nalu_callback(nalu_data,nalu_data_position-parsed_prefix_len); + }else{ + //qDebug()<<"Not a valid NALU"; + } + // move the read prefix to the beginning of the nalu buffer + NALU::write_prefix(nalu_data,parsed_prefix_len==4); + nalu_data_position = parsed_prefix_len; + //nalu_data[0] = 0; + //nalu_data[1] = 0; + //nalu_data[2] = 0; + //nalu_data[3] = 1; + // Forward NALU only if it has enough data + //raw_nalu_callback(nalu_data,nalu_data_position-4); + //nalu_data_position = 4; + nalu_search_state = 0; + }else{ + nalu_search_state = 0; + } + break; + default: + break; + } + } +} + +void RawReceiver::raw_nalu_callback(const uint8_t *data, int len) +{ + //qDebug()<<"Got NAL "< lock(m_data_mutex); + // we discard any data in this state + // TODO fixme + //if(config_has_changed_during_decode)return; + //qDebug()<<"Got frame2"; + NALU nalu(nalu_data,nalu_data_len,is_h265); + qDebug()<<"Got NAL:"<=3){ + DecodingStatistcs::instance().util_set_estimate_keyframe_interval_int((n_frames_non_idr+n_frames_idr)/n_frames_idr); + n_frames_idr=0; + n_frames_non_idr=0; + } + if(m_keyframe_finder->all_config_available(is_h265)){ + if(!m_keyframe_finder->check_is_still_same_config_data(nalu)){ + // We neither queue on new data nor call the callback - upper level needs to reconfigure the decoder + qDebug()<<"config_has_changed_during_decode"; + config_has_changed_during_decode=true; + qDebug()<std::chrono::seconds(2)){ + const auto fps=m_estimate_fps_calculator.recalculate_fps_and_clear(); + const auto fps_as_string=StringHelper::to_string_with_precision(fps,2)+"fps"; + DecodingStatistcs::instance().set_estimate_rtp_fps({fps_as_string.c_str()}); + } + //qDebug()<<"Queue size:"<(nalu))){ + // If we cannot push a frame onto this queue, it means the decoder cannot keep up what we want to provide to it + n_dropped_frames++; + qDebug()<<"Dropping incoming frame, total:"<save_if_config(nalu); + } +} diff --git a/app/videostreaming/vscommon/custom/rawreceiver.h b/app/videostreaming/vscommon/custom/rawreceiver.h new file mode 100644 index 000000000..17b0c5a19 --- /dev/null +++ b/app/videostreaming/vscommon/custom/rawreceiver.h @@ -0,0 +1,55 @@ +#ifndef RAWRECEIVER_H +#define RAWRECEIVER_H + +#include + +#include "../nalu/NALU.hpp" +#include + +#include + +#include "common/TimeHelper.hpp" +#include "common/ThreadsafeQueue.hpp" + + +class RawReceiver +{ +public: + static constexpr const auto NALU_MAXLEN=1024*1024; + RawReceiver(int port,std::string ip,bool is_h265,bool unused); + ~RawReceiver(); + // Returns the oldest frame if available. + // (nullptr on failure) + // The timeout is optional + std::shared_ptr get_next_frame(std::optional timeout=std::nullopt); + // return nullptr if not enough config data is available yet, otherwise, return valid config data + std::shared_ptr> get_config_data(); + bool config_has_changed_during_decode=false; + // get width height using the config data (SPS) + // do not call that if there is no config data + std::array sps_get_width_height(); +private: + void udp_raw_data_callback(const uint8_t *payload, const std::size_t payloadSize); + void raw_nalu_callback(const uint8_t *data,int len); + void queue_data(const uint8_t* nalu_data,const std::size_t nalu_data_len); +private: + const bool is_h265; + std::mutex m_data_mutex; + std::unique_ptr m_udp_receiver=nullptr; + std::unique_ptr m_keyframe_finder; + std::array m_curr_nalu; + int m_nalu_data_length=0; + int nalu_search_state=0; + // Calculate fps, but note that this might not give the exact/correct value in some case(s) + FPSCalculator m_estimate_fps_calculator{}; + // space for up to X NALUs to account for "weird" cases, fifo anyways + // In case the decoder cannot keep up with the data we provide to it, the only fix would be to reduce the fps/resolution anyways. + qopenhd::ThreadsafeQueue> m_data_queue{20}; +private: + int n_frames_non_idr=0; + int n_frames_idr=0; + int n_dropped_frames=0; + bool valid=false; +}; + +#endif // RAWRECEIVER_H diff --git a/app/videostreaming/vscommon/decodingstatistcs.cpp b/app/videostreaming/vscommon/decodingstatistcs.cpp index 2f9a23e6f..cf18f8965 100644 --- a/app/videostreaming/vscommon/decodingstatistcs.cpp +++ b/app/videostreaming/vscommon/decodingstatistcs.cpp @@ -25,7 +25,7 @@ void DecodingStatistcs::reset_all_to_default() set_n_missing_rtp_video_packets(-1); set_rtp_measured_bitrate("-1"); set_estimate_rtp_fps("-1"); - set_estimate_keyframe_interval(-1); + set_estimate_keyframe_interval("N/A"); set_n_decoder_dropped_frames(-1); } @@ -36,3 +36,10 @@ void DecodingStatistcs::util_set_primary_stream_frame_format(std::string format, set_primary_stream_frame_format(ss.str().c_str()); } +void DecodingStatistcs::util_set_estimate_keyframe_interval_int(int value) +{ + std::stringstream ss; + ss< SPS=nullptr; std::unique_ptr PPS=nullptr; // VPS are only used in H265 std::unique_ptr VPS=nullptr; public: - bool saveIfKeyFrame(const NALU &nalu){ + bool save_if_config(const NALU &nalu){ if(nalu.getSize()<=0)return false; if(nalu.isSPS()){ SPS=std::make_unique(nalu); @@ -41,14 +41,14 @@ class KeyFrameFinder{ } // H264 needs sps and pps // H265 needs sps,pps and vps - bool allKeyFramesAvailable(const bool IS_H265=false){ + bool all_config_available(const bool IS_H265=false){ if(IS_H265){ return SPS != nullptr && PPS != nullptr && VPS!=nullptr; } return SPS != nullptr && PPS != nullptr; } - std::shared_ptr> get_keyframe_data(const bool IS_H265=false){ - assert(allKeyFramesAvailable(IS_H265)); + std::shared_ptr> get_config_data(const bool IS_H265=false){ + assert(all_config_available(IS_H265)); if(IS_H265){ // Looks like avcodec wants the VPS before sps and pps auto& sps=SPS->get_nal(); @@ -74,7 +74,7 @@ class KeyFrameFinder{ // returns false if the config data (SPS,PPS,optional VPS) has changed // true otherwise bool check_is_still_same_config_data(const NALU &nalu){ - assert(allKeyFramesAvailable(nalu.IS_H265_PACKET)); + assert(all_config_available(nalu.IS_H265_PACKET)); if(nalu.isSPS()){ return compare(nalu,SPS->get_nal()); }else if(nalu.isPPS()){ diff --git a/app/videostreaming/vscommon/nalu/NALU.hpp b/app/videostreaming/vscommon/nalu/NALU.hpp index dd6c6548d..90a78a2ba 100644 --- a/app/videostreaming/vscommon/nalu/NALU.hpp +++ b/app/videostreaming/vscommon/nalu/NALU.hpp @@ -194,6 +194,22 @@ class NALU{ ss<<"id:"<(ss.str()); } - m_keyframe_finder=std::make_unique(); + m_keyframe_finder=std::make_unique(); #ifdef OPENHD_USE_LIB_UVGRTP m_session = m_ctx.create_session(ip.c_str()); int flags = RCE_RECEIVE_ONLY; @@ -84,15 +84,13 @@ RTPReceiver::~RTPReceiver() std::shared_ptr RTPReceiver::get_next_frame(std::optional timeout) { - std::shared_ptr ret=nullptr; - //qDebug()<<"get_data size_estimate:"< lock(m_new_nalu_data_cb_mutex); @@ -102,8 +100,8 @@ void RTPReceiver::register_new_nalu_callback(NEW_NALU_CALLBACK cb){ std::shared_ptr> RTPReceiver::get_config_data() { std::lock_guard lock(m_data_mutex); - if(m_keyframe_finder->allKeyFramesAvailable(is_h265)){ - return m_keyframe_finder->get_keyframe_data(is_h265); + if(m_keyframe_finder->all_config_available(is_h265)){ + return m_keyframe_finder->get_config_data(is_h265); } return nullptr; } @@ -123,18 +121,29 @@ void RTPReceiver::queue_data(const uint8_t* nalu_data,const std::size_t nalu_dat NALU nalu(nalu_data,nalu_data_len,is_h265); //qDebug()<<"Got frame:"<2)return; + dev_count++; + }*/ if(nalu.is_frame_but_not_keyframe()){ n_frames_non_idr++; } if(nalu.is_keyframe()){ + //qDebug()<<"Got frame:"<=3){ - DecodingStatistcs::instance().set_estimate_keyframe_interval((n_frames_non_idr+n_frames_idr)/n_frames_idr); + int keyframe_interval=(n_frames_non_idr+n_frames_idr)/n_frames_idr; + DecodingStatistcs::instance().util_set_estimate_keyframe_interval_int(keyframe_interval); n_frames_idr=0; n_frames_non_idr=0; } - if(m_keyframe_finder->allKeyFramesAvailable(is_h265)){ + if(n_frames_non_idr>60*3 && n_frames_idr==0){ + std::stringstream ss; + ss<<">"<all_config_available(is_h265)){ if(!m_keyframe_finder->check_is_still_same_config_data(nalu)){ // We neither queue on new data nor call the callback - upper level needs to reconfigure the decoder qDebug()<<"config_has_changed_during_decode"; @@ -174,7 +183,7 @@ void RTPReceiver::queue_data(const uint8_t* nalu_data,const std::size_t nalu_dat } }else{ // We don't have all config data yet, drop anything that is not config data. - m_keyframe_finder->saveIfKeyFrame(nalu); + m_keyframe_finder->save_if_config(nalu); } } diff --git a/app/videostreaming/vscommon/rtp/rtpreceiver.h b/app/videostreaming/vscommon/rtp/rtpreceiver.h index 780a9b7c2..42e976e0c 100644 --- a/app/videostreaming/vscommon/rtp/rtpreceiver.h +++ b/app/videostreaming/vscommon/rtp/rtpreceiver.h @@ -11,10 +11,10 @@ #include #include "../nalu/NALU.hpp" -#include "../nalu/KeyFrameFinder.hpp" +#include "../nalu/CodecConfigFinder.hpp" #include "common/TimeHelper.hpp" -#include "common/moodycamel/readerwriterqueue/readerwritercircularbuffer.h" +#include "common/ThreadsafeQueue.hpp" //#define OPENHD_USE_LIB_UVGRTP @@ -59,13 +59,13 @@ class RTPReceiver std::mutex m_data_mutex; // space for up to X NALUs to account for "weird" cases, fifo anyways // In case the decoder cannot keep up with the data we provide to it, the only fix would be to reduce the fps/resolution anyways. - moodycamel::BlockingReaderWriterCircularBuffer> m_data_queue{20}; + qopenhd::ThreadsafeQueue> m_data_queue{20}; void queue_data(const uint8_t* nalu_data,const std::size_t nalu_data_len); std::mutex m_new_nalu_data_cb_mutex; NEW_NALU_CALLBACK m_new_nalu_cb=nullptr; bool forward_via_cb_if_registered(); private: - std::unique_ptr m_keyframe_finder; + std::unique_ptr m_keyframe_finder; int n_dropped_frames=0; BitrateCalculator m_rtp_bitrate; private: @@ -84,6 +84,7 @@ class RTPReceiver int n_frames_idr=0; private: std::chrono::steady_clock::time_point m_last_log_hud_dropped_frame=std::chrono::steady_clock::now(); + int dev_count=0; }; #endif // RTPRECEIVER_H diff --git a/app/videostreaming/vscommon/vscommon.pri b/app/videostreaming/vscommon/vscommon.pri index 54ddfd469..12002a930 100644 --- a/app/videostreaming/vscommon/vscommon.pri +++ b/app/videostreaming/vscommon/vscommon.pri @@ -20,7 +20,7 @@ windows { $$PWD/decodingstatistcs.cpp \ HEADERS += \ - $$PWD/nalu/KeyFrameFinder.hpp \ + $$PWD/nalu/CodecConfigFinder.hpp \ $$PWD/nalu/NALUnitType.hpp \ $$PWD/rtp/ParseRTP.h \ $$PWD/rtp/RTP.hpp \ @@ -31,5 +31,9 @@ windows { } HEADERS += \ - $$PWD/ExternalDecodeService.hpp + $$PWD/ExternalDecodeService.hpp \ + $$PWD/custom/rawreceiver.h + +SOURCES += \ + $$PWD/custom/rawreceiver.cpp diff --git a/lib/mavlink-headers b/lib/mavlink-headers index 48c17d748..a34e186e2 160000 --- a/lib/mavlink-headers +++ b/lib/mavlink-headers @@ -1 +1 @@ -Subproject commit 48c17d748f611d3155dcb98d6d1d7bf121fbd079 +Subproject commit a34e186e2ec6c6a2add840254691a6719606e189 diff --git a/qml/main.qml b/qml/main.qml index e676d0d0e..8ba46dc81 100755 --- a/qml/main.qml +++ b/qml/main.qml @@ -152,26 +152,10 @@ ApplicationWindow { _qopenhd.disable_service_and_quit() } } + AnyParamBusyIndicator{ + z: 10 + } - /*Rectangle { - width: 800 - height: 600 - color: "red" - z: 1 - - MediaPlayer { - id: player - source: "gst-pipeline: videotestsrc ! videoconvert ! qtvideosink" - //source: "gst-pipeline: udpsrc port=5600 caps = \"application/x-rtp, media=(string)video, encoding-name=(string)H264, payload=(int)96\" ! rtph264depay ! decodebin ! qtvideosink" - autoPlay: true - } - - VideoOutput { - id: videoOutput - source: player - anchors.fill: parent - } - }*/ Component.onCompleted: { console.log("Completed"); hudOverlayGrid.regain_focus() diff --git a/qml/qml.qrc b/qml/qml.qrc index f3907ee16..24739dd8d 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -1,33 +1,33 @@ - + ../translations/QOpenHD_en.qm resources/cursors/arrow_512_green.png resources/cursors/arrow_512_transparent.png resources/cursors/arrow_512_white.png resources/cursors/hand_white.png - + ../translations/QOpenHD_de.qm - + ../translations/QOpenHD_ru.qm - + ../translations/QOpenHD_es.qm - + ../translations/QOpenHD_fr.qm - + ../translations/QOpenHD_nl.qm - + ../translations/QOpenHD_ro.qm - + ../translations/QOpenHD_it.qm - + ../translations/QOpenHD_zh.qm @@ -143,7 +143,6 @@ ui/widgets/ImuTempWidget.qml ui/widgets/FlightMahKmWidget.qml ui/widgets/MissionWidget.qml - ui/widgets/Sidebar.qml resources/osdicons.ttf resources/materialdesignicons-webfont.ttf resources/ic128.png @@ -266,5 +265,38 @@ ui/elements/ButtonIconConnect.qml ui/elements/SmallHeaderInfoRow.qml ui/elements/SettingsCategory.qml + ui/elements/BaseHeaderItem2.qml + ui/configpopup/openhd_settings/ChooseCameraDialoque.qml + ui/elements/SettingBaseElement2.qml + ui/configpopup/openhd_settings/ChooseResolutionDialoque.qml + ui/elements/SimpleAdvancedChoiceElement.qml + ui/configpopup/openhd_settings/PopupBigGeneric.qml + ui/widgets/SOCStatusWidgetGeneric.qml + ui/elements/BigClickableText.qml + ui/configpopup/openhd_settings/MavlinkParamValueEditElement.qml + ui/elements/ButtonIconGear.qml + ui/widgets/DevStreamingInfo.qml + ui/widgets/DevStreamingInfoText.qml + ui/AnyParamBusyIndicator.qml + ui/sidebar/SideBarMain.qml + ui/sidebar/TriangleButton.qml + ui/sidebar/MavlinkSwitch.qml + ui/sidebar/InfoElement.qml + ui/sidebar/GoBackElement.qml + ui/sidebar/EditRateElement.qml + ui/sidebar/EditFrequencyElement.qml + ui/sidebar/EditChannelWidthElement.qml + ui/sidebar/BaseJoyEditElement.qml + ui/sidebar/MavlinkChoiceElement.qml + ui/sidebar/SideBarBasePanel.qml + ui/sidebar/Panel1Link.qml + ui/sidebar/Panel5RC.qml + ui/sidebar/Panel2Video.qml + ui/sidebar/Panel3Camera.qml + ui/sidebar/Panel4Recording.qml + ui/sidebar/Panel6Misc.qml + ui/sidebar/Panel7Status.qml + ui/sidebar/SidebarStackButton.qml + ui/sidebar/RCChannelView.qml diff --git a/qml/ui/AnyParamBusyIndicator.qml b/qml/ui/AnyParamBusyIndicator.qml new file mode 100644 index 000000000..6ced62250 --- /dev/null +++ b/qml/ui/AnyParamBusyIndicator.qml @@ -0,0 +1,76 @@ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.0 +import QtGraphicalEffects 1.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +Item{ + width: parent.width + height: parent.height + property int busy_indicator_size: 120; + property int text_font_pixel_size: 18 + + // This indicates a busy param + BusyIndicator{ + id: any_param_busy_indiscator + width: busy_indicator_size + height: busy_indicator_size + anchors.centerIn: parent + running: _airCameraSettingsModel.ui_is_busy || _airCameraSettingsModel2.ui_is_busy || _ohdSystemAirSettingsModel.ui_is_busy || _ohdSystemGroundSettings.ui_is_busy || + _qopenhd.is_busy + //visible: _xParamUI.is_busy + } + Text{ + id: busy_description + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: any_param_busy_indiscator.bottom + text: { + if(_qopenhd.is_busy){ + return _qopenhd.busy_reason; + } + var busy_param="ERROR" + if(_airCameraSettingsModel.ui_is_busy){ + busy_param="CAM1"; + }else if(_airCameraSettingsModel2.ui_is_busy){ + busy_param="CAM2"; + }else if(_ohdSystemAirSettingsModel.ui_is_busy){ + busy_param="AIR"; + }else if(_ohdSystemGroundSettings.ui_is_busy){ + busy_param="GND"; + } + return busy_param+" PARAM BUSY ..."; + } + visible: any_param_busy_indiscator.running + font.pixelSize: text_font_pixel_size + //font.family: settings.font_text + //style: Text.Outline + //styleColor: "green"//settings.color_glow + color: "green" + smooth: true + } + // This indicates a busy camera + BusyIndicator{ + id: cam_busy_indicator + width: busy_indicator_size + height: busy_indicator_size + anchors.centerIn: parent + running: !any_param_busy_indiscator.running && (_cameraStreamModelPrimary.camera_status>0 && _cameraStreamModelPrimary.camera_status==2 && _ohdSystemAir.is_alive) && !settings_panel.visible + } + Text{ + id: cam_busy_description + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: cam_busy_indicator.bottom + text: "Restarting Camera ..." + visible: cam_busy_indicator.running + font.pixelSize: text_font_pixel_size + color: "green" + smooth: true + } +} + + diff --git a/qml/ui/HUDOverlayGrid.qml b/qml/ui/HUDOverlayGrid.qml index 638e92a5d..0146b033e 100644 --- a/qml/ui/HUDOverlayGrid.qml +++ b/qml/ui/HUDOverlayGrid.qml @@ -12,9 +12,12 @@ import "./widgets" import "./widgets/map" import "../resources" as Resources import "./elements" +import "./sidebar" import "../video" +import "sidebar" + Item { id: hudOverlayGrid focus: true @@ -44,6 +47,7 @@ Item { hudOverlayGrid.focus = true; // Receive key events hudOverlayGrid.enabled =true; + hudOverlayGrid.visible =true; // argh actual_hud_elements.visible=true; } @@ -201,7 +205,14 @@ Item { Keys.onPressed: (event)=> { console.log("HUDOverlayGrid::Key was pressed:"+event); - if (event.key == Qt.Key_Return) { + if(event.key==Qt.Key_Left || event.key == Qt.Key_Right || event.key == Qt.Key_Up || event.key == Qt.Key_Down){ + // If the user presses any navigation key, we open up the sidebar and hand over the inputs to it + if(!sidebar.m_extra_is_visible){ + sidebar.open_and_take_control(true); + event.accepted=true; + } + } + /*if (event.key == Qt.Key_Return) { //console.log("enter was pressed"); event.accepted = true; dummy_joystick_enter() @@ -221,7 +232,7 @@ Item { //console.log("down was pressed") event.accepted=true; dummy_joystick_down() - } + }*/ } Image { @@ -264,6 +275,7 @@ Item { id: actual_hud_elements width: parent.width height: parent.height + visible: !quickPanel.visible // By default on top row // -------------------------------------------------------------------------- @@ -296,10 +308,6 @@ Item { // TODO SORT ME // + 0% cpu - MessageHUD { - id: messageHUD - } - GroundPowerWidget { id: groundPowerWidget } @@ -472,9 +480,13 @@ Item { id: uavtimewidget } - Sidebar{ + SideBarMain{ id: sidebar } + + MessageHUD { + id: messageHUD + } } // Extra element - allows customizing the OSD color(s) and more @@ -496,7 +508,9 @@ Item { Label{ text: "JOSTICK NAVIGATION ENABLED" - anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 visible: m_keyboard_navigation_active // style color: settings.color_text @@ -506,6 +520,7 @@ Item { wrapMode: Text.NoWrap style: Text.Outline styleColor: settings.color_glow + height: 50 } // Shows center while dragging widgets Rectangle{ @@ -524,6 +539,10 @@ Item { anchors.left: parent.left visible: m_show_vertical_center_indicator } + + DevStreamingInfo{ + + } } diff --git a/qml/ui/configpopup/ConfigPopup.qml b/qml/ui/configpopup/ConfigPopup.qml index 5a8f6956b..de73ea75f 100644 --- a/qml/ui/configpopup/ConfigPopup.qml +++ b/qml/ui/configpopup/ConfigPopup.qml @@ -16,6 +16,7 @@ import "openhd_settings" import "rc" import "status" + // Contains the selector on the left and a stack view for the panels on the right Rectangle { id: settings_form @@ -57,6 +58,10 @@ Rectangle { ohdSettingsPanel.user_quidance_animate_channel_scan(); } + function gain_focus(){ + sidebar.focus=true; + } + /*Keys.onPressed: (event)=> { console.log("ConfigPopup Key was pressed:"+event); if (event.key == Qt.Key_Return) { @@ -150,6 +155,12 @@ Rectangle { }else if(event.key == Qt.Key_Left){ close_all() }else if(event.key == Qt.Key_Right){ + // Right now we only have joystick navigation for the QUICK panel + /*if(mainStackLayout.currentIndex==0){ + // hand over navigation to the panel + quickPanel.gain_focus(); + return; + }*/ //mainStackLayout.childAt(mainStackLayout.currentIndex).focus=true _qopenhd.show_toast("No joystick navigation for this panel"); } @@ -162,6 +173,15 @@ Rectangle { id: navigation_buttons_column width: parent.width anchors.top: parent.top + + // + /*ConfigPopupSidebarButton{ + id: quick + m_icon_text: "\uf0fb" + m_description_text: "Quick" + m_selection_index: 0 + }*/ + // Status ConfigPopupSidebarButton{ id: power diff --git a/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml b/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml index f9413ac7a..c88657073 100644 --- a/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml +++ b/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml @@ -85,27 +85,14 @@ Rectangle { Layout.leftMargin: 12 } // air - Text { - id: test2 - text: qsTr("video0 FEC encode: " + _cameraStreamModelPrimary.curr_fec_encode_time_avg_min_max) - Layout.leftMargin: 12 - } - Text { - id: testX - text: qsTr("video0 TX delay: " + _cameraStreamModelPrimary.curr_time_until_tx_min_max_avg) - Layout.leftMargin: 12 - } - Text { - id: test4 - text: qsTr("video0 FEC block length: " + _cameraStreamModelPrimary.curr_fec_block_length_min_max_avg) - Layout.leftMargin: 12 - } - // ground - Text { - id: test3 - text: qsTr("video0 FEC decode: " + _cameraStreamModelPrimary.curr_fec_decode_time_avg_min_max) - Layout.leftMargin: 12 + Button{ + height: 23 + text: "SHOW DEV OVERLAY" + onClicked: { + settings.show_dev_stats_overlay=!settings.show_dev_stats_overlay + } } + Button { height: 24 text: "Restart local OHD service" diff --git a/qml/ui/configpopup/openhd_settings/ChooseCameraDialoque.qml b/qml/ui/configpopup/openhd_settings/ChooseCameraDialoque.qml new file mode 100644 index 000000000..8228696c0 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/ChooseCameraDialoque.qml @@ -0,0 +1,354 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// Dialoque to select a camera +Card { + id: dialoque_choose_camera + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: "Camera Selection" + cardNameColor: "black" + visible: false + + // Supported platform types: + readonly property int mPLATFORM_TYPE_RPI:0; + readonly property int mPLATFORM_TYPE_ROCK:1; + readonly property int mPLATFORM_TYPE_X20:2; + readonly property int mPLATFORM_TYPE_X86:3; + property int m_platform_type: 0; + + // Secondary camera only supports a few options - we can omit + // the vendor selection + property bool m_is_for_secondary_camera: false + property var m_cam_model : m_is_for_secondary_camera ? _cameraStreamModelSecondary : _cameraStreamModelPrimary; + + property int m_user_selected_camera_type: -100; + + function close(){ + visible=false; + enabled=false; + } + + // Returns true if we have a 'NICE UI' for this platform type + function set_ohd_platform_type(){ + const ohd_platform_type=_ohdSystemAir.ohd_platform_type; + console.log("Platform:"+ohd_platform_type) + if(ohd_platform_type>=10 && ohd_platform_type<20){ + m_platform_type=mPLATFORM_TYPE_RPI; + return true; + } + if(ohd_platform_type>=20 && ohd_platform_type<30){ + m_platform_type=mPLATFORM_TYPE_ROCK; + return true; + } + if(ohd_platform_type==1){ + m_platform_type=mPLATFORM_TYPE_X86; + return true; + } + if(ohd_platform_type==30){ + m_platform_type=mPLATFORM_TYPE_X20; + return true; + } + return false; + } + + function get_platform_name(){ + if(m_platform_type==mPLATFORM_TYPE_RPI){ + return "RPI"; + }else if(m_platform_type==mPLATFORM_TYPE_ROCK){ + return "ROCK"; + }else if(m_platform_type==mPLATFORM_TYPE_X20){ + return "X20"; + } + return "X86"; + } + + // For debugging + function on_user_clicked_save(){ + var openhd_core_cam_type=combobox_cameras.model.get(combobox_cameras.currentIndex).value; + console.log("Current openhd_core_cam_type: "+openhd_core_cam_type) + + var success= false; + if(m_is_for_secondary_camera){ + success=_airCameraSettingsModel2.try_update_parameter_int("CAMERA_TYPE",openhd_core_cam_type)==""; + }else{ + success=_airCameraSettingsModel.try_update_parameter_int("CAMERA_TYPE",openhd_core_cam_type)==""; + } + if(!success){ + _qopenhd.show_toast("Cannot save, please try again"); + }else{ + _messageBoxInstance.set_text_and_show("Saved, rebooting air unit",10); + close(); + } + } + + // The manufacturer model(s) don't need a value + ListModel{ + id: model_manufacturers_rpi + ListElement {title: "ARDUCAM"} + ListElement {title: "VEYE"} + ListElement {title: "RPI FOUNDATION"} + ListElement {title: "GEEKWORM"} + ListElement {title: "USB"} + ListElement {title: "DEV/DEBUG"} + } + ListModel{ + id: model_manufacturers_rock + ListElement {title: "ARDUCAM"} + ListElement {title: "RADXA"} + ListElement {title: "USB"} + ListElement {title: "DEV/DEBUG"} + } + ListModel{ + id: model_manufacturers_x20 + ListElement {title: "RUNCAM"} + } + ListModel{ // X86 doesn't reall have manufacturers + id: model_manufacturers_x86 + ListElement {title: "USB"} + ListElement {title: "DEV/DEBUG"} + } + ListModel{ // Secondary camera doesn't reall have manufacturers + id: model_manufacturers_secondary_cam + ListElement {title: "SECONDARY CAM"} + } + + // Actual CAMERA model(s) + // Value needs to map to the corrseponding openhd camera type ! + ListModel{ + id: rpi_arducam_cameras + ListElement {title: "SKYMASTERHDR"; value: 40} + ListElement {title: "SKYVISIONPRO"; value: 41} + ListElement {title: "IMX477m"; value: 42} + ListElement {title: "IMX462"; value: 43} + ListElement {title: "IMX327"; value: 44} + ListElement {title: "IMX290"; value: 45} + ListElement {title: "IMX462_LOWLIGHT_MINI"; value: 46} + } + ListModel{ + id: rpi_veye_cameras + ListElement {title: "2MP"; value: 60} + ListElement {title: "CSIMX307"; value: 61} + ListElement {title: "CSSC132"; value: 62} + ListElement {title: "MVCAM"; value: 63} + } + ListModel{ + id: rpi_rpif_cameras + ListElement {title: "V1 OV5647"; value: 30} + ListElement {title: "V2 IMX219"; value: 31} + ListElement {title: "V3 IMX708"; value: 32} + ListElement {title: "HQ IMX477"; value: 33} + } + ListModel{ + id: rpi_geekworm_cameras + ListElement {title: "HDMI to CSI"; value: 20} + } + ListModel{ + id: cameras_usb + ListElement {title: "INFIRAY USB"; value: 11} + ListElement {title: "EXP USB GENERIC"; value: 10} + } + ListModel{ + id: cameras_debug + ListElement {title: "Dummy (debug)"; value: 0} + ListElement {title: "External (DEV)"; value: 2} + ListElement {title: "External IP (DEV)"; value: 3} + ListElement {title: "DEV Filecamera"; value: 4} + } + ListModel{ + id: x20_runcam_cameras + ListElement {title: "RUNCAM NANO"; value: 70} + } + ListModel{ + id: rock_arducam_cameras + ListElement {title: "ARDUCAM ROCK 0"; value: 0} + ListElement {title: "ARDUCAM ROCK 1"; value: 1} + } + ListModel{ + id: rock_raxca_cameras + ListElement {title: "HDMI IN"; value: 0} + } + ListModel{ + id: generic_secondary_camera_options + ListElement {title: "DISABLED (Default)"; value: 255} + ListElement {title: "INFIRAY USB"; value: 11} + ListElement {title: "EXP USB GENERIC"; value: 10} + ListElement {title: "Dummy (debug)"; value: 0} + ListElement {title: "External (DEV)"; value: 2} + ListElement {title: "External IP (DEV)"; value: 3} + } + + + function initialize_and_show(){ + console.log("Choose camera dialoque opened with "+get_platform_name()); + comboBoxManufacturers.model=get_manufacturers_model(); + var current_openhd_core_cam_type=m_cam_model.camera_type; + // Try and find the index (2D) of the currently active camera + var found=false; + for(var i=0;i=0 && m_user_selected_camera_type!=(m_is_secondary_cam ? _cameraStreamModelSecondary.camera_type : _cameraStreamModelPrimary.camera_type); + } + } + } + } +} + diff --git a/qml/ui/configpopup/openhd_settings/ChooseResolutionDialoque.qml b/qml/ui/configpopup/openhd_settings/ChooseResolutionDialoque.qml new file mode 100644 index 000000000..5b23704fb --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/ChooseResolutionDialoque.qml @@ -0,0 +1,209 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// Dialoque to select a resolution for a camera +Card { + id: dialoque_choose_resolution + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: "Resolution Selection" + cardNameColor: "black" + visible: false + + property string m_current_resolution_fps: "1920x1080@30" + property bool m_is_for_secondary: false + property bool m_textinput_display_text_valid: false; + property string m_default_resolution_fps: "N/A"; + property string m_cam_type_as_string: "N/A"; + + + property bool argh_is_changing_model:false; + ListModel{ + id: resolutions_model + //ListElement {title: "0x0@0 (AUTO)"; value: "0x0@0"} + //ListElement {title: "480p@30fps (4:3)"; value: "640x480@30"} + ListElement {title: "480p@60fps (4:3)"; value: "640x480@60"} + //ListElement {title: "480p@60fps (16:9)"; value: "848x480@60"} + //ListElement {title: "720p@49fps (16:9)"; value: "1280x720@49"} + //ListElement {title: "720p@60fps (4:3)"; value: "960x720@60"} + ListElement {title: "720p@60fps (16:9)"; value: "1280x720@60"} + ListElement {title: "1080p@30fps (16:9)"; value: "1920x1080@30"} + //ListElement {title: "1080p@30fps (4:3)"; value: "1440x1080@30"} + //ListElement {title: "1080p@49fps (4:3)"; value: "1440x1080@49"} + } + + function close(){ + visible=false; + enabled=false; + } + + property var m_stream_model : m_is_for_secondary ? _cameraStreamModelSecondary : _cameraStreamModelPrimary; + + + function update_resolutions_model(){ + var supported_resolutions=m_stream_model.get_supported_resolutions(); + resolutions_model.clear() + for(var i=0; i=0){ + combobox_resolutions.currentIndex=curr_res_index; + advanced_checkbox.checked=false; + }else{ + console.log("Current resolution not found in model"); + advanced_checkbox.checked=true; + combobox_resolutions.currentIndex=0; + } + text_input_cameras.text=m_current_resolution_fps; + argh_is_changing_model=false; + + } + + function get_user_selected_resolution(){ + if(advanced_checkbox.checked){ + return text_input_cameras.displayText; + } + return combobox_resolutions.model.get(combobox_resolutions.currentIndex).value; + } + + + cardBody: Column{ + spacing: 10 + padding: 10 + Text{ + width: 200 + text: "Your Camera: "+m_cam_type_as_string; + } + Text{ + width: 200 + text: "Default Resolution: "+m_default_resolution_fps + } + ComboBox { + width: 300 + id: combobox_resolutions + model: resolutions_model + textRole: "title" + implicitWidth: elementComboBoxWidth + currentIndex: 0 + visible: !advanced_checkbox.checked + } + Row{ + spacing: 10 + padding: 0 + TextInput{ + //width: 300 + id: text_input_cameras + text: "WIDTHxHEIGHT@FPS" + visible: advanced_checkbox.checked + maximumLength: 13 //3840x2160@200 | 13 chars + font.pixelSize: 20 + padding: 2 + topPadding: 6 + Rectangle{ + implicitWidth: parent.width + implicitHeight: 2 + color: "black" + anchors.left: parent.left + anchors.bottom: parent.bottom + } + color: m_textinput_display_text_valid ? "black" : "red"; + onDisplayTextChanged: { + if(_cameraStreamModelPrimary.is_valid_resolution_fps_string(displayText)){ + m_textinput_display_text_valid=true; + }else{ + m_textinput_display_text_valid=false; + } + } + } + // padding + Item { + width: 10 + } + ButtonIconInfo{ + visible: text_input_cameras.visible + onClicked: { + _messageBoxInstance.set_text_and_show("You can enter any (UNCHECKED !) resolution@fps you like here.") + } + } + } + // Toggles between text and drop down input + CheckBox{ + id: advanced_checkbox + text: "experiment" + } + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + RowLayout{ + anchors.fill: parent + + Button{ + Layout.preferredWidth: 150 + text: "CANCEL" + onPressed: { + close(); + } + } + Button{ + Layout.preferredWidth: 150 + text: "SAVE" + onPressed: { + var success=false; + const selected_res_fps=get_user_selected_resolution(); + console.log("Setting "+(m_is_for_secondary ? "CAM2" : "CAM1")+" to {"+selected_res_fps+"}"); + if(m_is_for_secondary){ + success=_airCameraSettingsModel2.try_update_parameter_string("RESOLUTION_FPS",selected_res_fps)==="" + }else{ + success=_airCameraSettingsModel.try_update_parameter_string("RESOLUTION_FPS",selected_res_fps)==="" + } + if(success){ + _messageBoxInstance.set_text_and_show("Saved "+selected_res_fps); + close(); + }else{ + _messageBoxInstance.set_text_and_show("Failed,please try again"); + } + } + enabled: { + if(argh_is_changing_model){ + return false; + } + const selected_res_fps=get_user_selected_resolution(); + return _cameraStreamModelPrimary.is_valid_resolution_fps_string(selected_res_fps) && selected_res_fps!=m_current_resolution_fps; + } + } + } + } +} + diff --git a/qml/ui/configpopup/openhd_settings/LinkQuickPanel.qml b/qml/ui/configpopup/openhd_settings/LinkQuickPanel.qml index 6cae39cee..01fbf0fd6 100644 --- a/qml/ui/configpopup/openhd_settings/LinkQuickPanel.qml +++ b/qml/ui/configpopup/openhd_settings/LinkQuickPanel.qml @@ -146,6 +146,7 @@ Rectangle{ SettingsCategory{ m_description: "FREQUENCY / TOOLKIT" spacing: 1 + m_hide_elements: false; Row{ anchors.horizontalCenter: parent.horizontalCenter @@ -355,7 +356,8 @@ Rectangle{ } SettingsCategory{ - m_description: "TX POWER" + m_description: "TX POWER"; + m_hide_elements: false; Row{ anchors.horizontalCenter: parent.horizontalCenter Text{ @@ -370,6 +372,7 @@ Rectangle{ Button{ text: "EDIT" enabled: _ohdSystemAir.is_alive + //enabled: true onClicked: { close_all_dialoques(); popup_change_tx_power.m_is_air=true; @@ -388,6 +391,7 @@ Rectangle{ Button{ text: "EDIT" enabled: _ohdSystemGround.is_alive + //enabled: true onClicked: { close_all_dialoques(); popup_change_tx_power.m_is_air=false; @@ -400,7 +404,7 @@ Rectangle{ SettingsCategory{ m_description: "ADVANCED (STBC,LDPC)" - + m_hide_elements: false; Row{ anchors.horizontalCenter: parent.horizontalCenter Text{ @@ -457,6 +461,5 @@ Rectangle{ DialoqueFreqChangeAirGnd{ id: dialoqueFreqChangeAirGnd } - } diff --git a/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml b/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml index f1cbf6add..f28c3cdb7 100644 --- a/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml +++ b/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml @@ -25,7 +25,9 @@ Rectangle{ width: total_width height: parent.height anchors.right: parent.right; + //anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top + //anchors.centerIn: parent //anchors.topMargin: -15 color: "#333c4c" @@ -58,6 +60,18 @@ Rectangle{ // disable some checking we do for the user, should be used only in really rare cases property bool enableAdvanced: false + // For getting the result of a update operation + property int m_update_count: instanceMavlinkSettingsModel.update_count + + onM_update_countChanged: { + console.log("Update count changed, "+instanceMavlinkSettingsModel.last_updated_param_id+" "+instanceMavlinkSettingsModel.last_updated_param_success); + if(instanceMavlinkSettingsModel.last_updated_param_id==parameterId && instanceMavlinkSettingsModel.last_updated_param_success && parameterEditor.visible){ + // Success updating, close the editor + parameterEditor.visible=false + //_qopenhd.show_toast("Set "+parameterId+" success"); + } + } + function holds_int_value(){ return paramValueType==0; @@ -132,20 +146,9 @@ Rectangle{ } setup_spin_box_int_param() setup_text_input_string_param() - set_description_enabled(false) parameterEditor.visible=true } - function show_description(show_description){ - set_description_enabled(true) - } - function set_description_enabled(enabled){ - if(enabled){ - descriptionMessageBox.visible=true - }else{ - descriptionMessageBox.visible=false; - } - } // For int params we use the spin box function setup_spin_box_int_param(){ @@ -295,23 +298,6 @@ Rectangle{ horizontalAlignment: Qt.AlignHCenter Layout.alignment: Qt.AlignCenter } - Button { - width: 300 - height:customHeight - id: buttonOpenDescription - flat: true - //text: qsTr("Description: "+shortParamDescription) - text: qsTr("Description") - //horizontalAlignment: Qt.AlignCenter - //horizontalAlignment: Text.AlignHCenter - Layout.alignment: Qt.AlignCenter - onClicked: show_description() - //palette { - // button: "green" - //} - Material.background:Material.LightBlue - visible: m_has_param_description - } Text{ width: 300 height:customHeight @@ -406,6 +392,17 @@ Rectangle{ color: "white" } // Type string only end -------------------------- + /*CheckBox{ + id: advanced_checkbox + text: "experiment" + Layout.alignment: Qt.AlignHCenter + onClicked: { + enableAdvanced= !enableAdvanced + // Completely re-fresh the UI - the user has now more direct access to the parameter(s) + setup_spin_box_int_param() + setup_text_input_string_param() + } + }*/ //Value edit part end RowLayout{ @@ -424,6 +421,7 @@ Rectangle{ setup_spin_box_int_param() setup_text_input_string_param() } + enabled: !instanceMavlinkSettingsModel.ui_is_busy } Button{ text: "Save" @@ -441,13 +439,14 @@ Rectangle{ //var value_int = parseInt(value_int_as_string) //console.log("UI set int:{"+value_int_as_string+"}={"+value_int+"}") console.log("UI set int:{"+value_int+"}") - res=instanceMavlinkSettingsModel.try_update_parameter_int(parameterId,value_int) + //res=instanceMavlinkSettingsModel.try_update_parameter_int(parameterId,value_int) + instanceMavlinkSettingsModel.try_set_param_int_async(parameterId,value_int,true); }else{ var value_string=textInputParamtypeString.text console.log("UI set string:{"+value_string+"}") - res=instanceMavlinkSettingsModel.try_update_parameter_string(parameterId,value_string); + instanceMavlinkSettingsModel.try_set_param_string_async(parameterId,value_string,true); } - if(res===""){ + /*if(res===""){ // Update success (no error code) if(instanceMavlinkSettingsModel.get_param_requires_manual_reboot(parameterId)){ _messageBoxInstance.set_text_and_show("Please reboot to apply") @@ -458,71 +457,9 @@ Rectangle{ }else{ console.log("Update failed") _qopenhd.show_toast(res,true); - } - set_description_enabled(false) - } - } - } - } - - // Dirty, popup card that contains the param description - // TODO: FUCKING ANNOYING QT UI FIXME - property int m_description_message_box_width:320 - property int m_description_message_box_height:320 - property int m_description_message_box_footer_width:140 - property int m_description_message_box_footer_height:48 - Card { - id: descriptionMessageBox - width: m_description_message_box_width - height: m_description_message_box_height - z: 5.0 - anchors.centerIn: parent - cardName: qsTr("Param description") - cardNameColor: "black" - visible: false - cardBody: Column { - width: m_description_message_box_width - height: m_description_message_box_height-m_description_message_box_footer_height - ScrollView{ - //anchors.fill: parent - width: m_description_message_box_width-20 - height: m_description_message_box_height-48-48 - contentWidth: availableWidth - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AlwaysOn - // allow dragging without using the vertical scroll bar - ScrollBar.vertical.interactive: true - Text { - id: descriptionMessageBox_text - text: shortParamDescription - width: parent.width - height:parent.height - leftPadding: 12 - rightPadding: 12 - wrapMode: Text.WordWrap - font.pixelSize: 15 - } - } - } - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - id: descriptionMessageBox_button - height: 48 - width: 140 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Green - highlighted: true - text: qsTr("Okay") - onPressed: { - descriptionMessageBox.visible=false + }*/ } + enabled: !instanceMavlinkSettingsModel.ui_is_busy } } } diff --git a/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml b/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml index 90dfdf41b..f2e0960e5 100644 --- a/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml +++ b/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml @@ -23,8 +23,11 @@ Rectangle { property var m_instanceMavlinkSettingsModel: _ohdSystemGroundSettings // figure out if the system is alive property var m_instanceCheckIsAvlie: _ohdSystemGround + // unly used for camera 1 / camera2 + property bool m_is_secondary_cam: false property string m_name: "undefined" + property bool m_requires_alive_air: false //color: "red" //color: "transparent" @@ -32,79 +35,115 @@ Rectangle { property int m_progress_perc : m_instanceMavlinkSettingsModel.curr_get_all_progress_perc; - SimpleProgressBar{ - id: fetch_all_progress - width: parent.width - height: 15 - anchors.top: parent.top - visible: m_progress_perc>=0 && m_progress_perc<=100 - impl_curr_progress_perc: m_progress_perc - impl_curr_color: "#333c4c" + property bool m_any_param_eitor_opened: parameterEditor.visible || dialoque_choose_camera.visible || dialoque_choose_resolution.visible; + property bool m_any_param_busy: _ohdSystemGroundSettings.ui_is_busy || _ohdSystemAirSettingsModel.ui_is_busy || _airCameraSettingsModel.ui_is_busy || + _airCameraSettingsModel2.ui_is_busy; + + onVisibleChanged: { + if(visible){ + if(!m_instanceCheckIsAvlie.is_alive){ + var message= m_requires_alive_air ? "AIR not alive" : "GND not alive"; + message+=", parameters unavailable"; + _qopenhd.show_toast(message); + }else{ + if(! m_instanceMavlinkSettingsModel.has_params_fetched){ + m_instanceMavlinkSettingsModel.try_refetch_all_parameters_async() + } + } + } } - RowLayout{ - id: upper_action_row - width: parent.width - height: 48 - anchors.top: fetch_all_progress.bottom - anchors.topMargin: 1 - anchors.left: parent.left - anchors.leftMargin: 12 - ButtonIconWarning{ - onClicked: { - _messageBoxInstance.set_text_and_show(""+m_name+ " not alive, parameters unavailable. Please check status view."); + function open_apropiate_param_editor(model){ + // For a few params we have extra ui elements, otherwise, use the generic param editor + var init_special_ui_element_success=false; + if(model.unique_id==="CAMERA_TYPE"){ + dialoque_choose_camera.m_is_for_secondary_camera=m_is_secondary_cam; + if(dialoque_choose_camera.set_ohd_platform_type()){ + dialoque_choose_camera.initialize_and_show() + init_special_ui_element_success=true; } - visible: !m_instanceCheckIsAvlie.is_alive + }else if(model.unique_id==="RESOLUTION_FPS"){ + dialoque_choose_resolution.m_current_resolution_fps=model.value; + dialoque_choose_resolution.m_is_for_secondary=m_is_secondary_cam; + dialoque_choose_resolution.initialize_and_show(); + init_special_ui_element_success=true; } + if(!init_special_ui_element_success){ + // generic editor + parameterEditor.setup_for_parameter(model.unique_id,model) + } + } + + Rectangle{ + id: upper_action_row + width: parent.width + height: rowHeight;//*2 / 3; + color: "#8cbfd7f3" Button { - text:"REFETCH "+m_name - visible: m_instanceCheckIsAvlie.is_alive + text: m_instanceCheckIsAvlie.is_alive ? qsTr("\uf2f1") : qsTr("\uf127"); + font.family: "Font Awesome 5 Free" + anchors.left: parent.left + anchors.leftMargin: 10 onClicked: { parameterEditor.visible=false m_instanceMavlinkSettingsModel.try_refetch_all_parameters_async() } + anchors.verticalCenter: parent.verticalCenter + enabled: m_instanceCheckIsAvlie.is_alive && (!m_any_param_busy) } - Button { - text:"INFO" - Material.background:Material.LightBlue - visible: m_instanceCheckIsAvlie.is_alive - onClicked: { - var text="Refresh your complete parameter set by fetching it from the openhd air/ground unit." - _messageBoxInstance.set_text_and_show(text) - } + Text{ + text: "FULL "+m_name+" PARAM SET" + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + font.pixelSize: 13 } - Switch{ + CheckBox{ + anchors.right: down_button.left + anchors.rightMargin: 3 checked: settings.screen_settings_openhd_parameters_transparent onCheckedChanged: settings.screen_settings_openhd_parameters_transparent = checked + anchors.verticalCenter: parent.verticalCenter } Button{ - text:"DOWN" + id: up_button + anchors.right: down_button.left + anchors.leftMargin: 3 + text: "\uf0d8" //UP + font.family: "Font Awesome 5 Free"; + anchors.verticalCenter: parent.verticalCenter onClicked: { - paramListScrollView.ScrollBar.vertical.position += 0.1 + paramListScrollView.ScrollBar.vertical.position -= 0.1 } } Button{ - text:"UP" + id: down_button + font.family: "Font Awesome 5 Free"; + text: "\uf0d7" //DOWN + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter onClicked: { - paramListScrollView.ScrollBar.vertical.position -= 0.1 + paramListScrollView.ScrollBar.vertical.position += 0.1 } } - Item{ // Filler - Layout.fillWidth: true - Layout.fillHeight: true + Rectangle{ + width: parent.width + height: 2 + color: "black" + anchors.bottom: parent.bottom + anchors.left: parent.left + } + SimpleProgressBar{ + width: parent.width + height: 15 + anchors.top: parent.top + visible: m_progress_perc>=0 && m_progress_perc<100 + impl_curr_progress_perc: m_progress_perc + impl_curr_color: "#333c4c" } } - /*ProgressBar{ - width: parent.width - height: 50 - from: 0 - to: 100 - value: m_progress_perc - //visible: m_progress_perc>=0 - anchors.top: fetchAllButtonId.top - }*/ - Component { id: delegateMavlinkSettingsValue @@ -112,24 +151,17 @@ Rectangle { //color: (index % 2 == 0) ? "#8cbfd7f3" : "#00000000" //color: "transparent" color: settings.screen_settings_openhd_parameters_transparent ? "transparent" : ((index % 2 == 0) ? "#8cbfd7f3" : "#00000000") - //color: "green" - //implicitHeight: elementsRow.implicitHeight - //implicitWidth: elementsRow.implicitWidth - //height: 64 - //width: 200 height: 64 width: listView.width-12 Row { - id: elementsRow - //anchors.fill: parent - spacing: 5 - //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" - height: 64 + spacing: 30 + height: parent.height + width: parent.width anchors.left: parent.left anchors.leftMargin: 12 Text { anchors.verticalCenter: parent.verticalCenter - width:160 + width:180 text: model.unique_id font.bold: true font.pixelSize: 14 @@ -137,8 +169,8 @@ Rectangle { style: settings.screen_settings_openhd_parameters_transparent ? Text.Outline : Text.Normal styleColor: settings.color_glow } - Text { - width:160 + /*Text { + width:180 text: model.extraValue font.bold: true font.pixelSize: 14 @@ -146,26 +178,12 @@ Rectangle { color: settings.screen_settings_openhd_parameters_transparent ? settings.color_text : "black" style: settings.screen_settings_openhd_parameters_transparent ? Text.Outline : Text.Normal styleColor: settings.color_glow - } - Button { - anchors.verticalCenter: parent.verticalCenter - text: "EDIT" - onClicked: { - // this initializes and opens up the param editor - parameterEditor.setup_for_parameter(model.unique_id,model) - } - // gray out the button for read-only params - enabled: !model.read_only - } - // Empty item for padding - Item{ - width: 16 - height: parent.height - } - Button { + }*/ + //Button { + ButtonIconInfo{ anchors.verticalCenter: parent.verticalCenter - text: "INFO" - Material.background:Material.LightBlue + //text: "INFO" + //Material.background: Material.LightBlue onClicked: { var text = model.shortDescription if(text==="TODO"){ @@ -177,6 +195,37 @@ Rectangle { _messageBoxInstance.set_text_and_show(text) } } + BigClickableText{ + text: model.extraValue + anchors.verticalCenter: parent.verticalCenter + onClicked: { + open_apropiate_param_editor(model); + } + // gray out the button for read-only params + enabled: !model.read_only && m_instanceCheckIsAvlie.is_alive && (!m_any_param_eitor_opened) && (!m_any_param_busy) + } + /*MavlinkParamValueEditElement{ + m_display_text: model.extraValue + m_is_int: model.valueType===0 + anchors.verticalCenter: parent.verticalCenter + }*/ + ButtonIconGear { + anchors.verticalCenter: parent.verticalCenter + //text: "EDIT" + onClicked: { + open_apropiate_param_editor(model); + } + // gray out the button for read-only params + enabled: !model.read_only && m_instanceCheckIsAvlie.is_alive && (!m_any_param_eitor_opened) && (!m_any_param_busy) + } + ButtonIconWarning{ + id: warning_whitelisted + anchors.verticalCenter: parent.verticalCenter + onClicked: { + _messageBoxInstance.set_text_and_show("This param is whitelisted (You should not edit it from here / editing can break things))") + } + visible: model.whitelisted + } } } } @@ -203,11 +252,47 @@ Rectangle { // Always show the scroll bar (sometimes the interactive might not work) but allow interactive also ScrollBar.vertical.policy: ScrollBar.AlwaysOn ScrollBar.vertical.interactive: true + ListView { id: listView width: parent.width model: m_instanceMavlinkSettingsModel delegate: delegateMavlinkSettingsValue + visible: !please_fetch_item.visible && m_instanceCheckIsAvlie.is_alive + } + } + Item{ + id: please_fetch_item + width: scrollViewRectangle.width + height: scrollViewRectangle.height + visible: !m_instanceMavlinkSettingsModel.has_params_fetched && m_instanceCheckIsAvlie.is_alive + Text{ + anchors.centerIn: parent + text: "Please fetch"; + font.bold: true + } + } + Item{ + id: not_connected_overlay + width: scrollViewRectangle.width + height: scrollViewRectangle.height + visible: !m_instanceCheckIsAvlie.is_alive + /*Rectangle{ + anchors.fill: parent + color: "gray" + opacity: 0.5 + }*/ + Text { + anchors.fill: parent + text: qsTr("\uf127"); + font.family: "Font Awesome 5 Free"; + color: "black" + //fontSizeMode: Text.Fit + //font.pointSize: 100000 + font.pixelSize: 100 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + opacity: 0.5 } } } @@ -220,12 +305,12 @@ Rectangle { instanceMavlinkSettingsModel: m_instanceMavlinkSettingsModel } - BusyIndicator{ - width: 96 - height: 96 - anchors.centerIn: parent - running: m_instanceMavlinkSettingsModel.ui_is_busy - //visible: _xParamUI.is_busy + // For (as of now, 2) Settings we have their own custom UI elements to change them + // (Since they do not really fit into a 'generic fits all' type + ChooseCameraDialoque{ + id: dialoque_choose_camera + } + ChooseResolutionDialoque{ + id: dialoque_choose_resolution } - } diff --git a/qml/ui/configpopup/openhd_settings/MavlinkParamValueEditElement.qml b/qml/ui/configpopup/openhd_settings/MavlinkParamValueEditElement.qml new file mode 100644 index 000000000..7dd5f5e21 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/MavlinkParamValueEditElement.qml @@ -0,0 +1,63 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + + +ComboBox{ + property string m_display_text: "FILL ME" + property string m_param_id: "TODO" + + property var m_settings_model: _ohdSystemGroundSettings + + property bool m_is_int: false; + + displayText: m_display_text + + + onActivated: { + console.log("onActivated:"+currentIndex); + // Check if we have an enum mapping for this param + if(m_is_int){ + + } + } + onM_display_textChanged: { + if(m_display_text=="FILL ME")return + // Check if we have an enum mapping for this param + var has_mapped_enum = false; + var enum_keys; + var enum_values; + if(m_is_int){ + has_mapped_enum = m_settings_model.int_param_has_enum_keys_values(m_param_id); + enum_keys = m_settings_model.int_param_get_enum_keys(m_param_id); + enum_values = m_settings_model.int_param_get_enum_values(m_param_id); + + }else{ + has_mapped_enum = m_settings_model.string_param_has_enum(m_param_id); + enum_keys = m_settings_model.string_param_get_enum_keys(m_param_id); + enum_values = m_settings_model.string_param_get_enum_values(m_param_id); + } + if(has_mapped_enum){ + console.log("Param "+m_param_id+" has enum mapping") + // Check if the current value is inside the enum - otherwise we open the advanced editor + var current_value = m_settings_model.int_par + for(var i=0;i fetched from @property m_short_description +// Optional info popup -> icon visible when @property m_long_description!=="none" +// actuall seting element -> fill with e.g. your QT settings element, e.g. a Switch +Rectangle { + width: parent.width + height: rowHeight + // Item needs to be placed in a Column, in which case columns are colored alternating white - light blue + // This increases readability + color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + + // Set this to the setting(s) short description. + // This value is always shown inside a text view on the left + property string m_short_description: "PLACEHOLDER" + + // Optionally - set this to the setting(s) long descrition. + // If a long description is set, a "info" icon is shown right next + // to the setting(s) short description, and clicking it opens up a popup + // with the long description. + property string m_long_description: "none" + + property string m_value: "value" + + + function has_valid_long_description(){ + return m_long_description !== "none" + } + property bool m_show_edit_button: true + property bool m_enable_edit_button: true + + signal editButtonClicked(); + + + Text { + id: description + text: qsTr(m_short_description) + font.weight: Font.Bold + font.pixelSize: 13 + anchors.leftMargin: 8 + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + width: 224 + height: elementHeight + anchors.left: parent.left + } + + Button { + anchors.verticalCenter: parent.verticalCenter + anchors.left: description.right + //text: "INFO" + text: "\uf05a" + font.family: "Font Awesome 5 Free" + Material.background:Material.LightBlue + visible: has_valid_long_description() + onClicked: { + _messageBoxInstance.set_text_and_show(m_long_description) + } + } + + Text { + id: value + text: qsTr(m_value) + font.weight: Font.Bold + font.pixelSize: 13 + anchors.leftMargin: 8 + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + width: 224 + height: elementHeight + anchors.left: description.right + } + + Button{ + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + text: "EDIT"; + enabled: m_enable_edit_button + visible: m_show_edit_button + onClicked: { + editButtonClicked() + } + } + + /*Rectangle{ + width: parent.width + height: parent.height + opacity: m_use_style_not_alive ? 0.6 : 1 + }*/ + +} diff --git a/qml/ui/elements/SettingsCategory.qml b/qml/ui/elements/SettingsCategory.qml index fd4b08a43..d2dc8ab19 100644 --- a/qml/ui/elements/SettingsCategory.qml +++ b/qml/ui/elements/SettingsCategory.qml @@ -39,7 +39,7 @@ Column { //color: "green" //color: "#8cbfd7f3" color: "#8cbfd7f3" - + //color: "#333c4c" Text { id: description @@ -48,19 +48,15 @@ Column { font.pixelSize: 13 anchors.leftMargin: 8 anchors.rightMargin: 8 - anchors.fill: parent + anchors.centerIn: parent + width: m_description.length> 20 ? 250 : 150 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter + //color: "white" } - MouseArea{ - anchors.fill: description - onClicked: { - m_hide_elements=!m_hide_elements - change_children_visibility() - } - } + Rectangle{ width: parent.width height: 2 @@ -68,6 +64,37 @@ Column { anchors.bottom: parent.bottom anchors.left: parent.left } + Text{ + text: m_hide_elements ? qsTr("\uf0d7") : qsTr("\uf0d8"); + font.family: "Font Awesome 5 Free"; + anchors.right: description.left + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + font.pixelSize: 20 + MouseArea{ + anchors.fill: parent + onClicked: { + m_hide_elements=!m_hide_elements + change_children_visibility() + } + } + //color: "white" + } + MouseArea{ + anchors.fill: parent + onClicked: { + m_hide_elements=!m_hide_elements + change_children_visibility() + } + } + + /*Rectangle{ + implicitWidth: parent.width + implicitHeight: parent.height + color: "transparent" + border.color: "black" + border.width: 2 + }*/ } diff --git a/qml/ui/elements/SimpleAdvancedChoiceElement.qml b/qml/ui/elements/SimpleAdvancedChoiceElement.qml new file mode 100644 index 000000000..6724d21f5 --- /dev/null +++ b/qml/ui/elements/SimpleAdvancedChoiceElement.qml @@ -0,0 +1,39 @@ +import QtQuick 2.0 + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +// Allows the user to switch between simple and advanced view +Rectangle { + width: parent.width + height: rowHeight*2 / 3; + //color: "green" + //color: "#8cbfd7f3" + color: "#8cbfd7f3" + //color: "#333c4c" + + property bool m_is_advanced_selected: false + + Switch{ + text: m_is_advanced_selected ? "ADVANCED" : "SIMPLE" + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + //anchors.left: parent.left + //anchors.leftMargin: 10 + //font.pixelSize: 20 + checked: m_is_advanced_selected + onCheckedChanged: { + m_is_advanced_selected=checked + } + } + + Rectangle{ + width: parent.width + height: 2 + color: "black" + anchors.bottom: parent.bottom + anchors.left: parent.left + } +} diff --git a/qml/ui/sidebar/BaseJoyEditElement.qml b/qml/ui/sidebar/BaseJoyEditElement.qml new file mode 100644 index 000000000..2f227eae9 --- /dev/null +++ b/qml/ui/sidebar/BaseJoyEditElement.qml @@ -0,0 +1,167 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../ui" as Ui +import "../elements" + +// +// Base for an UI element that has the following properties: +// left / right: Increment / Decrement (model) value (If increment / decrement is available) +// top / bottom : Go to next / previous element +// MIDDLE: Show the current value +// +// It looks like this: +// |-------------------------------| +// | TITLE | +// | {ARROW L} {CONTENT} {ARROW R} | +// |------------------------------ | +// +// Joystick navigatable (focus) but also supports touch. +Item{ + id: base_joy_edit_element + width: 320 + height: 50+30 + + property string m_title: "FILL ME" + + property bool m_is_selected: false + + property string m_displayed_value: "VALUE T" + + property bool m_is_enabled: true + + // Set to true if the "select element left" button should be activated + property bool m_button_left_activated: false + // Set to true if the "select element right" button should be activated + property bool m_button_right_activated: false + + // Allows the parent to override the text color + // Right now only used by the change frequency element + property bool override_show_red_text: false + + // Emitted if the button left is clicked + signal choice_left(); + // Emitted if the button right is clicked + signal choice_right(); + + // Emitted if the button up is clicked (navigate to previous eelement) + signal goto_previous(); + signal goto_next(); + + Keys.onPressed: (event)=> { + console.log("BaseJoyElement"+m_title+" key was pressed:"+event); + if(event.key == Qt.Key_Left){ + if(m_button_left_activated){ + choice_left() + }else{ + _qopenhd.show_toast("NOT AVAILABLE"); + } + event.accepted=true; + }else if(event.key == Qt.Key_Right){ + if(m_button_right_activated){ + choice_right() + }else{ + _qopenhd.show_toast("NOT AVAILABLE"); + } + event.accepted=true; + }else if(event.key==Qt.Key_Up){ + goto_previous() + event.accepted=true; + }else if(event.key==Qt.Key_Down){ + goto_next(); + event.accepted=true; + } + } + + function takeover_control(){ + focus=true; + } + + Rectangle{ + width: parent.width + height:parent.height + border.color: "white" + border.width: base_joy_edit_element.focus ? 3 : 0; + color: "#333c4c" + opacity: base_joy_edit_element.focus ? 1.0 : 0.3; + } + + Text { + id: title_str + text: qsTr(m_title) + width: parent.width + height: 30 + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + font.pixelSize: 18 + anchors.top: parent.top + color: "white" + } + + Item{ + id: middle_element_holder + width: parent.width + height: 50 + anchors.top: title_str.bottom + Rectangle{ + id: main_selector + color: "transparent" + width: parent.width /2; + height: parent.height + //border.color: "white" + //border.width: 2 + anchors.centerIn: parent + //Item{ + //id: main_selector + //width: parent.width /2; + //height: parent.height + //anchors.centerIn: parent + Text{ + id: middle_element + width: parent.width + height: parent.height + text: m_displayed_value + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + font.pixelSize: 15 + color: override_show_red_text? "red" : "white" + } + } + TriangleButton{ + m_point_right: false + id: button_left + anchors.right: main_selector.left + height: parent.height + width: height + onClicked: choice_left() + enabled: m_button_left_activated + m_is_enabled: enabled + } + + TriangleButton{ + m_point_right: true + id: button_right + anchors.left: main_selector.right + height: parent.height + width: height + onClicked: choice_right() + enabled: m_button_right_activated + m_is_enabled: enabled + } + } + + Rectangle{ + width: parent.width + height: parent.height + color: "gray" + opacity: 0.7 + visible: !m_is_enabled + } + +} diff --git a/qml/ui/sidebar/EditChannelWidthElement.qml b/qml/ui/sidebar/EditChannelWidthElement.qml new file mode 100644 index 000000000..55113cb71 --- /dev/null +++ b/qml/ui/sidebar/EditChannelWidthElement.qml @@ -0,0 +1,58 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../ui" as Ui +import "../elements" + + +BaseJoyEditElement{ + m_title: "Channel Width" + + property int m_model_index: -1; + + m_button_left_activated: { + if(!_ohdSystemAir.is_alive)return false; + return _ohdSystemAir.curr_channel_width_mhz==40; + } + + m_button_right_activated: { + if(!_ohdSystemAir.is_alive)return false; + return _ohdSystemAir.curr_channel_width_mhz==20; + } + + m_displayed_value: { + if(!_ohdSystemAir.is_alive)return "N/A"; + var channel_width=_ohdSystemAir.curr_channel_width_mhz + if(channel_width==20){ + return "20Mhz" + }else if(channel_width==40){ + return "40Mhz\n(HIGH BW)" + } + return "N/A" + } + + function set_channel_width_async(channel_width_mhz){ + if(!_ohdSystemAir.is_alive){ + _hudLogMessagesModel.add_message_warning("Cannot change BW:"+channel_width_mhz+"Mhz, AIR not alive"); + return; + } + _qopenhd.set_busy_for_milliseconds(2000,"CHANGING BW"); + _wbLinkSettingsHelper.change_param_air_channel_width_async(channel_width_mhz,true); + } + + onChoice_left: { + set_channel_width_async(20); + } + + onChoice_right: { + set_channel_width_async(40); + } + + +} diff --git a/qml/ui/sidebar/EditFrequencyElement.qml b/qml/ui/sidebar/EditFrequencyElement.qml new file mode 100644 index 000000000..9cc7fe079 --- /dev/null +++ b/qml/ui/sidebar/EditFrequencyElement.qml @@ -0,0 +1,123 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +BaseJoyEditElement{ + m_title: "Frequency" + + // 5700,5745,5785,5825,5865 + ListModel{ + id: frequencies_model + ListElement {frequency: 5700; verbose:"5700Mhz\nOHD 1"} + ListElement {frequency: 5745; verbose:"5745Mhz\nOHD 2"} + ListElement {frequency: 5785; verbose:"5785Mhz\nOHD 3"} + ListElement {frequency: 5825; verbose:"5825Mhz\nOHD 4"} + ListElement {frequency: 5865; verbose:"5865Mhz\nOHD 5"} + } + ListModel{ + id: frequencies_model_with_5180mhz_lowband + ListElement {frequency: 5180; verbose:"5180Mhz\n{CUSTOM LB}"} + ListElement {frequency: 5220; verbose:"5220Mhz\n{CUSTOM LB}"} + ListElement {frequency: 5260; verbose:"5260Mhz\n{CUSTOM LB}"} + ListElement {frequency: 5300; verbose:"5300Mhz\n{CUSTOM LB}"} + // + ListElement {frequency: 5700; verbose:"5700Mhz\nOHD 1"} + ListElement {frequency: 5745; verbose:"5745Mhz\nOHD 2"} + ListElement {frequency: 5785; verbose:"5785Mhz\nOHD 3"} + ListElement {frequency: 5825; verbose:"5825Mhz\nOHD 4"} + ListElement {frequency: 5865; verbose:"5865Mhz\nOHD 5"} + } + function get_model(){ + if(settings.dev_show_5180mhz_lowband){ + return frequencies_model_with_5180mhz_lowband + } + return frequencies_model; + } + + property int m_model_index: -1; + + function update_model_index(){ + const model=get_model(); + const curr_frequency=curr_air_frequency; + for(var i=0;i0 && (!is_armed()); + } + + m_button_right_activated: { + if(!_ohdSystemAir.is_alive)return false; + return m_model_index!=-1 && m_model_index0)return true; + return false; + } + + m_button_right_activated: { + if(!_ohdSystemAir.is_alive)return false; + const mcs_index=_ohdSystemAir.curr_mcs_index; + if(mcs_index<2)return true; + return false; + } + + m_displayed_value: { + if(!_ohdSystemAir.is_alive)return "N/A"; + const mcs_index=_ohdSystemAir.curr_mcs_index; + var value="["+mcs_index+"]\n"; + if(mcs_index==0){ + value+="LONG RANGE" + }else if(mcs_index==1){ + value+="RANGE" + }else if(mcs_index==2){ + value+="QUALITY" + }else{ + value+="EXPERIMENTAL" + } + return value; + } + + function set_air_only_mcs(mcs_index){ + _qopenhd.set_busy_for_milliseconds(2000,"CHANGING RATE"); + _wbLinkSettingsHelper.set_param_air_only_mcs_async(mcs_index) + } + + onChoice_left: { + const mcs_index=_ohdSystemAir.curr_mcs_index; + if(mcs_index<=0)return; + set_air_only_mcs(mcs_index-1); + } + + onChoice_right: { + const mcs_index=_ohdSystemAir.curr_mcs_index; + if(mcs_index>=2)return; + set_air_only_mcs(mcs_index+1); + } + + +} diff --git a/qml/ui/sidebar/GoBackElement.qml b/qml/ui/sidebar/GoBackElement.qml new file mode 100644 index 000000000..3c19aa6ee --- /dev/null +++ b/qml/ui/sidebar/GoBackElement.qml @@ -0,0 +1,46 @@ +import QtQuick 2.0 + + +Item{ + id: go_back_element + width: parent.width + height: 50 + + function close_and_go_back(){ + // Go back to the + //settings_form.gain_focus(); + quickPanelX.visible=false; + hudOverlayGrid.regain_focus(); + } + + Rectangle{ + width: parent.width + height: parent.height + border.color: "black" + border.width: go_back_element.focus ? 3 : 0; + color: "#333c4c" + opacity: go_back_element.focus ? 1.0 : 0.3; + } + Text{ + text: "BACK" + color: "white" + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + anchors.fill: parent + } + MouseArea{ + anchors.fill: parent + onClicked: { + close_and_go_back() + } + } + + Keys.onPressed: (event)=> { + console.log("Go back element key was pressed:"+event); + if(event.key == Qt.Key_Left || event.key == Qt.Key_Right || event.key == Qt.Key_Return){ + close_and_go_back() + } + } +} + + diff --git a/qml/ui/sidebar/InfoElement.qml b/qml/ui/sidebar/InfoElement.qml new file mode 100644 index 000000000..542025966 --- /dev/null +++ b/qml/ui/sidebar/InfoElement.qml @@ -0,0 +1,30 @@ +import QtQuick 2.0 + +Item{ + property string m_left_text: "" + property string m_right_text: "" + width: parent.width + height: 20 + property bool m_use_red: false + + Text{ + id: left + width: 80 + height: 20 + text: qsTr(m_left_text) + font.pixelSize: 14 + color: m_use_red ? "red" : "#ff05ff00" + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + } + Text{ + width: parent.width-left.width + height: 20 + anchors.left: left.right + text: qsTr(m_right_text) + font.pixelSize: 14 + color: m_use_red ? "red" : "#ff05ff00" + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + } +} diff --git a/qml/ui/sidebar/MavlinkChoiceElement.qml b/qml/ui/sidebar/MavlinkChoiceElement.qml new file mode 100644 index 000000000..161167490 --- /dev/null +++ b/qml/ui/sidebar/MavlinkChoiceElement.qml @@ -0,0 +1,316 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../ui" as Ui +import "../elements" + + +// +// Complex ;) +// Way more javascript code than i'd like, but as long as you: +// 1) Specify the right param_id +// 2) Specify the right settings model instance +// 3) Declare a matching choices-model here +// This handles all the edge cases that might arise ;) +BaseJoyEditElement{ + + // Needs to contain the unique ID of the param inside the param set + property string m_param_id: "FILL ME" + // Set to air / ground /air cam 1 / air cam 2 depending on where the param is stored + property var m_settings_model: _ohdSystemGroundSettings + // Int param is much more common, but string param is also possible + property bool override_takes_string_param: false + + ListModel{ + id: elements_model_brightness + ListElement {value: 0; verbose:"0%"} + ListElement {value: 25; verbose:"25%"} + ListElement {value: 50; verbose:"50%"} + ListElement {value: 75; verbose:"75%"} + ListElement {value: 95; verbose:"95%"} + ListElement {value: 98; verbose:"98%"} + ListElement {value: 100; verbose:"100%\n(Default)"} + ListElement {value: 102; verbose:"102%"} + ListElement {value: 105; verbose:"105%"} + ListElement {value: 125; verbose:"125%"} + ListElement {value: 150; verbose:"150%"} + ListElement {value: 175; verbose:"175%"} + ListElement {value: 200; verbose:"200%"} + } + ListModel{ + id: elements_model_saturation + ListElement {value: 50; verbose:"50%"} + ListElement {value: 75; verbose:"75%"} + ListElement {value: 90; verbose:"90%"} + ListElement {value: 100; verbose:"100%\n(Default)"} + ListElement {value: 110; verbose:"110%"} + ListElement {value: 125; verbose:"125%"} + ListElement {value: 150; verbose:"150%"} + } + ListModel{ + id: elements_model_contrast + ListElement {value: 50; verbose:"50%"} + ListElement {value: 75; verbose:"75%"} + ListElement {value: 90; verbose:"90%"} + ListElement {value: 100; verbose:"100%\n(Default)"} + ListElement {value: 125; verbose:"125%"} + ListElement {value: 150; verbose:"150%"} + } + ListModel{ + id: elements_model_sharpness + ListElement {value: 50; verbose:"50%"} + ListElement {value: 75; verbose:"75%"} + ListElement {value: 90; verbose:"90%"} + ListElement {value: 100; verbose:"100%\n(Default)"} + ListElement {value: 110; verbose:"110%"} + ListElement {value: 125; verbose:"125%"} + ListElement {value: 150; verbose:"150%"} + } + ListModel{ + id: elements_model_rotation + ListElement {value: 50; verbose:"50%"} + ListElement {value: 75; verbose:"75%"} + ListElement {value: 90; verbose:"90%"} + ListElement {value: 100; verbose:"100%\n(Default)"} + ListElement {value: 110; verbose:"110%"} + ListElement {value: 125; verbose:"125%"} + ListElement {value: 150; verbose:"150%"} + } + ListModel{ + id: elements_model_on_off + ListElement {value: 0; verbose:"OFF"} + ListElement {value: 1; verbose:"ON"} + } + ListModel{ + id: elements_model_air_recording + ListElement {value: 0; verbose:"ALWAYS\nOFF"} + ListElement {value: 1; verbose:"ALWAYS\nON"} + ListElement {value: 2; verbose:"AUTO\n(WHEN ARMED)"} + } + ListModel{ + id: elements_model_hotspot + ListElement {value: 0; verbose:"AUTO\n(WHEN DISARMED)"} + ListElement {value: 1; verbose:"ALWAYS\nOFF"} + ListElement {value: 2; verbose:"ALWAYS\nON"} + } + ListModel{ + id: elements_model_camera_rotation_degree + ListElement {value: 0; verbose:"0°\n(Default)"} + ListElement {value: 1; verbose:"180°\n"} + } + ListModel{ + id: elements_model_camera_rotation_flip + ListElement {value: 0; verbose:"0°\n(Default)"} + ListElement {value: 1; verbose:"VFLIP\n(mirror)\n"} + ListElement {value: 2; verbose:"HFLIP\n(180°)"} + ListElement {value: 3; verbose:"BOTH\n"} + } + + ListModel{ + id: elements_model_undefined + ListElement {value: 0; verbose:"0\nUNDEFINED"} + ListElement {value: 1; verbose:"1\nUNDEFINED"} + ListElement {value: 2; verbose:"2\nUNDEFINED"} + } + + // NOTE: This is for a string param ! + ListModel{ + id: elements_model_camera_resolution_bup + ListElement {value: "640x480@60"; verbose:"VGA 4:3\n60fps"} + ListElement {value: "1280x720@60"; verbose:"HD 16:9\n60fps"} + ListElement {value: "1920x1080@30"; verbose:"FHD 16:9\n30fps"} + } + ListModel{ + id: elements_model_camera_resolution_dynamic + } + // Built dynamically depending on the used camera + function get_camera_resolution_model(){ + if(_cameraStreamModelPrimary.camera_type<0){ + return elements_model_camera_resolution_bup; + } + var supported_resolutions=_cameraStreamModelPrimary.get_supported_resolutions(); + elements_model_camera_resolution_dynamic.clear() + for(var i=0; i0){ + populate_enable_left=true; + }else{ + populate_enable_left=false; + } + if(m_model_index { + console.log("SidebarStackButton"+override_tag+" key was pressed:"+event); + if(event.key === Qt.Key_Up){ + m_stack_index--; + if(m_stack_index<-1){ + m_stack_index=-1; + } + sidebar.handover_joystick_control_to_button(m_stack_index); + if(m_stack_index==-1){ + sidebar.close(); + } + event.accepted=true; + }else if(event.key === Qt.Key_Down){ + m_stack_index++; + if(m_stack_index>6){ + m_stack_index=6; + } + sidebar.handover_joystick_control_to_button(m_stack_index); + event.accepted=true; + }else if(event.key==Qt.Key_Right && focus){ + sidebar.handover_joystick_control_to_panel(sidebar.m_stack_index); + event.accepted=true; + } + } +} diff --git a/qml/ui/sidebar/TriangleButton.qml b/qml/ui/sidebar/TriangleButton.qml new file mode 100644 index 000000000..a3a8002c3 --- /dev/null +++ b/qml/ui/sidebar/TriangleButton.qml @@ -0,0 +1,53 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Shapes 1.15 + +Button{ + id: triangle_button + property bool m_is_enabled: false + + property bool m_point_right: true + + property color m_fill_color: "green" + + //text: "L" + text: "" + + background: Item{ + width: triangle_button.width + height: triangle_button.height + Rectangle{ + width: parent.width + height: parent.height + color: { + if(triangle_button.hovered){ + if(m_is_enabled){ + return "gray"; + } + return "red"; + } + return "transparent"; + } + } + Shape { + anchors.fill: parent + anchors.margins: 12 + id: shape + antialiasing: true + opacity: m_is_enabled ? 1.0 : 0.3; + ShapePath { + fillColor: { + return "white" + } + strokeWidth: 0 + strokeColor: fillColor + startX: m_point_right ? 0 : shape.width + PathLine { x: (m_point_right ? 0 : shape.width) ; y: 0 } + PathLine { x: (m_point_right ? shape.width : 0) ; y: shape.height/2 } + PathLine { x: (m_point_right ? 0 : shape.width) ; y: shape.height } + } + } + } +} diff --git a/qml/ui/widgets/BaseWidget.qml b/qml/ui/widgets/BaseWidget.qml index 35d643eab..bf0aeeb86 100644 --- a/qml/ui/widgets/BaseWidget.qml +++ b/qml/ui/widgets/BaseWidget.qml @@ -236,7 +236,12 @@ BaseWidgetForm { if (globalDragLock) { return; } - widgetAction.open() + // A bit dirty - different action + if(widgetIdentifier=="wb_link_rate_control_widget"){ + sidebar.open_link_category(); + }else{ + widgetAction.open() + } } } } diff --git a/qml/ui/widgets/DevStreamingInfo.qml b/qml/ui/widgets/DevStreamingInfo.qml new file mode 100644 index 000000000..edc244e6a --- /dev/null +++ b/qml/ui/widgets/DevStreamingInfo.qml @@ -0,0 +1,57 @@ +import QtQuick 2.0 + +Item { + width: 200 + height: 200 + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: 100 + visible: settings.show_dev_stats_overlay && !sidebar.m_extra_is_visible + + Column{ + width: parent.width + anchors.left: parent.left + anchors.top: parent.top + DevStreamingInfoText{ + m_left_text: "Drop TX:" + m_right_text: _cameraStreamModelPrimary.total_n_tx_dropped_frames + } + DevStreamingInfoText{ + m_left_text: "Drop RX:" + m_right_text: _cameraStreamModelPrimary.count_blocks_lost + } + DevStreamingInfoText{ + m_left_text: "BLKS L/R:" + m_right_text: _cameraStreamModelPrimary.count_blocks_lost+" "+_cameraStreamModelPrimary.count_blocks_recovered + } + DevStreamingInfoText{ + m_left_text: "TX Delay:" + m_right_text: _cameraStreamModelPrimary.curr_time_until_tx_min_max_avg + } + DevStreamingInfoText{ + m_left_text: "FEC Enc:" + m_right_text: _cameraStreamModelPrimary.curr_fec_encode_time_avg_min_max + } + DevStreamingInfoText{ + m_left_text: "FEC BL:"; + m_right_text: _cameraStreamModelPrimary.curr_fec_block_length_min_max_avg + } + DevStreamingInfoText{ + m_left_text: "B SET:" + m_right_text: _cameraStreamModelPrimary.curr_recomended_video_bitrate_string + } + DevStreamingInfoText{ + m_left_text: "B MES:" + m_right_text: _cameraStreamModelPrimary.curr_video_measured_encoder_bitrate + } + DevStreamingInfoText{ + m_left_text: "B+FEC:" + m_right_text: _cameraStreamModelPrimary.curr_video_injected_bitrate + } + DevStreamingInfoText{ + m_left_text: "FEC D:" + m_right_text: _cameraStreamModelPrimary.curr_fec_decode_time_avg_min_max + } + } + +} diff --git a/qml/ui/widgets/DevStreamingInfoText.qml b/qml/ui/widgets/DevStreamingInfoText.qml new file mode 100644 index 000000000..d8c2c7a59 --- /dev/null +++ b/qml/ui/widgets/DevStreamingInfoText.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0 + +Item{ + property string m_left_text: "" + property string m_right_text: "" + width: parent.width + height: 20 + + Text{ + id: left + width: 80 + height: 20 + text: qsTr(m_left_text) + font.pixelSize: 14 + color: "#ff05ff00" + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + } + Text{ + width: parent.width-left.width + height: 20 + anchors.left: left.right + text: qsTr(m_right_text) + font.pixelSize: 14 + color: "#ff05ff00" + horizontalAlignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + } +} diff --git a/qml/ui/widgets/LinkDownRSSIWidget.qml b/qml/ui/widgets/LinkDownRSSIWidget.qml index 9e64dbdff..bd871b985 100644 --- a/qml/ui/widgets/LinkDownRSSIWidget.qml +++ b/qml/ui/widgets/LinkDownRSSIWidget.qml @@ -441,6 +441,29 @@ BaseWidget { styleColor: settings.color_glow } + // TX errors + Text{ + id: txerrors_icon + visible: _ohdSystemAir.is_alive && _ohdSystemAir.tx_is_currently_dropping_packets + width: 32 + height: 32 + color: "yellow" + text: qsTr("\uf7ba") + anchors.left: downlink_dbm.right + anchors.leftMargin: 2 + anchors.top: parent.top + anchors.topMargin: 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 30 + font.family: settings.font_text + wrapMode: Text.NoWrap + elide: Text.ElideNone + clip: false + style: Text.Outline + styleColor: settings.color_glow + } + // Consti10 temporary begin - r.n we only have the n of injected and received packets per card, no FEC statistics (and the fec statistics also have changed such // that what was displayed previosly doesn't make sense anymore ColumnLayout{ diff --git a/qml/ui/widgets/MessageHUD.qml b/qml/ui/widgets/MessageHUD.qml index ae4a21053..be6343909 100644 --- a/qml/ui/widgets/MessageHUD.qml +++ b/qml/ui/widgets/MessageHUD.qml @@ -31,6 +31,7 @@ BaseWidget { // We do not want this widget to be dragable / touchable, it needs to be "hidden" otherwise // clicking the secondary video becomes impossible disable_dragging : true + z: 20 Item { id: widgetInner diff --git a/qml/ui/widgets/SOCStatusWidgetAir.qml b/qml/ui/widgets/SOCStatusWidgetAir.qml index b206a4ae6..7201abb8d 100644 --- a/qml/ui/widgets/SOCStatusWidgetAir.qml +++ b/qml/ui/widgets/SOCStatusWidgetAir.qml @@ -6,16 +6,13 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -BaseWidget { +SOCStatusWidgetGeneric { id: airStatusWidget width: 112 height: 24 visible: settings.show_air_status - widgetIdentifier: "air_status_widget" - bw_verbose_name: "CPU LOAD AIR" - defaultAlignment: 1 defaultXOffset: 128 defaultYOffset: 0 @@ -24,443 +21,8 @@ BaseWidget { hasWidgetDetail: true hasWidgetAction: true + widgetIdentifier: "air_status_widget" + bw_verbose_name: "CPU LOAD AIR" - // Functionality between air and air widget is really similar - this helps reducing code bloat a bit - property int m_cpuload_perc: _ohdSystemAir.curr_cpuload_perc - property int m_cpuload_perc_warn: settings.air_status_cpu_warn - property int m_cpuload_perc_caution: settings.air_status_cpu_caution - - property int m_soc_temperature_deg: _ohdSystemAir.curr_soc_temp_degree - property int m_soc_temperature_deg_warn: settings.air_status_temp_warn - property int m_soc_temperature_deg_caution: settings.air_status_temp_caution - - // These do not need warning level(s) and are hidden in the action popup - property int m_curr_cpu_freq_mhz: _ohdSystemAir.curr_cpu_freq_mhz - property int m_curr_isp_freq_mhz: _ohdSystemAir.curr_isp_freq_mhz - property int m_curr_h264_freq_mhz : _ohdSystemAir.curr_h264_freq_mhz - property int m_curr_core_freq_mhz : _ohdSystemAir.curr_core_freq_mhz - property int m_curr_v3d_freq_mhz : _ohdSystemAir.curr_v3d_freq_mhz - property int m_ram_usage_perc : _ohdSystemAir.ram_usage_perc - property bool m_rpi_undervolt_error: _ohdSystemAir.rpi_undervolt_error - - // 0 - no warning - // 1 - caution - // 2 - warning - function get_cpuload_warning_level(){ - if (m_cpuload_perc>= m_cpuload_perc_warn) { - return 2; - } - if (m_cpuload_perc > m_cpuload_perc_caution) { - return 1; - } - return 0; - } - function get_temperature_warning_level(){ - if (m_soc_temperature_deg>= m_soc_temperature_deg_warn) { - return 2; - } - if (m_soc_temperature_deg>= m_soc_temperature_deg_caution) { - return 1; - } - return 0; - } - // The icon is colored if any of those warnings are set - function get_highest_warning_level(){ - var w1=get_cpuload_warning_level(); - var w2=get_temperature_warning_level(); - return w1 >= w2 ? w1 : w2; - } - - function warning_level_to_color(level){ - if(level===2)return settings.color_warn; - if(level===1)return settings.color_caution; - return settings.color_shape; - } - - widgetDetailComponent: ScrollView { - - contentHeight: idBaseWidgetDefaultUiControlElements.height - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - clip: true - - BaseWidgetDefaultUiControlElements{ - id: idBaseWidgetDefaultUiControlElements - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Declutter Upon Arm") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.air_status_declutter - onCheckedChanged: settings.air_status_declutter = checked - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Show undervolt icon") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.air_status_show_undervolt_icon - onCheckedChanged: settings.air_status_show_undervolt_icon = checked - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Caution CPU") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.air_status_cpu_caution - color: settings.color_caution - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: air_status_cpu_caution_Slider - orientation: Qt.Horizontal - value: settings.air_status_cpu_caution - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.air_status_cpu_caution = Math.round( - air_status_cpu_caution_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Warn CPU") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.air_status_cpu_warn - color: settings.color_warn - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: air_status_cpu_warn_Slider - orientation: Qt.Horizontal - value: settings.air_status_cpu_warn - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.air_status_cpu_warn = Math.round( - air_status_cpu_warn_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Caution Temp") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.air_status_temp_caution - color: settings.color_caution - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: air_status_temp_caution_Slider - orientation: Qt.Horizontal - value: settings.air_status_temp_caution - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.air_status_temp_caution = Math.round( - air_status_temp_caution_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Warn Temp") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.air_status_temp_warn - color: settings.color_warn - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: air_status_temp_warn_Slider - orientation: Qt.Horizontal - value: settings.air_status_temp_warn - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.air_status_temp_warn = Math.round( - air_status_temp_warn_Slider.value * 10) / 10.0 - } - } - } - - } - } - widgetActionComponent: ScrollView{ - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - clip: true - - ColumnLayout{ - width:200 - - Text { - //Layout.alignment: left - text: "CPU freq: "+m_curr_cpu_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "ISP freq: "+m_curr_isp_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "H264 freq: "+m_curr_h264_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "Core freq: "+m_curr_core_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "v3d freq: "+m_curr_v3d_freq_mhz+" Mhz" - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "RAM: "+m_ram_usage_perc+" %" - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "RPI undervolt: "+(m_rpi_undervolt_error ? "Y" : "N") - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - } - } - - Item { - id: widgetInner - - anchors.fill: parent - scale: bw_current_scale - - Text { - id: chip_icon - y: 0 - width: 24 - height: 24 - color: { - var level=get_highest_warning_level(); - return warning_level_to_color(level) - } - opacity: bw_current_opacity - //text: "\uF2DA" - text: "\uf2db" - anchors.right: cpuload_air.left - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - font.family: "Font Awesome 5 Free" - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - Text{ - id: undervolt_error - width: 24 - height: 24 - opacity: bw_current_opacity - //text: "X" - text: String.fromCodePoint(0xf0e7)+"!" - anchors.right: chip_icon.left - anchors.top: chip_icon.top - anchors.rightMargin: 0 - anchors.topMargin: 2 - anchors.verticalCenter: parent.verticalCenter - font.family: "Font Awesome 5 Free" - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: "red" - color: "yellow" - //visible: true - visible: m_rpi_undervolt_error && settings.air_status_show_undervolt_icon - } - - Text { - id: cpuload_air - x: 0 - y: 0 - width: 36 - height: 24 - color: { - var level=get_cpuload_warning_level(); - return warning_level_to_color(level) - } - opacity: bw_current_opacity - text: Number(m_cpuload_perc).toLocaleString(Qt.locale(), - 'f', 0) + "%" - anchors.verticalCenter: parent.verticalCenter - anchors.right: temp_air.left - anchors.rightMargin: 2 - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - font.family: settings.font_text - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - - Text { - id: temp_air - x: 0 - y: 0 - width: 36 - height: 24 - color: { - var level=get_temperature_warning_level() - return warning_level_to_color(level) - } - opacity: bw_current_opacity - text: Number(m_soc_temperature_deg).toLocaleString(Qt.locale(), - 'f', 0) + "°" - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 0 - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - font.family: settings.font_text - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - } + m_is_air: true } diff --git a/qml/ui/widgets/SOCStatusWidgetGeneric.qml b/qml/ui/widgets/SOCStatusWidgetGeneric.qml new file mode 100644 index 000000000..8b6f5643b --- /dev/null +++ b/qml/ui/widgets/SOCStatusWidgetGeneric.qml @@ -0,0 +1,493 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +BaseWidget { + id: airStatusWidget + width: 112 + height: 24 + + visible: settings.show_air_status + + widgetIdentifier: "air_status_widget" + bw_verbose_name: "CPU LOAD AIR" + + property bool m_is_air: false + + // Functionality between air and air widget is really similar - this helps reducing code bloat a bit + property int m_cpuload_perc: m_is_air ? _ohdSystemAir.curr_cpuload_perc : _ohdSystemGround.curr_cpuload_perc; + property int m_cpuload_perc_warn: m_is_air ? settings.air_status_cpu_warn : settings.ground_status_cpu_warn; + property int m_cpuload_perc_caution: m_is_air ? settings.air_status_cpu_caution : settings.ground_status_cpu_caution + + property int m_soc_temperature_deg: m_is_air ? _ohdSystemAir.curr_soc_temp_degree : _ohdSystemGround.curr_soc_temp_degree + property int m_soc_temperature_deg_warn: m_is_air ? settings.air_status_temp_warn : settings.ground_status_temp_warn + property int m_soc_temperature_deg_caution: m_is_air ? settings.air_status_temp_caution : settings.ground_status_temp_caution + + // These do not need warning level(s) and are hidden in the action popup + property int m_curr_cpu_freq_mhz: m_is_air ?_ohdSystemAir.curr_cpu_freq_mhz : _ohdSystemGround.curr_cpu_freq_mhz + property int m_curr_isp_freq_mhz: m_is_air ?_ohdSystemAir.curr_isp_freq_mhz : _ohdSystemGround.curr_isp_freq_mhz + property int m_curr_h264_freq_mhz : m_is_air ?_ohdSystemAir.curr_h264_freq_mhz : _ohdSystemGround.curr_h264_freq_mhz + property int m_curr_core_freq_mhz : m_is_air ?_ohdSystemAir.curr_core_freq_mhz : _ohdSystemGround.curr_core_freq_mhz + property int m_curr_v3d_freq_mhz : m_is_air ?_ohdSystemAir.curr_v3d_freq_mhz : _ohdSystemGround.curr_v3d_freq_mhz + property int m_ram_usage_perc : m_is_air ?_ohdSystemAir.ram_usage_perc : _ohdSystemGround.ram_usage_perc + property bool m_rpi_undervolt_error: m_is_air ?_ohdSystemAir.rpi_undervolt_error : _ohdSystemGround.rpi_undervolt_error + + // 0 - no warning + // 1 - caution + // 2 - warning + function get_cpuload_warning_level(){ + if (m_cpuload_perc>= m_cpuload_perc_warn) { + return 2; + } + if (m_cpuload_perc > m_cpuload_perc_caution) { + return 1; + } + return 0; + } + function get_temperature_warning_level(){ + if (m_soc_temperature_deg>= m_soc_temperature_deg_warn) { + return 2; + } + if (m_soc_temperature_deg>= m_soc_temperature_deg_caution) { + return 1; + } + return 0; + } + // The icon is colored if any of those warnings are set + function get_highest_warning_level(){ + var w1=get_cpuload_warning_level(); + var w2=get_temperature_warning_level(); + return w1 >= w2 ? w1 : w2; + } + + function warning_level_to_color(level){ + if(level===2)return settings.color_warn; + if(level===1)return settings.color_caution; + return settings.color_shape; + } + + widgetDetailComponent: ScrollView { + + contentHeight: idBaseWidgetDefaultUiControlElements.height + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + clip: true + + BaseWidgetDefaultUiControlElements{ + id: idBaseWidgetDefaultUiControlElements + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Declutter Upon Arm") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: m_is_air ? settings.air_status_declutter : settings.ground_status_declutter + onCheckedChanged: { + if(m_is_air){ + settings.air_status_declutter = checked + }else{ + settings.ground_status_declutter = checked + } + } + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Show undervolt icon") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: m_is_air ? settings.air_status_show_undervolt_icon : settings.ground_status_show_undervolt_icon + onCheckedChanged: { + if(m_is_air){ + settings.air_status_show_undervolt_icon = checked + }else{ + settings.ground_status_show_undervolt_icon = checked + } + } + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Caution CPU") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: m_is_air ? settings.air_status_cpu_caution : settings.ground_status_cpu_caution + color: settings.color_caution + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.right + verticalAlignment: Text.AlignVCenter + } + Slider { + id: air_status_cpu_caution_Slider + orientation: Qt.Horizontal + value: m_is_air ? settings.air_status_cpu_caution : settings.ground_status_cpu_caution + from: 20 + to: 100 + stepSize: 1 + height: parent.height + anchors.rightMargin: 0 + anchors.right: parent.right + width: parent.width - 96 + + onValueChanged: { + if(m_is_air){ + settings.air_status_cpu_caution = Math.round( + air_status_cpu_caution_Slider.value * 10) / 10.0 + }else{ + settings.ground_status_cpu_caution = Math.round( + air_status_cpu_caution_Slider.value * 10) / 10.0 + } + + } + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Warn CPU") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: m_is_air ? settings.air_status_cpu_warn : settings.ground_status_cpu_warn + color: settings.color_warn + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.right + verticalAlignment: Text.AlignVCenter + } + Slider { + id: air_status_cpu_warn_Slider + orientation: Qt.Horizontal + value: m_is_air ? settings.air_status_cpu_warn : settings.ground_status_cpu_warn + from: 20 + to: 100 + stepSize: 1 + height: parent.height + anchors.rightMargin: 0 + anchors.right: parent.right + width: parent.width - 96 + + onValueChanged: { + if(m_is_air){ + settings.air_status_cpu_warn = Math.round( + air_status_cpu_warn_Slider.value * 10) / 10.0 + }else{ + settings.ground_status_cpu_warn = Math.round( + air_status_cpu_warn_Slider.value * 10) / 10.0 + } + } + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Caution Temp") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: m_is_air ? settings.air_status_temp_caution : settings.ground_status_temp_caution + color: settings.color_caution + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.right + verticalAlignment: Text.AlignVCenter + } + Slider { + id: air_status_temp_caution_Slider + orientation: Qt.Horizontal + value: settings.air_status_temp_caution + from: 20 + to: 100 + stepSize: 1 + height: parent.height + anchors.rightMargin: 0 + anchors.right: parent.right + width: parent.width - 96 + + onValueChanged: { + if(m_is_air){ + settings.air_status_temp_caution = Math.round( + air_status_temp_caution_Slider.value * 10) / 10.0 + }else{ + settings.ground_status_temp_caution = Math.round( + air_status_temp_caution_Slider.value * 10) / 10.0 + } + } + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Warn Temp") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: m_is_air ? settings.air_status_temp_warn : settings.ground_status_temp_warn + color: settings.color_warn + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.right + verticalAlignment: Text.AlignVCenter + } + Slider { + id: air_status_temp_warn_Slider + orientation: Qt.Horizontal + value: m_is_air ? settings.air_status_temp_warn : settings.ground_status_temp_warn + from: 20 + to: 100 + stepSize: 1 + height: parent.height + anchors.rightMargin: 0 + anchors.right: parent.right + width: parent.width - 96 + + onValueChanged: { + if(m_is_air){ + settings.air_status_temp_warn = Math.round( + air_status_temp_warn_Slider.value * 10) / 10.0 + }else{ + settings.ground_status_temp_warn = Math.round( + air_status_temp_warn_Slider.value * 10) / 10.0 + } + + + } + } + } + + } + } + widgetActionComponent: ScrollView{ + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + clip: true + + ColumnLayout{ + width:200 + + Text { + //Layout.alignment: left + text: "CPU freq: "+m_curr_cpu_freq_mhz+" Mhz"; + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "ISP freq: "+m_curr_isp_freq_mhz+" Mhz"; + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "H264 freq: "+m_curr_h264_freq_mhz+" Mhz"; + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "Core freq: "+m_curr_core_freq_mhz+" Mhz"; + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "v3d freq: "+m_curr_v3d_freq_mhz+" Mhz" + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "RAM: "+m_ram_usage_perc+" %" + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + Text { + //Layout.alignment: left + text: "RPI undervolt: "+(m_rpi_undervolt_error ? "Y" : "N") + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } + } + } + + Item { + id: widgetInner + + anchors.fill: parent + scale: bw_current_scale + + Text { + id: chip_icon + y: 0 + width: 24 + height: 24 + color: { + var level=get_highest_warning_level(); + return warning_level_to_color(level) + } + opacity: bw_current_opacity + text: m_is_air ? "\uf2db" : "\uF2DA" + anchors.right: cpuload_air.left + anchors.rightMargin: 2 + anchors.verticalCenter: parent.verticalCenter + font.family: "Font Awesome 5 Free" + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: settings.color_glow + } + Text{ + id: undervolt_error + width: 24 + height: 24 + opacity: bw_current_opacity + //text: "X" + text: String.fromCodePoint(0xf0e7)+"!" + anchors.right: chip_icon.left + anchors.top: chip_icon.top + anchors.rightMargin: 0 + anchors.topMargin: 2 + anchors.verticalCenter: parent.verticalCenter + font.family: "Font Awesome 5 Free" + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: "red" + color: "yellow" + //visible: true + visible: m_rpi_undervolt_error && settings.air_status_show_undervolt_icon + } + + Text { + id: cpuload_air + x: 0 + y: 0 + width: 36 + height: 24 + color: { + var level=get_cpuload_warning_level(); + return warning_level_to_color(level) + } + opacity: bw_current_opacity + text: Number(m_cpuload_perc).toLocaleString(Qt.locale(), + 'f', 0) + "%" + anchors.verticalCenter: parent.verticalCenter + anchors.right: temp_air.left + anchors.rightMargin: 2 + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + font.family: settings.font_text + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: settings.color_glow + } + + Text { + id: temp_air + x: 0 + y: 0 + width: 36 + height: 24 + color: { + var level=get_temperature_warning_level() + return warning_level_to_color(level) + } + opacity: bw_current_opacity + text: Number(m_soc_temperature_deg).toLocaleString(Qt.locale(), + 'f', 0) + "°" + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 0 + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + font.family: settings.font_text + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: settings.color_glow + } + } +} diff --git a/qml/ui/widgets/SOCStatusWidgetGround.qml b/qml/ui/widgets/SOCStatusWidgetGround.qml index a92a83627..a9cd64dd6 100644 --- a/qml/ui/widgets/SOCStatusWidgetGround.qml +++ b/qml/ui/widgets/SOCStatusWidgetGround.qml @@ -6,16 +6,13 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -BaseWidget { +SOCStatusWidgetGeneric { id: groundStatusWidget width: 112 height: 24 visible: settings.show_ground_status - widgetIdentifier: "ground_status_widget" - bw_verbose_name: "CPU LOAD GND" - defaultAlignment: 1 defaultXOffset: 128 defaultYOffset: 24 @@ -24,443 +21,8 @@ BaseWidget { hasWidgetDetail: true hasWidgetAction: true + widgetIdentifier: "ground_status_widget" + bw_verbose_name: "CPU LOAD GND" - // Functionality between air and ground widget is really similar - this helps reducing code bloat a bit - property int m_cpuload_perc: _ohdSystemGround.curr_cpuload_perc - property int m_cpuload_perc_warn: settings.ground_status_cpu_warn - property int m_cpuload_perc_caution: settings.ground_status_cpu_caution - - property int m_soc_temperature_deg: _ohdSystemGround.curr_soc_temp_degree - property int m_soc_temperature_deg_warn: settings.ground_status_temp_warn - property int m_soc_temperature_deg_caution: settings.ground_status_temp_caution - - // These do not need warning level(s) and are hidden in the action popup - property int m_curr_cpu_freq_mhz: _ohdSystemGround.curr_cpu_freq_mhz - property int m_curr_isp_freq_mhz: _ohdSystemGround.curr_isp_freq_mhz - property int m_curr_h264_freq_mhz : _ohdSystemGround.curr_h264_freq_mhz - property int m_curr_core_freq_mhz : _ohdSystemGround.curr_core_freq_mhz - property int m_curr_v3d_freq_mhz : _ohdSystemGround.curr_v3d_freq_mhz - property int m_ram_usage_perc : _ohdSystemGround.ram_usage_perc - property bool m_rpi_undervolt_error: _ohdSystemGround.rpi_undervolt_error - - // 0 - no warning - // 1 - caution - // 2 - warning - function get_cpuload_warning_level(){ - if (m_cpuload_perc>= m_cpuload_perc_warn) { - return 2; - } - if (m_cpuload_perc > m_cpuload_perc_caution) { - return 1; - } - return 0; - } - function get_temperature_warning_level(){ - if (m_soc_temperature_deg>= m_soc_temperature_deg_warn) { - return 2; - } - if (m_soc_temperature_deg>= m_soc_temperature_deg_caution) { - return 1; - } - return 0; - } - // The icon is colored if any of those warnings are set - function get_highest_warning_level(){ - var w1=get_cpuload_warning_level(); - var w2=get_temperature_warning_level(); - return w1 >= w2 ? w1 : w2; - } - - function warning_level_to_color(level){ - if(level===2)return settings.color_warn; - if(level===1)return settings.color_caution; - return settings.color_shape; - } - - widgetDetailComponent: ScrollView { - - contentHeight: idBaseWidgetDefaultUiControlElements.height - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - clip: true - - BaseWidgetDefaultUiControlElements{ - id: idBaseWidgetDefaultUiControlElements - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Declutter Upon Arm") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.ground_status_declutter - onCheckedChanged: settings.ground_status_declutter = checked - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Show undervolt icon") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.ground_status_show_undervolt_icon - onCheckedChanged: settings.ground_status_show_undervolt_icon = checked - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Caution CPU") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.ground_status_cpu_caution - color: settings.color_caution - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: ground_status_cpu_caution_Slider - orientation: Qt.Horizontal - value: settings.ground_status_cpu_caution - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.ground_status_cpu_caution = Math.round( - ground_status_cpu_caution_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Warn CPU") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.ground_status_cpu_warn - color: settings.color_warn - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: ground_status_cpu_warn_Slider - orientation: Qt.Horizontal - value: settings.ground_status_cpu_warn - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.ground_status_cpu_warn = Math.round( - ground_status_cpu_warn_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Caution Temp") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.ground_status_temp_caution - color: settings.color_caution - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: ground_status_temp_caution_Slider - orientation: Qt.Horizontal - value: settings.ground_status_temp_caution - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.ground_status_temp_caution = Math.round( - ground_status_temp_caution_Slider.value * 10) / 10.0 - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Warn Temp") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - text: settings.ground_status_temp_warn - color: settings.color_warn - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: ground_status_temp_warn_Slider - orientation: Qt.Horizontal - value: settings.ground_status_temp_warn - from: 20 - to: 100 - stepSize: 1 - height: parent.height - anchors.rightMargin: 0 - anchors.right: parent.right - width: parent.width - 96 - - onValueChanged: { - settings.ground_status_temp_warn = Math.round( - ground_status_temp_warn_Slider.value * 10) / 10.0 - } - } - } - - } - } - widgetActionComponent: ScrollView{ - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - clip: true - - ColumnLayout{ - width:200 - - Text { - //Layout.alignment: left - text: "CPU freq: "+m_curr_cpu_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "ISP freq: "+m_curr_isp_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "H264 freq: "+m_curr_h264_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "Core freq: "+m_curr_core_freq_mhz - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "v3d freq: "+m_curr_v3d_freq_mhz+" Mhz" - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "RAM: "+m_ram_usage_perc+" %" - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - Text { - //Layout.alignment: left - text: "RPI undervolt: "+(m_rpi_undervolt_error ? "Y" : "N") - color: "white" - font.bold: true - height: parent.height - font.pixelSize: detailPanelFontPixels - verticalAlignment: Text.AlignVCenter - } - } - } - - Item { - id: widgetInner - - anchors.fill: parent - scale: bw_current_scale - - Text { - id: chip_icon - y: 0 - width: 24 - height: 24 - color: { - var level=get_highest_warning_level(); - return warning_level_to_color(level) - } - opacity: bw_current_opacity - text: "\uF2DA" - //text: "\uf2db" - anchors.right: cpuload_gnd.left - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - font.family: "Font Awesome 5 Free" - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - - Text{ - id: undervolt_error - width: 24 - height: 24 - opacity: bw_current_opacity - //text: "X" - text: String.fromCodePoint(0xf0e7)+"!" - anchors.right: chip_icon.left - anchors.top: chip_icon.top - anchors.rightMargin: 0 - anchors.topMargin: 2 - anchors.verticalCenter: parent.verticalCenter - font.family: "Font Awesome 5 Free" - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: "red" - color: "yellow" - //visible: true - visible: m_rpi_undervolt_error && settings.ground_status_show_undervolt_icon - } - Text { - id: cpuload_gnd - x: 0 - y: 0 - width: 36 - height: 24 - color: { - var level=get_cpuload_warning_level(); - return warning_level_to_color(level) - } - opacity: bw_current_opacity - text: Number(m_cpuload_perc).toLocaleString(Qt.locale(), - 'f', 0) + "%" - anchors.verticalCenter: parent.verticalCenter - anchors.right: temp_gnd.left - anchors.rightMargin: 2 - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - font.family: settings.font_text - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - - Text { - id: temp_gnd - x: 0 - y: 0 - width: 36 - height: 24 - color: { - var level=get_temperature_warning_level() - return warning_level_to_color(level) - } - opacity: bw_current_opacity - text: Number(m_soc_temperature_deg).toLocaleString(Qt.locale(), - 'f', 0) + "°" - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 0 - verticalAlignment: Text.AlignVCenter - font.pixelSize: 14 - font.family: settings.font_text - horizontalAlignment: Text.AlignRight - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - } + m_is_air: false } diff --git a/qml/ui/widgets/Sidebar.qml b/qml/ui/widgets/Sidebar.qml deleted file mode 100644 index fa46a00f7..000000000 --- a/qml/ui/widgets/Sidebar.qml +++ /dev/null @@ -1,1112 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 -import QtQuick.Shapes 1.0 -import QtQuick.Controls.Material 2.0 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../elements" - -BaseWidget { - id: sidebar - width: 64 - height: 24 - visible: settings.show_sidebar - defaultVCenter: true - widgetIdentifier: "sidebar_widget" - bw_verbose_name: "Menu" - defaultHCenter: false - hasWidgetDetail: false - - property int secondaryUiWidth: 335 - property int secondaryUiHeight: 375 - property string secondaryUiColor: "#000" - property real secondaryUiOpacity: 0.75 - property string mainDarkColor: "#302f30" - property string highlightColor: "#555" - property int m_control_yaw : settings.control_widget_use_fc_channels ? _rcchannelsmodelfc.control_yaw : _rcchannelsmodelground.control_yaw - property int m_control_roll: settings.control_widget_use_fc_channels ? _rcchannelsmodelfc.control_roll : _rcchannelsmodelground.control_roll - property int m_control_pitch: settings.control_widget_use_fc_channels ? _rcchannelsmodelfc.control_pitch : _rcchannelsmodelground.control_pitch - property int m_control_throttle: settings.control_widget_use_fc_channels ? _rcchannelsmodelfc.control_throttle : _rcchannelsmodelground.control_throttle - property int selectedItemIndex: -1 - - ColumnLayout { - id: uiButton - width: 32 - height: 32 - anchors.verticalCenter: parent.verticalCenter - - MouseArea { - id: mouseArea1 - Layout.fillWidth: true - Layout.fillHeight: true - - onClicked: { - googleUI.visible = true - linkUI.visible = true - uiButton.visible = false - } - - Rectangle { - width: parent.width - height: parent.height - color: "transparent" - - RowLayout { - width: parent.width - height: parent.height - - Text { - text: "\uf054" - font.pixelSize: 16 - opacity: 1.0 - font.family: "Font Awesome 5 Free" - color: "grey" - } - } - } - } - } - - - ColumnLayout { - spacing: 10 - visible: true - anchors.verticalCenter: parent.verticalCenter - - - - RowLayout { - spacing: 0 - - - Rectangle { - id: googleUI - width: secondaryUiHeight / 8 //number of items - height: secondaryUiHeight - color: highlightColor - opacity: 0.7 - visible: false - - // Add a property to track the selected item index - property int selectedItemIndex: 0 - - ListView { - width: parent.width - height: parent.height - focus: true - keyNavigationEnabled: true - interactive: false - - model: ListModel { - ListElement { text: " \uf1eb"; subText: "link" } - ListElement { text: " \uf11b"; subText: "rc" } - ListElement { text: " \uf03d"; subText: "video" } - ListElement { text: " \uf030"; subText: "camera" } - ListElement { text: " \uf0c7"; subText: "recording" } - ListElement { text: " \uf26c"; subText: "display" } - ListElement { text: " \uf55b"; subText: "drone" } - ListElement { text: " \uf053"; subText: "back" } - } - - delegate: Item { - width: parent.width - height: secondaryUiHeight / 8 //number of items - - MouseArea { - id: mouseArea - anchors.fill: parent - - onClicked: { - // Update the selected item index - googleUI.selectedItemIndex = index; - - function hideElements() { - linkUI.visible = false; - videoUI.visible = false; - cameraUI.visible = false; - recordingUI.visible = false; - displayUI.visible = false; - miscUI.visible = false; - rcUI.visible = false; - } - - console.log("Item clicked: " + model.subText) - var uiElementName = model.subText + "UI"; - if (model.subText === "back") { - googleUI.visible = false; - uiButton.visible = true; - hideElements(); - } else if (model.subText === "link") { - hideElements(); - linkUI.visible = true; - } else if (model.subText === "rc") { - hideElements(); - rcUI.visible = true; - } else if (model.subText === "video") { - hideElements(); - videoUI.visible = true; - } else if (model.subText === "camera") { - hideElements(); - cameraUI.visible = true; - } else if (model.subText === "recording") { - hideElements(); - recordingUI.visible = true; - } else if (model.subText === "display") { - hideElements(); - displayUI.visible = true; - } else if (model.subText === "drone") { - hideElements(); - miscUI.visible = true; - } - } - - Rectangle { - width: parent.width - height: parent.height - color: index === googleUI.selectedItemIndex ? highlightColor : mainDarkColor - } - - RowLayout { - width: parent.width - height: parent.height - - Text { - text: model.text - font.pixelSize: secondaryUiHeight / 16 - opacity: 1.0 - font.family: "Font Awesome 5 Free" - color: "white" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - Text { - text: model.subText - visible: false - font.pixelSize: secondaryUiHeight / 16 - opacity: 1.0 - color: "white" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - } - } - } - } - } - - // UI Starts here - - Rectangle { - id: linkUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: linkUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "LINK" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: linkUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - - Text{ - text: "Range -> Quality" + " (MCS" + _ohdSystemAir.curr_mcs_index + ")" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: valueSlider - from: 0 - to: 3 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - // Handle the slider value change here - console.log("MCS Slider:", value) - } - } - Text{ - text: "Frequency" + " (" + _ohdSystemGround.curr_channel_mhz + ")" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ComboBox { - width: 200 - model: [ "2312", "2332", "2352", "2372", "2392", "2412", "2432", "2452", "2472", "2492", "2512", "2532", "2572", "2592", "2612", "2632", "2652", "2672", "2692", "2712", "5180", "5200", "5220", "5240", "5260", "5280", "5300", "5320", "5500", "5520", "5540", "5560", "5580", "5600", "5620", "5640", "5660", "5680", "5700", "5745", "5765", "5785", "5805", "5825", "5845", "5865", "5885" ] - } - Text{ - text: "TX Power Air" + " (" + "TODO" + ")" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: txPowerAirSlider - from: 22 - to: 58 - stepSize: 4 - //snapMode: Slider.SnapToStep - value: 22 // Initial value - Material.accent: Material.Grey - onValueChanged: { - // Handle the slider value change here - console.log("TX Power Air Slider:", value) - } - } - Text{ - text: "Bandwith" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - TabBar { - id: control - Material.accent: "#fff" - Material.foreground: "white" - background: Rectangle { - implicitWidth: 100 - implicitHeight: 40 - color: secondaryUiColor - } - TabButton { - text: qsTr("20MHZ") - } - TabButton { - text: qsTr("40MHZ") - } - } - } - } - Rectangle { - id: linkUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveText.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveText.width-10 - - - Text { - id: saveText - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: rcUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: rcUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "OpenHD-RC" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: rcUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - - Text{ - text: "Enable RC" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Switch { - text: qsTr("") - } - Text{ - text: "Channel 1" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ProgressBar { - id: progressBar1 - from: 1000 - to: 2000 - value: m_control_yaw - Material.accent: Material.Grey - } - Text{ - text: "Channel 2" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ProgressBar { - id: progressBar2 - from: 1000 - to: 2000 - value: m_control_yaw - Material.accent: Material.Grey - } - Text{ - text: "Channel 3" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ProgressBar { - id: progressBar3 - from: 1000 - to: 2000 - value: m_control_yaw - Material.accent: Material.Grey - } - Text{ - text: "Channel 4" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ProgressBar { - id: progressBar4 - from: 1000 - to: 2000 - value: m_control_yaw - Material.accent: Material.Grey - } - } - } - Rectangle { - id: rcUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveTextRC.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveTextRC.width-10 - - - Text { - id: saveTextRC - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: videoUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: videoUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "Video" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: videoUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - - ComboBox { - id:raspberryCams3 - visible: true - width: 200 - model: [ "IMX708","IMX462","IMX477" ] - } - ComboBox { - id:rock5Cams3 - visible: false - width: 200 - model: [ "IMX415","IMX462","IMX708" ] - } - Text{ - text: "Camera Mode"+ " (" + "1080p,60fps" + ")" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: resolutionSlider3 - from: 0 - to: 2 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Resolution Slider:", value) - } - } - Text{ - text: "Switch Cameras" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Switch { - } - TabBar { - id: cameraSwitch - Material.accent: "#fff" - Material.foreground: "white" - background: Rectangle { - implicitWidth: 100 - implicitHeight: 40 - color: secondaryUiColor - } - TabButton { - text: qsTr("Cam1") - } - TabButton { - text: qsTr("Cam2") - } - } - } - } - Rectangle { - id: videoUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveTextVid.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveTextVid.width-10 - - - Text { - id: saveTextVid - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: cameraUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: cameraUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "Camera" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: cameraUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - Text{ - text: "ISO" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: isoSlider - from: 0 - to: 2 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Resolution Slider:", value) - } - } - Text{ - text: "Exposure" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: exposureSlider - from: 0 - to: 2 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Framerate Slider:", value) - } - } - Text{ - text: "Camera Mode" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: modeSlider - from: 0 - to: 2 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Resolution Slider:", value) - } - } - } - } - Rectangle { - id: camUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveTextCam.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveTextCam.width-10 - - - Text { - id: saveTextCam - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: recordingUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: recordingUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "Recording" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: recordingUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - Text{ - text: "Resolution 480p -> 1080p" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: recResolutionSlider - from: 0 - to: 2 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Resolution Slider:", value) - } - } - Text{ - text: "Framerate 30 -> 60" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: recFramerateSlider - from: 0 - to: 3 - stepSize: 1 - //snapMode: Slider.SnapToStep - value: 0 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Framerate Slider:", value) - } - } - Text{ - text: "Bitrate" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Slider { - id: recBitrateSlider - from: 2 - to: 18 - stepSize: 2 - //snapMode: Slider.SnapToStep - value: 8 // Initial value - Material.accent: Material.Grey - onValueChanged: { - console.log("Birtate Slider:", value) - } - } - Text{ - text: "Enable Recording" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Switch { - } - } - } - Rectangle { - id: recUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveTextRec.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveTextRec.width-10 - - - Text { - id: saveTextRec - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: displayUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: displayUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "Misc" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: displayUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - - Text{ - text: "Display Resolution" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - ComboBox { - id:displayResolution - visible: true - width: 200 - model: [ "1080p","720p","480p" ] - } - Text{ - text: "Enable wifi Hotspot" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Switch { - text: qsTr("Wifi Hotspot") - } - Text{ - text: "Enable ethernet Hotspot" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - Switch { - text: qsTr("Ethernet Hotspot") - } - Text{ - text: "Connect to device" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - TextField { - Material.accent: Material.Grey - Material.theme: Material.Dark - placeholderText: qsTr("192.168.3.1") - } - } - } - Rectangle { - id: miscUiFooter - anchors.top: parent.top - anchors.topMargin: secondaryUiHeight-saveTextMisc.height-10 - anchors.left: parent.left - anchors.leftMargin: secondaryUiWidth-saveTextMisc.width-10 - - - Text { - id: saveTextMisc - text: "\uf019" - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - } - Rectangle { - id: miscUI - width: secondaryUiWidth - height: secondaryUiHeight - color: secondaryUiColor - opacity: secondaryUiOpacity - visible: false - Rectangle { - id: miscUiHeader - width: secondaryUiWidth - height: secondaryUiHeight/8 - color: highlightColor - opacity: 1.0 - - Text { - anchors.centerIn: parent - text: "Status " - font.pixelSize: 21 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - } - Item { - anchors.top: parent.top - anchors.topMargin: miscUiHeader.height/2 - width: parent.width - height: parent.height - - Column { - anchors.centerIn: parent - spacing: 5 - - RowLayout { - Text { - text: "Status:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: "Connected" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - RowLayout { - Text { - text: "Screen:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: applicationWindow.width + "x" + applicationWindow.height - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - - RowLayout { - Text { - text: "Frequency:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: _ohdSystemGround.curr_channel_mhz + "Mhz" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - RowLayout { - Text { - text: "Wifi-Adapter:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - id: wifiMessage - property int wifiCode: _ohdSystemAir.ohd_wifi_type - - function wifiName(code) { - switch (code) { - case 11: return "Asus AC56"; - case 12: return "8812AU CUSTOM"; - case 21: return "88XXBU"; - default: return "unknown"; - } - } - - text: wifiName(wifiCode) + "("+_ohdSystemAir.ohd_wifi_type+")" - Layout.leftMargin: 12 - } - } - RowLayout { - Text { - text: "Format:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: "1080p 60fps" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - RowLayout { - Text { - text: "Version:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: "Todo" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - RowLayout { - Text { - text: "Hardware Air:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - id: platformMessage - property int platformCode: _ohdSystemAir.ohd_platform_type - - function platformName(code) { - switch (code) { - case 0: return "unknown"; - case 10: return "X20"; - case 20: return "x86"; - case 30: return "rpi"; - case 31: return "rpi 4"; - case 32: return "rpi 3"; - case 33: return "rpi 2"; - case 34: return "rpi 1"; - case 35: return "rpi 0"; - case 40: return "rock"; - case 41: return "rk3566"; - case 42: return "rock5a"; - case 43: return "rock5b"; - default: return "unknown"; - } - } - - text: platformName(platformCode) + "("+_ohdSystemAir.ohd_platform_type+")" - Layout.leftMargin: 12 - } - } - RowLayout { - Text { - text: "Hardware Ground:" - font.pixelSize: 14 - font.bold: true - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - } - - Text { - text: "Todo" - font.pixelSize: 14 - font.family: "AvantGarde-Medium" - color: "#ffffff" - smooth: true - - } - } - - Button { - id: button - text: "Advanced Menu" - - onClicked: { - settings_panel.visible = true - } - } - } - } - } - - } - } -} diff --git a/qml/ui/widgets/SpeedSecondWidget.qml b/qml/ui/widgets/SpeedSecondWidget.qml index 7413de0ef..4b6dff79a 100644 --- a/qml/ui/widgets/SpeedSecondWidget.qml +++ b/qml/ui/widgets/SpeedSecondWidget.qml @@ -17,7 +17,7 @@ BaseWidget { defaultYOffset: 128 dragging: false - visible: settings.show_speed_second + visible: settings.show_speed_second && !sidebar.m_is_active widgetIdentifier: "speed_second_widget" bw_verbose_name: "SECOND SPEED" diff --git a/qml/ui/widgets/SpeedWidget.qml b/qml/ui/widgets/SpeedWidget.qml index c727480ee..34cd5d776 100644 --- a/qml/ui/widgets/SpeedWidget.qml +++ b/qml/ui/widgets/SpeedWidget.qml @@ -13,7 +13,7 @@ BaseWidget { width: 64 height: 24 - visible: settings.speed_ladder_show + visible: settings.speed_ladder_show && !sidebar.m_is_active defaultXOffset: 20 defaultVCenter: true diff --git a/qml/ui/widgets/VideoBitrateWidgetGeneric.qml b/qml/ui/widgets/VideoBitrateWidgetGeneric.qml index dd6e5b734..505febe4b 100644 --- a/qml/ui/widgets/VideoBitrateWidgetGeneric.qml +++ b/qml/ui/widgets/VideoBitrateWidgetGeneric.qml @@ -81,7 +81,7 @@ BaseWidget { return; } if(mode===0){ //mode off - var result=camModel.try_update_parameter_int("V_AIR_RECORDING",0)==="" + var result=camModel.try_update_parameter_int("AIR_RECORDING_E",0)==="" if(result){ _hudLogMessagesModel.signalAddLogMessage(6,"recording "+camString+" disabled") m_camera_recording_mode=0; @@ -90,7 +90,7 @@ BaseWidget { } } if(mode===1){ //mode on - var result=camModel.try_update_parameter_int("V_AIR_RECORDING",1)==="" + var result=camModel.try_update_parameter_int("AIR_RECORDING_E",1)==="" if(result){ _hudLogMessagesModel.signalAddLogMessage(6,"recording "+camString+" enabled") m_camera_recording_mode=1; @@ -99,7 +99,7 @@ BaseWidget { } } if(mode==2){ //mode auto - var result=camModel.try_update_parameter_int("V_AIR_RECORDING",2)==="" + var result=camModel.try_update_parameter_int("AIR_RECORDING_E",2)==="" if(result){ _hudLogMessagesModel.signalAddLogMessage(6,"recording "+camString+" auto enabled") m_camera_recording_mode=2; @@ -189,76 +189,36 @@ BaseWidget { return _cameraStreamModelPrimary.camera_codec_to_string(m_camera_stream_model.encoding_codec); } } - - ComboBox{ - id: resolution_fps_cb - model: resolutions_model - textRole: "title" - width: 200 - Layout.preferredWidth: 200 - Layout.minimumWidth: 200 - height: 50 - currentIndex: -1 - displayText: { - if(!_ohdSystemAir.is_alive)return "Res@fps N/A"; + SimpleLeftRightText{ + m_left_text: qsTr("Resolution:") + m_right_text: { + if(!_ohdSystemAir.is_alive)return "RES@FPS N/A"; return m_curr_video_format; } - onActivated: { - console.log("onActivated:"+currentIndex); - if(currentIndex<0)return; - const resolution=model.get(currentIndex).value - console.log("Selected resolution: "+resolution); - set_camera_resolution(resolution); - _qopenhd.show_toast("NOTE: OpenHD cannot check if your HW actually supports a given resolution / framerate"); - } - enabled: _ohdSystemAir.is_alive; } - /*Item{ + Item{ width: parent.width - height: 150 - //color: "green" - GridLayout{ - width: parent.width - height: parent.height - rows: 3 - columns: 2 // - // 1 dummy sw - // 2 mmal - // 3 veye - // 4 libcamera - //enabled: (m_camera_stream_model.camera_type==1 || m_camera_stream_model.camera_type==2 || m_camera_stream_model.camera_type==4) - Button{ - text: "480p60(16:9)" - onClicked: set_camera_resolution("848x480@60") - highlighted: m_curr_video_format=="848x480@60" - } - Button{ - text: "480p60(4:3)" - onClicked: set_camera_resolution("640x480@60") - highlighted: m_curr_video_format=="640x480@60" - } - Button{ - text: "720p49(16:9)" - onClicked: set_camera_resolution("1280x720@49") - highlighted: m_curr_video_format=="1280x720@49" - } - Button{ - text: "720p60(4:3)" - onClicked: set_camera_resolution("960x720@60") - highlighted: m_curr_video_format=="960x720@60" - } - Button{ - text: "1080p30(16:9)" - onClicked: set_camera_resolution("1920x1080@30") - highlighted: m_curr_video_format=="1920x1080@30" + height: 50 + ButtonIconGear{ + id: button_change_resolution + anchors.left: parent.left + onClicked: { + sidebar.open_video_stream_category(m_is_for_primary_camera) + widgetAction.close() + } - Button{ - text: "1080p30(4:3)" - onClicked: set_camera_resolution("1440x1080@30") - highlighted: m_curr_video_format=="1440x1080@30" + } + ButtonIconGear{ + id: button_change_iq + anchors.left: button_change_resolution.right + anchors.leftMargin: 5 + onClicked: { + sidebar.open_category(2) + widgetAction.close() } } - }*/ + visible: m_is_for_primary_camera + } SimpleLeftRightText{ m_left_text: qsTr("Bitrate SET:") m_right_text: m_camera_stream_model.curr_recomended_video_bitrate_string diff --git a/qml/ui/widgets/WBLinkRateControlWidget.qml b/qml/ui/widgets/WBLinkRateControlWidget.qml index 9ee17494f..d43a140c0 100644 --- a/qml/ui/widgets/WBLinkRateControlWidget.qml +++ b/qml/ui/widgets/WBLinkRateControlWidget.qml @@ -23,7 +23,7 @@ BaseWidget { bw_verbose_name: "WB LIVE RATE CONTROL" defaultAlignment: 0 - defaultXOffset: 320 + defaultXOffset: 250 defaultYOffset: 2 defaultHCenter: false defaultVCenter: false @@ -331,28 +331,6 @@ Make the video more stable (less microfreezes) on the cost of less image quality m_text: "Range vs Bitrate" m_info_text: m_DESCRIPTION_CHANNEL_WIDTH } - /*TabBar{ - width: parent.width - height: m_row_height - id: channel_width_tab_bar - currentIndex: m_curr_channel_width==40 ? 1 : 0; - onCurrentIndexChanged: { - //const chan_w= currentIndex==0 ? 20 : 40; - //set_channel_width_async(20) - } - TabButton{ - text: "20Mhz" - onClicked: { - set_channel_width_async(20) - } - } - TabButton{ - text: "40Mhz" - onClicked: { - set_channel_width_async(40) - } - } - }*/ Row{ width: parent.width height: m_row_height