From f3aa5160b82e4e18c1f3abda3838468d58141a7d Mon Sep 17 00:00:00 2001 From: mingf2 <fm140905@gmail.com> Date: Sun, 23 May 2021 19:29:47 -0500 Subject: [PATCH] move reconstruction task to worker thread --- Headers/MainWindow.h | 31 +- Headers/readerwriterqueue/LICENSE.md | 28 - Headers/readerwriterqueue/README.md | 155 --- Headers/readerwriterqueue/atomicops.h | 679 ------------ .../readerwritercircularbuffer.h | 288 ------ Headers/readerwriterqueue/readerwriterqueue.h | 979 ------------------ Headers/reconstruction.h | 80 +- Headers/setup.h | 8 +- Headers/worker.h | 15 +- MainWindow.ui | 14 + README.md | 2 +- Sources/MainWindow.cpp | 193 ++-- Sources/main.cpp | 9 +- Sources/reconstruction.cpp | 116 ++- Sources/worker.cpp | 53 +- icons/saveas.png | Bin 0 -> 18600 bytes icons/screenshot.svg | 1 + imagerQt.pro | 3 - resources.qrc | 2 + 19 files changed, 307 insertions(+), 2349 deletions(-) delete mode 100755 Headers/readerwriterqueue/LICENSE.md delete mode 100755 Headers/readerwriterqueue/README.md delete mode 100755 Headers/readerwriterqueue/atomicops.h delete mode 100755 Headers/readerwriterqueue/readerwritercircularbuffer.h delete mode 100755 Headers/readerwriterqueue/readerwriterqueue.h create mode 100755 icons/saveas.png create mode 100755 icons/screenshot.svg diff --git a/Headers/MainWindow.h b/Headers/MainWindow.h index af2bc26..67a1930 100644 --- a/Headers/MainWindow.h +++ b/Headers/MainWindow.h @@ -26,19 +26,13 @@ public: explicit MainWindow(QWidget *parent = nullptr); virtual void changeEvent(QEvent *e); ~MainWindow(); - // reconstruct images - void run(); void closeEvent (QCloseEvent *event); - bool aborted=false; public slots: private: Ui::MainWindow *ui; Setup* config; - SPSC* coneQueue; - TH2D* hist; - void createHist(); - ULong64_t counts=0; + RecoImage* image; TText* countsText; void createCountsLabel(); // gridlines @@ -47,32 +41,21 @@ private: void aitoff2xy(const double& l, const double& b, double &Al, double &Ab); void createGridlines(); void redraw(); -// void createSourceMarker(); // worker thread responsible for image reconstruction Worker* workerThread; - bool threadExecutionFinished=false; - bool finished=false; - bool stop=true; + bool aborted=false; -// bool closed=false; -// void customClose(); - // update image - void updateImage(std::vector<Cone>::const_iterator first, - std::vector<Cone>::const_iterator last, - const bool& normalized=true); + bool saveCanvas(const QString& fileName); + QString curFile; protected: private slots: void handleOpen(); - void handleSave(); - void handleSaveAs(); + bool handleSave(); + bool handleSaveAs(); void handleClose(); - - void handleStart(); - void handleStop(); - void handleClear(); - + void handleScreenshot(); void handleAbout(); void notifyThreadFinished(); diff --git a/Headers/readerwriterqueue/LICENSE.md b/Headers/readerwriterqueue/LICENSE.md deleted file mode 100755 index 7b667d9..0000000 --- a/Headers/readerwriterqueue/LICENSE.md +++ /dev/null @@ -1,28 +0,0 @@ -This license applies to all the code in this repository except that written by third -parties, namely the files in benchmarks/ext, which have their own licenses, and Jeff -Preshing's semaphore implementation (used in the blocking queues) which has a zlib -license (embedded in atomicops.h). - -Simplified BSD License: - -Copyright (c) 2013-2021, 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. diff --git a/Headers/readerwriterqueue/README.md b/Headers/readerwriterqueue/README.md deleted file mode 100755 index ba87ecf..0000000 --- a/Headers/readerwriterqueue/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# A single-producer, single-consumer lock-free queue for C++ - -This mini-repository has my very own implementation of a lock-free queue (that I designed from scratch) for C++. - -It only supports a two-thread use case (one consuming, and one producing). The threads can't switch roles, though -you could use this queue completely from a single thread if you wish (but that would sort of defeat the purpose!). - -Note: If you need a general-purpose multi-producer, multi-consumer lock free queue, I have [one of those too][mpmc]. - -This repository also includes a [circular-buffer SPSC queue][circular] which supports blocking on enqueue as well as dequeue. - - -## Features - -- [Blazing fast][benchmarks] -- Compatible with C++11 (supports moving objects instead of making copies) -- Fully generic (templated container of any type) -- just like `std::queue`, you never need to allocate memory for elements yourself - (which saves you the hassle of writing a lock-free memory manager to hold the elements you're queueing) -- Allocates memory up front, in contiguous blocks -- Provides a `try_enqueue` method which is guaranteed never to allocate memory (the queue starts with an initial capacity) -- Also provides an `enqueue` method which can dynamically grow the size of the queue as needed -- Also provides `try_emplace`/`emplace` convenience methods -- Has a blocking version with `wait_dequeue` -- Completely "wait-free" (no compare-and-swap loop). Enqueue and dequeue are always O(1) (not counting memory allocation) -- On x86, the memory barriers compile down to no-ops, meaning enqueue and dequeue are just a simple series of loads and stores (and branches) - - -## Use - -Simply drop the readerwriterqueue.h (or readerwritercircularbuffer.h) and atomicops.h files into your source code and include them :-) -A modern compiler is required (MSVC2010+, GCC 4.7+, ICC 13+, or any C++11 compliant compiler should work). - -Note: If you're using GCC, you really do need GCC 4.7 or above -- [4.6 has a bug][gcc46bug] that prevents the atomic fence primitives -from working correctly. - -Example: - -```cpp -using namespace moodycamel; - -ReaderWriterQueue<int> q(100); // Reserve space for at least 100 elements up front - -q.enqueue(17); // Will allocate memory if the queue is full -bool succeeded = q.try_enqueue(18); // Will only succeed if the queue has an empty slot (never allocates) -assert(succeeded); - -int number; -succeeded = q.try_dequeue(number); // Returns false if the queue was empty - -assert(succeeded && number == 17); - -// You can also peek at the front item of the queue (consumer only) -int* front = q.peek(); -assert(*front == 18); -succeeded = q.try_dequeue(number); -assert(succeeded && number == 18); -front = q.peek(); -assert(front == nullptr); // Returns nullptr if the queue was empty -``` - -The blocking version has the exact same API, with the addition of `wait_dequeue` and -`wait_dequeue_timed` methods: - -```cpp -BlockingReaderWriterQueue<int> q; - -std::thread reader([&]() { - int item; -#if 1 - for (int i = 0; i != 100; ++i) { - // Fully-blocking: - q.wait_dequeue(item); - } -#else - for (int i = 0; i != 100; ) { - // Blocking with timeout - if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5))) - ++i; - } -#endif -}); -std::thread writer([&]() { - for (int i = 0; i != 100; ++i) { - q.enqueue(i); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } -}); -writer.join(); -reader.join(); - -assert(q.size_approx() == 0); -``` - -Note that `wait_dequeue` will block indefinitely while the queue is empty; this -means care must be taken to only call `wait_dequeue` if you're sure another element -will come along eventually, or if the queue has a static lifetime. This is because -destroying the queue while a thread is waiting on it will invoke undefined behaviour. - -The blocking circular buffer has a fixed number of slots, but is otherwise quite similar to -use: - -```cpp -BlockingReaderWriterCircularBuffer<int> q(1024); // pass initial capacity - -q.try_enqueue(1); -int number; -q.try_dequeue(number); -assert(number == 1); - -q.wait_enqueue(123); -q.wait_dequeue(number); -assert(number == 123); - -q.wait_dequeue_timed(number, std::chrono::milliseconds(10)); -``` - - -## CMake installation -As an alternative to including the source files in your project directly, -you can use CMake to install the library in your system's include directory: - -``` -mkdir build -cd build -cmake .. -make install -``` - -Then, you can include it from your source code: -``` -#include <readerwriterqueue/readerwriterqueue.h> -``` - -## Disclaimers - -The queue should only be used on platforms where aligned integer and pointer access is atomic; fortunately, that -includes all modern processors (e.g. x86/x86-64, ARM, and PowerPC). *Not* for use with a DEC Alpha processor (which has very weak memory ordering) :-) - -Note that it's only been tested on x86(-64); if someone has access to other processors I'd love to run some tests on -anything that's not x86-based. - -## More info - -See the [LICENSE.md][license] file for the license (simplified BSD). - -My [blog post][blog] introduces the context that led to this code, and may be of interest if you're curious -about lock-free programming. - - -[blog]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++ -[license]: LICENSE.md -[benchmarks]: http://moodycamel.com/blog/2013/a-fast-lock-free-queue-for-c++#benchmarks -[gcc46bug]: http://stackoverflow.com/questions/16429669/stdatomic-thread-fence-has-undefined-reference -[mpmc]: https://github.com/cameron314/concurrentqueue -[circular]: readerwritercircularbuffer.h diff --git a/Headers/readerwriterqueue/atomicops.h b/Headers/readerwriterqueue/atomicops.h deleted file mode 100755 index c1b8ab6..0000000 --- a/Headers/readerwriterqueue/atomicops.h +++ /dev/null @@ -1,679 +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 <cerrno> -#include <cassert> -#include <type_traits> -#include <cerrno> -#include <cstdint> -#include <ctime> - -// 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 -#if defined(__has_feature) -#if __has_feature(thread_sanitizer) -#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) -#else -#define AE_NO_TSAN -#endif -#else -#define AE_NO_TSAN -#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 <intrin.h> - -#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 <ppcintrinsics.h> -#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 <atomic> - -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: std::atomic_thread_fence(std::memory_order_acquire); break; - case memory_order_release: std::atomic_thread_fence(std::memory_order_release); break; - case memory_order_acq_rel: std::atomic_thread_fence(std::memory_order_acq_rel); break; - case memory_order_seq_cst: 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 <atomic> -#endif -#include <utility> - -// 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<typename T> -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<typename U> AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(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<typename U> AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { value = std::forward<U>(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<typename U> - AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN - { - value.store(std::forward<U>(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<T> 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 <mach/mach.h> -#elif defined(__unix__) -#include <semaphore.h> -#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<unsigned int>(timeout_usecs / 1000000); - ts.tv_nsec = static_cast<int>((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<unsigned int>(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<time_t>(usecs / usecs_in_1_sec); - ts.tv_nsec += static_cast<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 { - 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); - } - } - }; -#else -#error Unsupported platform! (No semaphore wrapper available) -#endif - - //--------------------------------------------------------- - // LightweightSemaphore - //--------------------------------------------------------- - class LightweightSemaphore - { - public: - typedef std::make_signed<std::size_t>::type ssize_t; - - private: - weak_atomic<ssize_t> 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<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.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<std::size_t>(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 diff --git a/Headers/readerwriterqueue/readerwritercircularbuffer.h b/Headers/readerwriterqueue/readerwritercircularbuffer.h deleted file mode 100755 index dbaabe8..0000000 --- a/Headers/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 <utility> -#include <chrono> -#include <memory> -#include <cstdlib> -#include <cstdint> -#include <cassert> - -// 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<typename T> -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<spsc_sema::LightweightSemaphore::ssize_t>(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<char*>(std::malloc(capacity * sizeof(T) + std::alignment_of<T>::value - 1)); - data = align_for<T>(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<T*>(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<typename Rep, typename Period> - inline bool wait_enqueue_timed(T const& item, std::chrono::duration<Rep, Period> const& timeout) - { - return wait_enqueue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(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<typename Rep, typename Period> - inline bool wait_enqueue_timed(T&& item, std::chrono::duration<Rep, Period> const& timeout) - { - return wait_enqueue_timed(std::move(item), std::chrono::duration_cast<std::chrono::microseconds>(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<typename U> - 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<typename U> - 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<typename U> - 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<typename U, typename Rep, typename Period> - inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout) - { - return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(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<typename U> - void inner_enqueue(U&& item) - { - std::size_t i = nextSlot++; - new (reinterpret_cast<T*>(data) + (i & mask)) T(std::forward<U>(item)); - items->signal(); - } - - template<typename U> - void inner_dequeue(U& item) - { - std::size_t i = nextItem++; - T& element = reinterpret_cast<T*>(data)[i & mask]; - item = std::move(element); - element.~T(); - slots->signal(); - } - - template<typename U> - static inline char* align_for(char* ptr) - { - const std::size_t alignment = std::alignment_of<U>::value; - return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(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<spsc_sema::LightweightSemaphore> slots; // number of slots currently free - std::unique_ptr<spsc_sema::LightweightSemaphore> items; // number of elements currently enqueued - char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(char*) * 2 - sizeof(std::size_t) * 2 - sizeof(std::unique_ptr<spsc_sema::LightweightSemaphore>) * 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 -}; - -} diff --git a/Headers/readerwriterqueue/readerwriterqueue.h b/Headers/readerwriterqueue/readerwriterqueue.h deleted file mode 100755 index d87110a..0000000 --- a/Headers/readerwriterqueue/readerwriterqueue.h +++ /dev/null @@ -1,979 +0,0 @@ -// ©2013-2020 Cameron Desrochers. -// Distributed under the simplified BSD license (see the license file that -// should have come with this header). - -#pragma once - -#include "atomicops.h" -#include <new> -#include <type_traits> -#include <utility> -#include <cassert> -#include <stdexcept> -#include <new> -#include <cstdint> -#include <cstdlib> // For malloc/free/abort & size_t -#include <memory> -#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 -#include <chrono> -#endif - - -// A lock-free queue for a single-consumer, single-producer architecture. -// The queue is also wait-free in the common path (except if more memory -// needs to be allocated, in which case malloc is called). -// Allocates memory sparingly, and only once if the original maximum size -// estimate is never exceeded. -// Tested on x86/x64 processors, but semantics should be correct for all -// architectures (given the right implementations in atomicops.h), provided -// that aligned integer and pointer accesses are naturally atomic. -// Note that there should only be one consumer thread and producer thread; -// Switching roles of the threads, or using multiple consecutive threads for -// one role, is not safe unless properly synchronized. -// Using the queue exclusively from one thread is fine, though a bit silly. - -#ifndef MOODYCAMEL_CACHE_LINE_SIZE -#define MOODYCAMEL_CACHE_LINE_SIZE 64 -#endif - -#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 - -#ifndef MOODYCAMEL_HAS_EMPLACE -#if !defined(_MSC_VER) || _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 -#define MOODYCAMEL_HAS_EMPLACE 1 -#endif -#endif - -#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE -#if defined (__APPLE__) && defined (__MACH__) && __cplusplus >= 201703L -// This is required to find out what deployment target we are using -#include <CoreFoundation/CoreFoundation.h> -#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 -// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we can't support over-alignment in this case -#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE -#endif -#endif -#endif - -#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE -#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) -#endif - -#ifdef AE_VCPP -#pragma warning(push) -#pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -#pragma warning(disable: 4820) // padding was added -#pragma warning(disable: 4127) // conditional expression is constant -#endif - -namespace moodycamel { - -template<typename T, size_t MAX_BLOCK_SIZE = 512> -class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue -{ - // Design: Based on a queue-of-queues. The low-level queues are just - // circular buffers with front and tail indices indicating where the - // next element to dequeue is and where the next element can be enqueued, - // respectively. Each low-level queue is called a "block". Each block - // wastes exactly one element's worth of space to keep the design simple - // (if front == tail then the queue is empty, and can't be full). - // The high-level queue is a circular linked list of blocks; again there - // is a front and tail, but this time they are pointers to the blocks. - // The front block is where the next element to be dequeued is, provided - // the block is not empty. The back block is where elements are to be - // enqueued, provided the block is not full. - // The producer thread owns all the tail indices/pointers. The consumer - // thread owns all the front indices/pointers. Both threads read each - // other's variables, but only the owning thread updates them. E.g. After - // the consumer reads the producer's tail, the tail may change before the - // consumer is done dequeuing an object, but the consumer knows the tail - // will never go backwards, only forwards. - // If there is no room to enqueue an object, an additional block (of - // equal size to the last block) is added. Blocks are never removed. - -public: - typedef T value_type; - - // Constructs a queue that can hold at least `size` elements without further - // allocations. If more than MAX_BLOCK_SIZE elements are requested, - // then several blocks of MAX_BLOCK_SIZE each are reserved (including - // at least one extra buffer block). - AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) -#ifndef NDEBUG - : enqueuing(false) - ,dequeuing(false) -#endif - { - assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2"); - assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); - - Block* firstBlock = nullptr; - - largestBlockSize = ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block - if (largestBlockSize > MAX_BLOCK_SIZE * 2) { - // We need a spare block in case the producer is writing to a different block the consumer is reading from, and - // wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity - // between front == tail meaning "empty" and "full". - // So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the - // number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying): - size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); - largestBlockSize = MAX_BLOCK_SIZE; - Block* lastBlock = nullptr; - for (size_t i = 0; i != initialBlockCount; ++i) { - auto block = make_block(largestBlockSize); - if (block == nullptr) { -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED - throw std::bad_alloc(); -#else - abort(); -#endif - } - if (firstBlock == nullptr) { - firstBlock = block; - } - else { - lastBlock->next = block; - } - lastBlock = block; - block->next = firstBlock; - } - } - else { - firstBlock = make_block(largestBlockSize); - if (firstBlock == nullptr) { -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED - throw std::bad_alloc(); -#else - abort(); -#endif - } - firstBlock->next = firstBlock; - } - frontBlock = firstBlock; - tailBlock = firstBlock; - - // Make sure the reader/writer threads will have the initialized memory setup above: - fence(memory_order_sync); - } - - // Note: The queue should not be accessed concurrently while it's - // being moved. It's up to the user to synchronize this. - AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) - : frontBlock(other.frontBlock.load()), - tailBlock(other.tailBlock.load()), - largestBlockSize(other.largestBlockSize) -#ifndef NDEBUG - ,enqueuing(false) - ,dequeuing(false) -#endif - { - other.largestBlockSize = 32; - Block* b = other.make_block(other.largestBlockSize); - if (b == nullptr) { -#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED - throw std::bad_alloc(); -#else - abort(); -#endif - } - b->next = b; - other.frontBlock = b; - other.tailBlock = b; - } - - // Note: The queue should not be accessed concurrently while it's - // being moved. It's up to the user to synchronize this. - ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN - { - Block* b = frontBlock.load(); - frontBlock = other.frontBlock.load(); - other.frontBlock = b; - b = tailBlock.load(); - tailBlock = other.tailBlock.load(); - other.tailBlock = b; - std::swap(largestBlockSize, other.largestBlockSize); - return *this; - } - - // Note: The queue should not be accessed concurrently while it's - // being deleted. It's up to the user to synchronize this. - AE_NO_TSAN ~ReaderWriterQueue() - { - // Make sure we get the latest version of all variables from other CPUs: - fence(memory_order_sync); - - // Destroy any remaining objects in queue and free memory - Block* frontBlock_ = frontBlock; - Block* block = frontBlock_; - do { - Block* nextBlock = block->next; - size_t blockFront = block->front; - size_t blockTail = block->tail; - - for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { - auto element = reinterpret_cast<T*>(block->data + i * sizeof(T)); - element->~T(); - (void)element; - } - - auto rawBlock = block->rawThis; - block->~Block(); - std::free(rawBlock); - block = nextBlock; - } while (block != frontBlock_); - } - - - // Enqueues a copy of element if there is room in the queue. - // Returns true if the element was enqueued, false otherwise. - // Does not allocate memory. - AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN - { - return inner_enqueue<CannotAlloc>(element); - } - - // Enqueues a moved copy of element if there is room in the queue. - // Returns true if the element was enqueued, false otherwise. - // Does not allocate memory. - AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN - { - return inner_enqueue<CannotAlloc>(std::forward<T>(element)); - } - -#if MOODYCAMEL_HAS_EMPLACE - // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). - template<typename... Args> - AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN - { - return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...); - } -#endif - - // Enqueues a copy of element on the queue. - // Allocates an additional block of memory if needed. - // Only fails (returns false) if memory allocation fails. - AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN - { - return inner_enqueue<CanAlloc>(element); - } - - // Enqueues a moved copy of element on the queue. - // Allocates an additional block of memory if needed. - // Only fails (returns false) if memory allocation fails. - AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN - { - return inner_enqueue<CanAlloc>(std::forward<T>(element)); - } - -#if MOODYCAMEL_HAS_EMPLACE - // Like enqueue() but with emplace semantics (i.e. construct-in-place). - template<typename... Args> - AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN - { - return inner_enqueue<CanAlloc>(std::forward<Args>(args)...); - } -#endif - - // Attempts to dequeue an element; if the queue is empty, - // returns false instead. If the queue has at least one element, - // moves front to result using operator=, then returns true. - template<typename U> - bool try_dequeue(U& result) AE_NO_TSAN - { -#ifndef NDEBUG - ReentrantGuard guard(this->dequeuing); -#endif - - // High-level pseudocode: - // Remember where the tail block is - // If the front block has an element in it, dequeue it - // Else - // If front block was the tail block when we entered the function, return false - // Else advance to next block and dequeue the item there - - // Note that we have to use the value of the tail block from before we check if the front - // block is full or not, in case the front block is empty and then, before we check if the - // tail block is at the front block or not, the producer fills up the front block *and - // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently - // reproducible in practice. - // In order to avoid overhead in the common case, though, we do a double-checked pattern - // where we have the fast path if the front block is not empty, then read the tail block, - // then re-read the front block and check if it's not empty again, then check if the tail - // block has advanced. - - Block* frontBlock_ = frontBlock.load(); - size_t blockTail = frontBlock_->localTail; - size_t blockFront = frontBlock_->front.load(); - - if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { - fence(memory_order_acquire); - - non_empty_front_block: - // Front block not empty, dequeue from here - auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); - result = std::move(*element); - element->~T(); - - blockFront = (blockFront + 1) & frontBlock_->sizeMask; - - fence(memory_order_release); - frontBlock_->front = blockFront; - } - else if (frontBlock_ != tailBlock.load()) { - fence(memory_order_acquire); - - frontBlock_ = frontBlock.load(); - blockTail = frontBlock_->localTail = frontBlock_->tail.load(); - blockFront = frontBlock_->front.load(); - fence(memory_order_acquire); - - if (blockFront != blockTail) { - // Oh look, the front block isn't empty after all - goto non_empty_front_block; - } - - // Front block is empty but there's another block ahead, advance to it - Block* nextBlock = frontBlock_->next; - // Don't need an acquire fence here since next can only ever be set on the tailBlock, - // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock which - // ensures next is up-to-date on this CPU in case we recently were at tailBlock. - - size_t nextBlockFront = nextBlock->front.load(); - size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); - fence(memory_order_acquire); - - // Since the tailBlock is only ever advanced after being written to, - // we know there's for sure an element to dequeue on it - assert(nextBlockFront != nextBlockTail); - AE_UNUSED(nextBlockTail); - - // We're done with this block, let the producer use it if it needs - fence(memory_order_release); // Expose possibly pending changes to frontBlock->front from last dequeue - frontBlock = frontBlock_ = nextBlock; - - compiler_fence(memory_order_release); // Not strictly needed - - auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); - - result = std::move(*element); - element->~T(); - - nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; - - fence(memory_order_release); - frontBlock_->front = nextBlockFront; - } - else { - // No elements in current block and no other block to advance to - return false; - } - - return true; - } - - - // Returns a pointer to the front element in the queue (the one that - // would be removed next by a call to `try_dequeue` or `pop`). If the - // queue appears empty at the time the method is called, nullptr is - // returned instead. - // Must be called only from the consumer thread. - T* peek() const AE_NO_TSAN - { -#ifndef NDEBUG - ReentrantGuard guard(this->dequeuing); -#endif - // See try_dequeue() for reasoning - - Block* frontBlock_ = frontBlock.load(); - size_t blockTail = frontBlock_->localTail; - size_t blockFront = frontBlock_->front.load(); - - if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { - fence(memory_order_acquire); - non_empty_front_block: - return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); - } - else if (frontBlock_ != tailBlock.load()) { - fence(memory_order_acquire); - frontBlock_ = frontBlock.load(); - blockTail = frontBlock_->localTail = frontBlock_->tail.load(); - blockFront = frontBlock_->front.load(); - fence(memory_order_acquire); - - if (blockFront != blockTail) { - goto non_empty_front_block; - } - - Block* nextBlock = frontBlock_->next; - - size_t nextBlockFront = nextBlock->front.load(); - fence(memory_order_acquire); - - assert(nextBlockFront != nextBlock->tail.load()); - return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T)); - } - - return nullptr; - } - - // Removes the front element from the queue, if any, without returning it. - // Returns true on success, or false if the queue appeared empty at the time - // `pop` was called. - bool pop() AE_NO_TSAN - { -#ifndef NDEBUG - ReentrantGuard guard(this->dequeuing); -#endif - // See try_dequeue() for reasoning - - Block* frontBlock_ = frontBlock.load(); - size_t blockTail = frontBlock_->localTail; - size_t blockFront = frontBlock_->front.load(); - - if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { - fence(memory_order_acquire); - - non_empty_front_block: - auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T)); - element->~T(); - - blockFront = (blockFront + 1) & frontBlock_->sizeMask; - - fence(memory_order_release); - frontBlock_->front = blockFront; - } - else if (frontBlock_ != tailBlock.load()) { - fence(memory_order_acquire); - frontBlock_ = frontBlock.load(); - blockTail = frontBlock_->localTail = frontBlock_->tail.load(); - blockFront = frontBlock_->front.load(); - fence(memory_order_acquire); - - if (blockFront != blockTail) { - goto non_empty_front_block; - } - - // Front block is empty but there's another block ahead, advance to it - Block* nextBlock = frontBlock_->next; - - size_t nextBlockFront = nextBlock->front.load(); - size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); - fence(memory_order_acquire); - - assert(nextBlockFront != nextBlockTail); - AE_UNUSED(nextBlockTail); - - fence(memory_order_release); - frontBlock = frontBlock_ = nextBlock; - - compiler_fence(memory_order_release); - - auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T)); - element->~T(); - - nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; - - fence(memory_order_release); - frontBlock_->front = nextBlockFront; - } - else { - // No elements in current block and no other block to advance to - return false; - } - - return true; - } - - // Returns the approximate number of items currently in the queue. - // Safe to call from both the producer and consumer threads. - inline size_t size_approx() const AE_NO_TSAN - { - size_t result = 0; - Block* frontBlock_ = frontBlock.load(); - Block* block = frontBlock_; - do { - fence(memory_order_acquire); - size_t blockFront = block->front.load(); - size_t blockTail = block->tail.load(); - result += (blockTail - blockFront) & block->sizeMask; - block = block->next.load(); - } while (block != frontBlock_); - return result; - } - - // Returns the total number of items that could be enqueued without incurring - // an allocation when this queue is empty. - // Safe to call from both the producer and consumer threads. - // - // NOTE: The actual capacity during usage may be different depending on the consumer. - // If the consumer is removing elements concurrently, the producer cannot add to - // the block the consumer is removing from until it's completely empty, except in - // the case where the producer was writing to the same block the consumer was - // reading from the whole time. - inline size_t max_capacity() const { - size_t result = 0; - Block* frontBlock_ = frontBlock.load(); - Block* block = frontBlock_; - do { - fence(memory_order_acquire); - result += block->sizeMask; - block = block->next.load(); - } while (block != frontBlock_); - return result; - } - - -private: - enum AllocationMode { CanAlloc, CannotAlloc }; - -#if MOODYCAMEL_HAS_EMPLACE - template<AllocationMode canAlloc, typename... Args> - bool inner_enqueue(Args&&... args) AE_NO_TSAN -#else - template<AllocationMode canAlloc, typename U> - bool inner_enqueue(U&& element) AE_NO_TSAN -#endif - { -#ifndef NDEBUG - ReentrantGuard guard(this->enqueuing); -#endif - - // High-level pseudocode (assuming we're allowed to alloc a new block): - // If room in tail block, add to tail - // Else check next block - // If next block is not the head block, enqueue on next block - // Else create a new block and enqueue there - // Advance tail to the block we just enqueued to - - Block* tailBlock_ = tailBlock.load(); - size_t blockFront = tailBlock_->localFront; - size_t blockTail = tailBlock_->tail.load(); - - size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; - if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { - fence(memory_order_acquire); - // This block has room for at least one more element - char* location = tailBlock_->data + blockTail * sizeof(T); -#if MOODYCAMEL_HAS_EMPLACE - new (location) T(std::forward<Args>(args)...); -#else - new (location) T(std::forward<U>(element)); -#endif - - fence(memory_order_release); - tailBlock_->tail = nextBlockTail; - } - else { - fence(memory_order_acquire); - if (tailBlock_->next.load() != frontBlock) { - // Note that the reason we can't advance to the frontBlock and start adding new entries there - // is because if we did, then dequeue would stay in that block, eventually reading the new values, - // instead of advancing to the next full block (whose values were enqueued first and so should be - // consumed first). - - fence(memory_order_acquire); // Ensure we get latest writes if we got the latest frontBlock - - // tailBlock is full, but there's a free block ahead, use it - Block* tailBlockNext = tailBlock_->next.load(); - size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); - nextBlockTail = tailBlockNext->tail.load(); - fence(memory_order_acquire); - - // This block must be empty since it's not the head block and we - // go through the blocks in a circle - assert(nextBlockFront == nextBlockTail); - tailBlockNext->localFront = nextBlockFront; - - char* location = tailBlockNext->data + nextBlockTail * sizeof(T); -#if MOODYCAMEL_HAS_EMPLACE - new (location) T(std::forward<Args>(args)...); -#else - new (location) T(std::forward<U>(element)); -#endif - - tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; - - fence(memory_order_release); - tailBlock = tailBlockNext; - } - else if (canAlloc == CanAlloc) { - // tailBlock is full and there's no free block ahead; create a new block - auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; - auto newBlock = make_block(newBlockSize); - if (newBlock == nullptr) { - // Could not allocate a block! - return false; - } - largestBlockSize = newBlockSize; - -#if MOODYCAMEL_HAS_EMPLACE - new (newBlock->data) T(std::forward<Args>(args)...); -#else - new (newBlock->data) T(std::forward<U>(element)); -#endif - assert(newBlock->front == 0); - newBlock->tail = newBlock->localTail = 1; - - newBlock->next = tailBlock_->next.load(); - tailBlock_->next = newBlock; - - // Might be possible for the dequeue thread to see the new tailBlock->next - // *without* seeing the new tailBlock value, but this is OK since it can't - // advance to the next block until tailBlock is set anyway (because the only - // case where it could try to read the next is if it's already at the tailBlock, - // and it won't advance past tailBlock in any circumstance). - - fence(memory_order_release); - tailBlock = newBlock; - } - else if (canAlloc == CannotAlloc) { - // Would have had to allocate a new block to enqueue, but not allowed - return false; - } - else { - assert(false && "Should be unreachable code"); - return false; - } - } - - return true; - } - - - // Disable copying - ReaderWriterQueue(ReaderWriterQueue const&) { } - - // Disable assignment - ReaderWriterQueue& operator=(ReaderWriterQueue const&) { } - - - AE_FORCEINLINE static size_t ceilToPow2(size_t x) - { - // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - --x; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - for (size_t i = 1; i < sizeof(size_t); i <<= 1) { - x |= x >> (i << 3); - } - ++x; - return x; - } - - template<typename U> - static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN - { - const std::size_t alignment = std::alignment_of<U>::value; - return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment; - } -private: -#ifndef NDEBUG - struct ReentrantGuard - { - AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) - : inSection(_inSection) - { - assert(!inSection && "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one thread at a time may hold the producer or consumer role)"); - inSection = true; - } - - AE_NO_TSAN ~ReentrantGuard() { inSection = false; } - - private: - ReentrantGuard& operator=(ReentrantGuard const&); - - private: - weak_atomic<bool>& inSection; - }; -#endif - - struct Block - { - // Avoid false-sharing by putting highly contended variables on their own cache lines - weak_atomic<size_t> front; // (Atomic) Elements are read from here - size_t localTail; // An uncontended shadow copy of tail, owned by the consumer - - char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)]; - weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here - size_t localFront; - - char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) - sizeof(size_t)]; // next isn't very contended, but we don't want it on the same cache line as tail (which is) - weak_atomic<Block*> next; // (Atomic) - - char* data; // Contents (on heap) are aligned to T's alignment - - const size_t sizeMask; - - - // size must be a power of two (and greater than 0) - AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) - : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1), rawThis(_rawThis) - { - } - - private: - // C4512 - Assignment operator could not be generated - Block& operator=(Block const&); - - public: - char* rawThis; - }; - - - static Block* make_block(size_t capacity) AE_NO_TSAN - { - // Allocate enough memory for the block itself, as well as all the elements it will contain - auto size = sizeof(Block) + std::alignment_of<Block>::value - 1; - size += sizeof(T) * capacity + std::alignment_of<T>::value - 1; - auto newBlockRaw = static_cast<char*>(std::malloc(size)); - if (newBlockRaw == nullptr) { - return nullptr; - } - - auto newBlockAligned = align_for<Block>(newBlockRaw); - auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block)); - return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); - } - -private: - weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block - - char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)]; - weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block - - size_t largestBlockSize; - -#ifndef NDEBUG - weak_atomic<bool> enqueuing; - mutable weak_atomic<bool> dequeuing; -#endif -}; - -// Like ReaderWriterQueue, but also providees blocking operations -template<typename T, size_t MAX_BLOCK_SIZE = 512> -class BlockingReaderWriterQueue -{ -private: - typedef ::moodycamel::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue; - -public: - explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN - : inner(size), sema(new spsc_sema::LightweightSemaphore()) - { } - - BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN - : inner(std::move(other.inner)), sema(std::move(other.sema)) - { } - - BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN - { - std::swap(sema, other.sema); - std::swap(inner, other.inner); - return *this; - } - - - // Enqueues a copy of element if there is room in the queue. - // Returns true if the element was enqueued, false otherwise. - // Does not allocate memory. - AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN - { - if (inner.try_enqueue(element)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a moved copy of element if there is room in the queue. - // Returns true if the element was enqueued, false otherwise. - // Does not allocate memory. - AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN - { - if (inner.try_enqueue(std::forward<T>(element))) { - sema->signal(); - return true; - } - return false; - } - -#if MOODYCAMEL_HAS_EMPLACE - // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). - template<typename... Args> - AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN - { - if (inner.try_emplace(std::forward<Args>(args)...)) { - sema->signal(); - return true; - } - return false; - } -#endif - - - // Enqueues a copy of element on the queue. - // Allocates an additional block of memory if needed. - // Only fails (returns false) if memory allocation fails. - AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN - { - if (inner.enqueue(element)) { - sema->signal(); - return true; - } - return false; - } - - // Enqueues a moved copy of element on the queue. - // Allocates an additional block of memory if needed. - // Only fails (returns false) if memory allocation fails. - AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN - { - if (inner.enqueue(std::forward<T>(element))) { - sema->signal(); - return true; - } - return false; - } - -#if MOODYCAMEL_HAS_EMPLACE - // Like enqueue() but with emplace semantics (i.e. construct-in-place). - template<typename... Args> - AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN - { - if (inner.emplace(std::forward<Args>(args)...)) { - sema->signal(); - return true; - } - return false; - } -#endif - - - // Attempts to dequeue an element; if the queue is empty, - // returns false instead. If the queue has at least one element, - // moves front to result using operator=, then returns true. - template<typename U> - bool try_dequeue(U& result) AE_NO_TSAN - { - if (sema->tryWait()) { - bool success = inner.try_dequeue(result); - assert(success); - AE_UNUSED(success); - return true; - } - return false; - } - - - // Attempts to dequeue an element; if the queue is empty, - // waits until an element is available, then dequeues it. - template<typename U> - void wait_dequeue(U& result) AE_NO_TSAN - { - while (!sema->wait()); - bool success = inner.try_dequeue(result); - AE_UNUSED(result); - assert(success); - AE_UNUSED(success); - } - - - // Attempts to dequeue an element; if the queue is empty, - // waits until an element is available up to the specified timeout, - // then dequeues it and returns true, or returns false if the timeout - // expires before an element can be dequeued. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - template<typename U> - bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN - { - if (!sema->wait(timeout_usecs)) { - return false; - } - bool success = inner.try_dequeue(result); - AE_UNUSED(result); - assert(success); - AE_UNUSED(success); - return true; - } - - -#if __cplusplus > 199711L || _MSC_VER >= 1700 - // Attempts to dequeue an element; if the queue is empty, - // waits until an element is available up to the specified timeout, - // then dequeues it and returns true, or returns false if the timeout - // expires before an element can be dequeued. - // Using a negative timeout indicates an indefinite timeout, - // and is thus functionally equivalent to calling wait_dequeue. - template<typename U, typename Rep, typename Period> - inline bool wait_dequeue_timed(U& result, std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN - { - return wait_dequeue_timed(result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); - } -#endif - - - // Returns a pointer to the front element in the queue (the one that - // would be removed next by a call to `try_dequeue` or `pop`). If the - // queue appears empty at the time the method is called, nullptr is - // returned instead. - // Must be called only from the consumer thread. - AE_FORCEINLINE T* peek() const AE_NO_TSAN - { - return inner.peek(); - } - - // Removes the front element from the queue, if any, without returning it. - // Returns true on success, or false if the queue appeared empty at the time - // `pop` was called. - AE_FORCEINLINE bool pop() AE_NO_TSAN - { - if (sema->tryWait()) { - bool result = inner.pop(); - assert(result); - AE_UNUSED(result); - return true; - } - return false; - } - - // Returns the approximate number of items currently in the queue. - // Safe to call from both the producer and consumer threads. - AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN - { - return sema->availableApprox(); - } - - // Returns the total number of items that could be enqueued without incurring - // an allocation when this queue is empty. - // Safe to call from both the producer and consumer threads. - // - // NOTE: The actual capacity during usage may be different depending on the consumer. - // If the consumer is removing elements concurrently, the producer cannot add to - // the block the consumer is removing from until it's completely empty, except in - // the case where the producer was writing to the same block the consumer was - // reading from the whole time. - AE_FORCEINLINE size_t max_capacity() const { - return inner.max_capacity(); - } - -private: - // Disable copying & assignment - BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) { } - BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) { } - -private: - ReaderWriterQueue inner; - std::unique_ptr<spsc_sema::LightweightSemaphore> sema; -}; - -} // end namespace moodycamel - -#ifdef AE_VCPP -#pragma warning(pop) -#endif diff --git a/Headers/reconstruction.h b/Headers/reconstruction.h index c695b99..5c45e87 100644 --- a/Headers/reconstruction.h +++ b/Headers/reconstruction.h @@ -2,39 +2,59 @@ #define RECONSTRUCTION_H +#include <QMutex> +#include <QString> #include "TH2D.h" #include "setup.h" -/** - * @brief Project cones onto the sphereical surface and update the image. - * Implementation based on - * https://www.nature.com/articles/s41598-020-58857-z - * - * @param config Input. Global settings. - * @param histo Input and output. Image to be updated. - * @param counts Input and output. Number of cones (events). - * @param first Input. First iterator to the vector of cones. - * @param last Input. Last iterator to the vector of cones. - * @return 0 - */ -int addCones(const Setup* config, TH2D* histo, ULong64_t& counts, - std::vector<Cone>::const_iterator first, - std::vector<Cone>::const_iterator last); +class RecoImage +{ +public: + RecoImage(const Setup* config_); + ~RecoImage(); + QMutex mMutex; + ULong64_t counts=0; -/** - * @brief Project cones onto the sphereical surface and update the image. - * Imeplemention based on - * https://www.overleaf.com/read/hjdvrcrjcvpx - * - * @param config Input. Global settings. - * @param histo Input and output. Image to be updated. - * @param counts Input and output. Number of cones (events). - * @param first Input. First iterator of the vector of cones. - * @param last Input. Last iterator of the vector of cones. - * @return 0 - */ -int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts, - std::vector<Cone>::const_iterator first, - std::vector<Cone>::const_iterator last); + void clear(); + bool saveImage(const QString& fileName); + void updateImage(std::vector<Cone>::const_iterator first, + std::vector<Cone>::const_iterator last, + const bool normailzed=true); +private: + const Setup* config; + TH2D* hist; + void createHist(); + bool hist2txt(const QString& fileName); + + /** + * @brief Project cones onto the sphereical surface and update the image. + * Implementation based on + * https://www.nature.com/articles/s41598-020-58857-z + * + * @param config Input. Global settings. + * @param histo Input and output. Image to be updated. + * @param counts Input and output. Number of cones (events). + * @param first Input. First iterator to the vector of cones. + * @param last Input. Last iterator to the vector of cones. + * @return 0 + */ + int addCones(std::vector<Cone>::const_iterator first, + std::vector<Cone>::const_iterator last); + + /** + * @brief Project cones onto the sphereical surface and update the image. + * Imeplemention based on + * https://www.overleaf.com/read/hjdvrcrjcvpx + * + * @param config Input. Global settings. + * @param histo Input and output. Image to be updated. + * @param counts Input and output. Number of cones (events). + * @param first Input. First iterator of the vector of cones. + * @param last Input. Last iterator of the vector of cones. + * @return 0 + */ + int addConesNormalized(std::vector<Cone>::const_iterator first, + std::vector<Cone>::const_iterator last); +}; #endif // RECONSTRUCTION_H diff --git a/Headers/setup.h b/Headers/setup.h index fdee368..4063ea9 100644 --- a/Headers/setup.h +++ b/Headers/setup.h @@ -7,10 +7,6 @@ #include <stdexcept> #include <QString> -#include <QStringRef> - -#include "readerwriterqueue/atomicops.h" -#include "readerwriterqueue/readerwriterqueue.h" class Vector3D { @@ -115,7 +111,7 @@ public: // queue capacity ulong capacity=1024; // max number of cones to process - ulong maxN=2000; + ulong maxN=1000; }; class Cone @@ -160,6 +156,4 @@ public: } }; -typedef moodycamel::BlockingReaderWriterQueue<Cone> SPSC; - #endif // SETUP_H diff --git a/Headers/worker.h b/Headers/worker.h index 649cd0d..d1e752d 100644 --- a/Headers/worker.h +++ b/Headers/worker.h @@ -3,26 +3,31 @@ #include <QObject> #include <QThread> -#include "setup.h" +#include "reconstruction.h" class Worker : public QThread { Q_OBJECT const Setup* config; - SPSC* coneQueue; + RecoImage* image; void run() override; - bool stop=false; + ulong localCounts=0; + bool stopped=true; + bool exitted=false; public: // explicit Worker(QObject *parent = nullptr); - Worker(QObject *parent, const Setup* config_, SPSC* coneQueue_) : + Worker(QObject *parent, const Setup* config_, RecoImage* image_) : QThread(parent), config(config_), - coneQueue(coneQueue_) + image(image_) {} //signals: public slots: + void handleStart(); + void handleStop(); + void handleClear(); void stopExecution(); }; diff --git a/MainWindow.ui b/MainWindow.ui index b17f128..5981cd0 100644 --- a/MainWindow.ui +++ b/MainWindow.ui @@ -68,6 +68,7 @@ <addaction name="actionStart"/> <addaction name="actionStop"/> <addaction name="actionClear"/> + <addaction name="actionScreenshot"/> </widget> <widget class="QStatusBar" name="statusBar"/> <action name="actionOpen"> @@ -89,6 +90,10 @@ </property> </action> <action name="actionSave_As"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/icons/saveas.png</normaloff>:/icons/icons/saveas.png</iconset> + </property> <property name="text"> <string>Save As</string> </property> @@ -138,6 +143,15 @@ <string>About</string> </property> </action> + <action name="actionScreenshot"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/icons/screenshot.svg</normaloff>:/icons/icons/screenshot.svg</iconset> + </property> + <property name="text"> + <string>Screenshot</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> diff --git a/README.md b/README.md index a1f4f78..223a076 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```bash mkdir -p build cd build - qmake ../imagerQt.pro + qmake CONFIG+=realease ../imagerQt.pro make ``` - Run: diff --git a/Sources/MainWindow.cpp b/Sources/MainWindow.cpp index 3a0662c..754cf3f 100644 --- a/Sources/MainWindow.cpp +++ b/Sources/MainWindow.cpp @@ -16,11 +16,10 @@ #include <QTimer> #include <QDebug> #include <QWindow> - +#include <QFileDialog> +#include <QDateTime> #include <QMessageBox> -#include "reconstruction.h" - MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) @@ -30,27 +29,33 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionExit, &QAction::triggered, this, &MainWindow::handleClose); connect(ui->actionSave, &QAction::triggered, this, &MainWindow::handleSave); connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::handleSaveAs); - - connect(ui->actionStart, &QAction::triggered, this, &MainWindow::handleStart); - connect(ui->actionStop, &QAction::triggered, this, &MainWindow::handleStop); - connect(ui->actionClear, &QAction::triggered, this, &MainWindow::handleClear); - + connect(ui->actionScreenshot, &QAction::triggered, this, &MainWindow::handleScreenshot); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::handleAbout); config = new Setup(Vector3D(SOURCE_X, SOURCE_Y, SOURCE_Z)); - coneQueue = new SPSC(config->capacity); + image = new RecoImage(config); + createGridlines(); + createCountsLabel(); - workerThread = new Worker(this, config, coneQueue); + // update image on worker thread + workerThread = new Worker(this, config, image); connect(workerThread, &Worker::finished, workerThread, &QObject::deleteLater); connect(workerThread, &Worker::finished, this, &MainWindow::notifyThreadFinished); + connect(ui->actionStart, &QAction::triggered, workerThread, &Worker::handleStart); + connect(ui->actionStop, &QAction::triggered, workerThread, &Worker::handleStop); + connect(ui->actionClear, &QAction::triggered, workerThread, &Worker::handleClear); connect(this, &MainWindow::threadStopped, workerThread, &Worker::stopExecution); workerThread->start(); - createHist(); - createGridlines(); - createCountsLabel(); + // update every 0.1 second + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::redraw)); + timer->start(100); + + setWindowTitle(tr("Back projection")); ui->canvas->Canvas()->Modified(); ui->canvas->Canvas()->Update(); + ui->statusBar->showMessage("Ready."); } void MainWindow::changeEvent(QEvent *e) @@ -78,22 +83,21 @@ void MainWindow::closeEvent (QCloseEvent *event) if (resBtn != QMessageBox::Yes) { event->ignore(); } else { - finished=true; if (workerThread) { // qDebug() << "Exiting thread.."; - aborted=true; // send stop signal to worker thread + aborted=true; emit threadStopped(); } -// event->accept(); + event->accept(); } } MainWindow::~MainWindow() { delete ui; - if (coneQueue) - delete coneQueue; + if (image) + delete image; if (config) delete config; for (int i = 0; i < longitudes.size(); ++i) { @@ -108,74 +112,6 @@ MainWindow::~MainWindow() } } -void MainWindow::run() -{ - std::vector<Cone> cones(config->chuckSize); - bool emptyQueue = false; - while (!finished) - { - if (!stop) - { - int i(0); - while (i < config->chuckSize) - { - emptyQueue = !(coneQueue->wait_dequeue_timed(cones[i], - std::chrono::milliseconds(10))); - if (emptyQueue) break; - i++; - } - // update image - updateImage(cones.cbegin(), cones.cbegin() + i, true); - if (emptyQueue && threadExecutionFinished) - { - // stop updating after this final run - finished = true; -// qDebug() << "Processing finished."; - QMessageBox messageBox; - messageBox.setText("Image reconstruction finished."); - messageBox.setWindowTitle("Finished"); - messageBox.exec(); - break; - } - // gSystem->Sleep(100); - } - QApplication::processEvents(); - } -} - -void MainWindow::updateImage(std::vector<Cone>::const_iterator first, - std::vector<Cone>::const_iterator last, - const bool& normalized) -{ - if (first == last) - return; - // update image - if (normalized) - addConesNormalized(config, hist, counts, first, last); - else - addCones(config, hist, counts, first, last); - // redraw - redraw(); -} - -void MainWindow::createHist() -{ - hist = new TH2D("ROI", " ; Azimuth (degree); Elevation (degree)", - config->phiBins, -180, 180, - config->thetaBins, -90, 90); - // init image - for (int i = 0; i < config->phiBins; i++) - { - for (int j = 0; j < config->thetaBins; j++) - { - hist->SetBinContent(i+1, j+1, 0); - } - } - gStyle->SetOptStat(0); - hist->GetZaxis()->SetLabelSize(0.02); - hist->Draw("z aitoff"); - -} void MainWindow::createGridlines() { @@ -234,7 +170,7 @@ void MainWindow::createGridlines() void MainWindow::createCountsLabel() { - std::string strtmp = "Total counts: " + std::to_string(counts); + std::string strtmp = "Total counts: " + std::to_string(image->counts); countsText = new TText(0.7, 0.92, strtmp.c_str()); countsText->SetTextSizePixels(5); countsText->SetNDC(kTRUE); @@ -261,10 +197,12 @@ void MainWindow::aitoff2xy(const double& l, const double& b, double &Al, double void MainWindow::redraw() { - std::string strtmp = "Total counts: " + std::to_string(counts); + image->mMutex.lock(); + std::string strtmp = "Total counts: " + std::to_string(image->counts); countsText->SetText(0.7, 0.92, strtmp.c_str()); ui->canvas->Canvas()->Modified(); ui->canvas->Canvas()->Update(); + image->mMutex.unlock(); } void MainWindow::handleOpen() @@ -272,53 +210,78 @@ void MainWindow::handleOpen() qDebug() << "Open file clicked"; } -void MainWindow::handleSave() +bool MainWindow::handleSave() { - qDebug() << "Save clicked"; +// qDebug() << "Save clicked"; + if (curFile.isEmpty()) { + return handleSaveAs(); + } else { + return saveCanvas(curFile); + } } -void MainWindow::handleSaveAs() +bool MainWindow::handleSaveAs() { - qDebug() << "Save as clicked"; + QString fileName = QFileDialog::getSaveFileName(this, + tr("Save Image"), "", + tr("PNG file (*.png);;ROOT file (*.root);;C file (*.C);;text file (*.txt)")); + if (fileName.isEmpty()) + return false; + else { + if (saveCanvas(fileName)) + curFile = fileName; + } } +bool MainWindow::saveCanvas(const QString& fileName) +{ + QFileInfo fileInfo(fileName); + QString ext=fileInfo.completeSuffix(); + bool saved=false; + if (ext == "root" || ext == "png" || ext == "C") { +// qDebug() << "Save as " << ext << " file."; + image->mMutex.lock(); + ui->canvas->Canvas()->SaveAs(fileName.toLocal8Bit().constData()); + image->mMutex.unlock(); + saved = true; + } + else if (ext == "txt") { + saved = image->saveImage(fileName); + } + if (saved) + { + ui->statusBar->showMessage(QString("Image saved to: %1").arg(fileName)); + } + return saved; +} void MainWindow::handleClose() { // qDebug() << "Close clicked"; this->close(); } -void MainWindow::handleStart() -{ - stop=false; -// qDebug() << "Start clicked"; -} - -void MainWindow::handleStop() +void MainWindow::handleScreenshot() { - stop=true; -// qDebug() << "Stop clicked"; + QDateTime dateTime = QDateTime::currentDateTime(); + QString currentDateTime = dateTime.toString("yyyy-MM-dd-HH-mm-ss"); + QString fileName = QString("Screenshot-%1.png").arg(currentDateTime); + saveCanvas(fileName); } - -void MainWindow::handleClear() -{ - counts = 0; - hist->Reset(); - redraw(); -// qDebug() << "Clear clicked"; -} - void MainWindow::handleAbout() { - QMessageBox messageBox; - messageBox.setText("This application is created using Qt 5.13.2. Source code is available at Gitlab."); - messageBox.setWindowTitle("About"); - messageBox.exec(); + QMessageBox::about(this, tr("About Application"), + tr("This application is created using Qt 5.13.2. Source code is available at Gitlab.")); // qDebug() << "About clicked"; } void MainWindow::notifyThreadFinished() { // pop up a message box indicating processing is finished. - threadExecutionFinished = true; + if (!aborted) + { + QMessageBox messageBox; + messageBox.setText("Image reconstruction finished."); + messageBox.setWindowTitle("Finished"); + messageBox.exec(); + } } diff --git a/Sources/main.cpp b/Sources/main.cpp index 6ce027c..06a3ef8 100644 --- a/Sources/main.cpp +++ b/Sources/main.cpp @@ -18,13 +18,6 @@ int main(int argc, char *argv[]) w.resize(w.sizeHint()); w.show(); w.resize(700,700); - w.run(); - if (!w.aborted) { - return a.exec(); - } - else { - gSystem->Sleep(500); - return 0; - } + return a.exec(); } diff --git a/Sources/reconstruction.cpp b/Sources/reconstruction.cpp index 06ec2b0..fbb6cc7 100644 --- a/Sources/reconstruction.cpp +++ b/Sources/reconstruction.cpp @@ -1,7 +1,109 @@ +#include "TStyle.h" +#include "TAxis.h" +#include <fstream> +#include <QFileInfo> +#include <QDebug> #include "reconstruction.h" -int addCones(const Setup* config, TH2D* histo, ULong64_t& counts, - std::vector<Cone>::const_iterator first, +RecoImage::RecoImage(const Setup* config_): + config(config_), + mMutex() +{ + counts=0; + createHist(); +} + +RecoImage::~RecoImage() +{ + delete hist; +} + +void RecoImage::createHist() +{ + hist = new TH2D("ROI", " ; Azimuth (degree); Elevation (degree)", + config->phiBins, -180, 180, + config->thetaBins, -90, 90); + // init image + for (int i = 0; i < config->phiBins; i++) + { + for (int j = 0; j < config->thetaBins; j++) + { + hist->SetBinContent(i+1, j+1, 0); + } + } + gStyle->SetOptStat(0); + hist->GetZaxis()->SetLabelSize(0.02); + hist->Draw("z aitoff"); + +} + +void RecoImage::clear() +{ + mMutex.lock(); + counts=0; + hist->Reset(); + mMutex.unlock(); +} + +bool RecoImage::saveImage(const QString& fileName) +{ + // save image + QFileInfo fileInfo(fileName); + QString ext=fileInfo.completeSuffix(); + if(ext == "txt"){ +// qDebug() << "Save as text file"; + mMutex.lock(); + bool saved=hist2txt(fileName); + mMutex.unlock(); + return saved; + } + else { + return false; + } +} + +bool RecoImage::hist2txt(const QString& fileName) +{ + std::ofstream outf(fileName.toLocal8Bit().constData()); + if (!outf.good()) + { + return false; + } + + outf << " Phi Theta Content\n"; + for (int i = 1; i <= hist->GetNbinsX(); i++) + { + for (int j = 1; j <= hist->GetNbinsY(); j++) + { + outf << std::fixed << std::setprecision(2) + << std::setw(8) << ((TAxis*)hist->GetXaxis())->GetBinCenter(i) + << std::setw(8) << ((TAxis*)hist->GetYaxis())->GetBinCenter(j) + << std::fixed << std::setprecision(8) + << std::setw(13) << hist->GetBinContent(i,j) << '\n'; + } + } +// qDebug() << "Text file saved."; +} + +void RecoImage::updateImage(std::vector<Cone>::const_iterator first, + std::vector<Cone>::const_iterator last, + const bool normalized) +{ + if (first==last) + return; + else if (normalized) { + mMutex.lock(); + addConesNormalized(first, last); + mMutex.unlock(); + } + else { + mMutex.lock(); + addCones(first, last); + mMutex.unlock(); + } +} + +int RecoImage::addCones(std::vector<Cone>::const_iterator first, std::vector<Cone>::const_iterator last) { // project cones onto the spherical surface @@ -30,8 +132,8 @@ int addCones(const Setup* config, TH2D* histo, ULong64_t& counts, sgmb2 = std::pow((ray/(ray*ray) + k->axis/(k->axis*k->axis)- (ray+k->axis)/(ray*k->axis))*config->sgmpos * beta, 2); sgmb2+= std::pow((ray/(ray*k->axis)-k->axis/(k->axis*k->axis))*config->sgmpos * beta, 2); - histo->SetBinContent(j+1, i+1, - histo->GetBinContent(j+1, i+1) + + hist->SetBinContent(j+1, i+1, + hist->GetBinContent(j+1, i+1) + std::exp(-std::pow((std::pow(beta, config->order)-std::pow(alpha, config->order)), 2)/ (2*std::pow(config->order, 2)* (std::pow(alpha,2*config->order-2)*sgma2 +std::pow(beta, 2*config->order-2)*sgmb2)))); @@ -39,12 +141,10 @@ int addCones(const Setup* config, TH2D* histo, ULong64_t& counts, } } counts += (last - first); - return 0; } -int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts, - std::vector<Cone>::const_iterator first, +int RecoImage::addConesNormalized(std::vector<Cone>::const_iterator first, std::vector<Cone>::const_iterator last) { // project cones onto the spherical surface @@ -92,7 +192,7 @@ int addConesNormalized(const Setup* config, TH2D* histo, ULong64_t& counts, { for (int j = 0; j < config->phiBins; j++) { - histo->SetBinContent(j+1, i+1, (histo->GetBinContent(j+1, i+1)*(counts - 1) + probDist[i][j] / summ) / counts); + hist->SetBinContent(j+1, i+1, (hist->GetBinContent(j+1, i+1)*(counts - 1) + probDist[i][j] / summ) / counts); } } } diff --git a/Sources/worker.cpp b/Sources/worker.cpp index 70c142b..819b09e 100644 --- a/Sources/worker.cpp +++ b/Sources/worker.cpp @@ -19,31 +19,46 @@ void Worker::run() QTextStream in(&conefile); // skip header (first line) in.readLineInto(&line); - ulong counts(0); - while(!stop) { - //some work to get data (read board or file) - int i(0); - while (!stop && i < config->chuckSize && in.readLineInto(&line)) - { - coneQueue->enqueue(Cone(line)); - i++; - } - counts+=i; - if (stop || line.isNull() || counts >= config->maxN) - { - break; - } - - while (!stop && coneQueue->size_approx() >= config->capacity) + std::vector<Cone> cones(config->chuckSize, Cone()); + while(!exitted) + { + if (!stopped) { - gSystem->Sleep(50); + //some work to get data (read board or file) + int i(0); + while (!exitted && i < config->chuckSize && in.readLineInto(&line)) + { + cones[i]=Cone(line); + i++; + } + localCounts+=i; + // update image + image->updateImage(cones.cbegin(), cones.cbegin()+i, true); + if (exitted || line.isNull() || localCounts >= config->maxN) + break; } } conefile.close(); -// qDebug() << "Worker thread exited."; + qDebug() << "Worker thread exited."; +} + +void Worker::handleStart() +{ + stopped=false; +} + +void Worker::handleStop() +{ + stopped=true; } void Worker::stopExecution() { - stop = true; + exitted = true; +} + +void Worker::handleClear() +{ + localCounts = 0; + image->clear(); } diff --git a/icons/saveas.png b/icons/saveas.png new file mode 100755 index 0000000000000000000000000000000000000000..da6677488c5df9c4e95150ef58736d26dcf8bf27 GIT binary patch literal 18600 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 literal 0 HcmV?d00001 diff --git a/icons/screenshot.svg b/icons/screenshot.svg new file mode 100755 index 0000000..bc14041 --- /dev/null +++ b/icons/screenshot.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-e</title><circle cx="256" cy="272" r="64"/><path d="M456,144H373c-3,0-6.72-1.94-9.62-5l-27.28-42.8C325,80,320,80,302,80H210c-18,0-24,0-34.07,16.21L148.62,139c-2.22,2.42-5.34,5-8.62,5V128a8,8,0,0,0-8-8H92a8,8,0,0,0-8,8v16H56a24,24,0,0,0-24,24V408a24,24,0,0,0,24,24H456a24,24,0,0,0,24-24V168A24,24,0,0,0,456,144ZM260.51,367.9a96,96,0,1,1,91.39-91.39A96.11,96.11,0,0,1,260.51,367.9Z"/></svg> \ No newline at end of file diff --git a/imagerQt.pro b/imagerQt.pro index b43f37a..bb12e69 100644 --- a/imagerQt.pro +++ b/imagerQt.pro @@ -38,9 +38,6 @@ SOURCES += \ HEADERS += \ $$PWD/Headers/MainWindow.h \ $$PWD/Headers/qrootcanvas.h \ - $$PWD/Headers/readerwriterqueue/atomicops.h \ - $$PWD/Headers/readerwriterqueue/readerwritercircularbuffer.h \ - $$PWD/Headers/readerwriterqueue/readerwriterqueue.h \ $$PWD/Headers/reconstruction.h \ $$PWD/Headers/setup.h \ $$PWD/Headers/worker.h diff --git a/resources.qrc b/resources.qrc index 31f7f50..a6de455 100644 --- a/resources.qrc +++ b/resources.qrc @@ -7,6 +7,8 @@ <file>icons/open.svg</file> <file>icons/about.svg</file> <file>icons/save.svg</file> + <file>icons/saveas.png</file> + <file>icons/screenshot.svg</file> </qresource> <qresource prefix="/Data"> <file>Data/cones_all_channels.txt</file> -- GitLab