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