Sanitize a bit the submodules and make 3 of them in-tree; abseil and google benchmark left
This commit is contained in:
parent
1e7d744460
commit
cec6b3f3ba
17 changed files with 19809 additions and 26 deletions
12
.gitmodules
vendored
12
.gitmodules
vendored
|
|
@ -6,15 +6,3 @@
|
|||
path = external/benchmark
|
||||
url = https://github.com/google/benchmark
|
||||
shallow = true
|
||||
[submodule "external/readerwriterqueue"]
|
||||
path = external/readerwriterqueue
|
||||
url = https://github.com/cameron314/readerwriterqueue.git
|
||||
shallow = true
|
||||
[submodule "external/cnpy"]
|
||||
path = external/cnpy
|
||||
url = https://github.com/paulfd/cnpy.git
|
||||
shallow = true
|
||||
[submodule "external/Catch2"]
|
||||
path = external/Catch2
|
||||
url = https://github.com/catchorg/Catch2.git
|
||||
shallow = true
|
||||
|
|
|
|||
|
|
@ -66,10 +66,6 @@ if (WIN32)
|
|||
target_include_directories(sndfile INTERFACE "${WIN_SNDFILE_PATH}/include")
|
||||
endif()
|
||||
|
||||
# Export MoodyCamel's queue as a library
|
||||
add_library(readerwriterqueue INTERFACE)
|
||||
target_include_directories(readerwriterqueue INTERFACE "external/readerwriterqueue")
|
||||
|
||||
add_subdirectory(sfizz)
|
||||
|
||||
if (SFIZZ_CLIENTS)
|
||||
|
|
@ -77,9 +73,6 @@ add_subdirectory(clients)
|
|||
endif()
|
||||
|
||||
if (SFIZZ_TESTS)
|
||||
# Tests
|
||||
add_subdirectory(external/Catch2 EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(external/cnpy EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
1
external/Catch2
vendored
1
external/Catch2
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit af8b2538a62db34b3cec10ceb74e3d29bd06e37f
|
||||
2
external/abseil-cpp
vendored
2
external/abseil-cpp
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 97c1664b4bbab5f78fac2b151ab02656268fb34b
|
||||
Subproject commit ac78ffc3bc0a8b295cab9a03817760fd460df2a1
|
||||
2
external/benchmark
vendored
2
external/benchmark
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 7ee72863fdb1ccb2af5a011250b56af3f49b7511
|
||||
Subproject commit bf4f2ea0bd1180b34718ac26eb79b170a4f6290e
|
||||
1
external/cnpy
vendored
1
external/cnpy
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 34693799ada6cb84be3f588aabbe75f24d010e2f
|
||||
1
external/readerwriterqueue
vendored
1
external/readerwriterqueue
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 2ae710de996a1d02bbc7696b2cdff2c6078e76f8
|
||||
|
|
@ -51,7 +51,7 @@ if(UNIX)
|
|||
target_link_libraries(sfizz PUBLIC stdc++fs atomic)
|
||||
target_compile_options(sfizz PRIVATE -fno-rtti -fno-exceptions)
|
||||
endif(UNIX)
|
||||
target_link_libraries(sfizz PUBLIC readerwriterqueue absl::strings)
|
||||
target_link_libraries(sfizz PUBLIC absl::strings)
|
||||
target_link_libraries(sfizz PRIVATE sndfile absl::flat_hash_map)
|
||||
|
||||
add_library(sfizz::parser ALIAS sfizz_parser)
|
||||
|
|
|
|||
676
sfizz/atomicops.h
Normal file
676
sfizz/atomicops.h
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
// ©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 <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() { }
|
||||
#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)
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
const long maxLong = 0x7fffffff;
|
||||
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
CloseHandle(m_hSema);
|
||||
}
|
||||
|
||||
void wait() AE_NO_TSAN
|
||||
{
|
||||
const unsigned long infinite = 0xffffffff;
|
||||
WaitForSingleObject(m_hSema, infinite);
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
|
||||
return WaitForSingleObject(m_hSema, 0) != RC_WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
bool timed_wait(std::uint64_t usecs) AE_NO_TSAN
|
||||
{
|
||||
const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
|
||||
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) != RC_WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void signal(int count = 1) AE_NO_TSAN
|
||||
{
|
||||
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)
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
semaphore_destroy(mach_task_self(), m_sema);
|
||||
}
|
||||
|
||||
void wait() AE_NO_TSAN
|
||||
{
|
||||
semaphore_wait(m_sema);
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
return timed_wait(0);
|
||||
}
|
||||
|
||||
bool timed_wait(std::int64_t timeout_usecs) AE_NO_TSAN
|
||||
{
|
||||
mach_timespec_t ts;
|
||||
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
|
||||
ts.tv_nsec = (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_OPERATION_TIMED_OUT && rc != KERN_ABORTED;
|
||||
}
|
||||
|
||||
void signal() AE_NO_TSAN
|
||||
{
|
||||
semaphore_signal(m_sema);
|
||||
}
|
||||
|
||||
void signal(int count) AE_NO_TSAN
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
semaphore_signal(m_sema);
|
||||
}
|
||||
}
|
||||
};
|
||||
#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)
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
sem_init(&m_sema, 0, initialCount);
|
||||
}
|
||||
|
||||
AE_NO_TSAN ~Semaphore()
|
||||
{
|
||||
sem_destroy(&m_sema);
|
||||
}
|
||||
|
||||
void 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);
|
||||
}
|
||||
|
||||
bool try_wait() AE_NO_TSAN
|
||||
{
|
||||
int rc;
|
||||
do {
|
||||
rc = sem_trywait(&m_sema);
|
||||
} while (rc == -1 && errno == EINTR);
|
||||
return !(rc == -1 && errno == EAGAIN);
|
||||
}
|
||||
|
||||
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 += usecs / usecs_in_1_sec;
|
||||
ts.tv_nsec += (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 == -1 && errno == ETIMEDOUT);
|
||||
}
|
||||
|
||||
void signal() AE_NO_TSAN
|
||||
{
|
||||
sem_post(&m_sema);
|
||||
}
|
||||
|
||||
void signal(int count) AE_NO_TSAN
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
sem_post(&m_sema);
|
||||
}
|
||||
}
|
||||
};
|
||||
#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 = 10000;
|
||||
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)
|
||||
{
|
||||
m_sema.wait();
|
||||
return true;
|
||||
}
|
||||
if (m_sema.timed_wait(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)
|
||||
{
|
||||
assert(initialCount >= 0);
|
||||
}
|
||||
|
||||
bool tryWait() AE_NO_TSAN
|
||||
{
|
||||
if (m_count.load() > 0)
|
||||
{
|
||||
m_count.fetch_add_acquire(-1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void wait() AE_NO_TSAN
|
||||
{
|
||||
if (!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);
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t availableApprox() const AE_NO_TSAN
|
||||
{
|
||||
ssize_t count = m_count.load();
|
||||
return count > 0 ? 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
|
||||
906
sfizz/readerwriterqueue.h
Normal file
906
sfizz/readerwriterqueue.h
Normal file
|
|
@ -0,0 +1,906 @@
|
|||
// ©2013-2016 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 <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 (O(lg(n) times, amortized), 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
|
||||
|
||||
#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 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 maxSize 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 maxSize = 15)
|
||||
#ifndef NDEBUG
|
||||
: enqueuing(false)
|
||||
,dequeuing(false)
|
||||
#endif
|
||||
{
|
||||
assert(maxSize > 0);
|
||||
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(maxSize + 1); // We need a spare slot to fit maxSize 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 maxSize and applying a ceiling to the division gives us (after simplifying):
|
||||
size_t initialBlockCount = (maxSize + 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() 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;
|
||||
}
|
||||
|
||||
|
||||
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(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:
|
||||
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(0), localTail(0), tail(0), 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 enqueued to this block
|
||||
|
||||
char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];
|
||||
weak_atomic<Block*> tailBlock; // (Atomic) Elements are dequeued from this block
|
||||
|
||||
size_t largestBlockSize;
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool enqueuing;
|
||||
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 maxSize = 15) AE_NO_TSAN
|
||||
: inner(maxSize), 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;
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
{
|
||||
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() 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();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -24,6 +24,10 @@ set(SFIZZ_TEST_SOURCES
|
|||
RegionTriggersT.cpp
|
||||
)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
add_library(cnpy cnpy.cpp)
|
||||
target_link_libraries(cnpy PRIVATE ZLIB::ZLIB)
|
||||
|
||||
add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES})
|
||||
target_link_libraries(sfizz_tests PRIVATE sfizz)
|
||||
|
||||
|
|
@ -31,7 +35,7 @@ target_link_libraries(sfizz_tests PRIVATE sfizz)
|
|||
if(UNIX)
|
||||
target_link_libraries(sfizz_tests PRIVATE stdc++fs atomic)
|
||||
endif(UNIX)
|
||||
target_link_libraries(sfizz_tests PRIVATE Catch2::Catch2 absl::strings absl::str_format absl::flat_hash_map sndfile readerwriterqueue cnpy-static absl::span absl::algorithm)
|
||||
target_link_libraries(sfizz_tests PRIVATE absl::strings absl::str_format absl::flat_hash_map sndfile cnpy absl::span absl::algorithm)
|
||||
target_include_directories(sfizz_tests SYSTEM PRIVATE sources)
|
||||
|
||||
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||
17075
tests/catch2/catch.hpp
Normal file
17075
tests/catch2/catch.hpp
Normal file
File diff suppressed because it is too large
Load diff
62
tests/catch2/catch_reporter_automake.hpp
Normal file
62
tests/catch2/catch_reporter_automake.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Created by Justin R. Wilson on 2/19/2017.
|
||||
* Copyright 2017 Justin R. Wilson. All rights reserved.
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
#ifndef TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED
|
||||
#define TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED
|
||||
|
||||
// Don't #include any Catch headers here - we can assume they are already
|
||||
// included before this header.
|
||||
// This is not good practice in general but is necessary in this case so this
|
||||
// file can be distributed as a single header that works with the main
|
||||
// Catch single header.
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct AutomakeReporter : StreamingReporterBase<AutomakeReporter> {
|
||||
AutomakeReporter( ReporterConfig const& _config )
|
||||
: StreamingReporterBase( _config )
|
||||
{}
|
||||
|
||||
~AutomakeReporter() override;
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Reports test results in the format of Automake .trs files";
|
||||
}
|
||||
|
||||
void assertionStarting( AssertionInfo const& ) override {}
|
||||
|
||||
bool assertionEnded( AssertionStats const& /*_assertionStats*/ ) override { return true; }
|
||||
|
||||
void testCaseEnded( TestCaseStats const& _testCaseStats ) override {
|
||||
// Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR.
|
||||
stream << ":test-result: ";
|
||||
if (_testCaseStats.totals.assertions.allPassed()) {
|
||||
stream << "PASS";
|
||||
} else if (_testCaseStats.totals.assertions.allOk()) {
|
||||
stream << "XFAIL";
|
||||
} else {
|
||||
stream << "FAIL";
|
||||
}
|
||||
stream << ' ' << _testCaseStats.testInfo.name << '\n';
|
||||
StreamingReporterBase::testCaseEnded( _testCaseStats );
|
||||
}
|
||||
|
||||
void skipTest( TestCaseInfo const& testInfo ) override {
|
||||
stream << ":test-result: SKIP " << testInfo.name << '\n';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#ifdef CATCH_IMPL
|
||||
AutomakeReporter::~AutomakeReporter() {}
|
||||
#endif
|
||||
|
||||
CATCH_REGISTER_REPORTER( "automake", AutomakeReporter)
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED
|
||||
253
tests/catch2/catch_reporter_tap.hpp
Normal file
253
tests/catch2/catch_reporter_tap.hpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Created by Colton Wolkins on 2015-08-15.
|
||||
* Copyright 2015 Martin Moene. All rights reserved.
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
#ifndef TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
|
||||
#define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
|
||||
|
||||
|
||||
// Don't #include any Catch headers here - we can assume they are already
|
||||
// included before this header.
|
||||
// This is not good practice in general but is necessary in this case so this
|
||||
// file can be distributed as a single header that works with the main
|
||||
// Catch single header.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct TAPReporter : StreamingReporterBase<TAPReporter> {
|
||||
|
||||
using StreamingReporterBase::StreamingReporterBase;
|
||||
|
||||
~TAPReporter() override;
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Reports test results in TAP format, suitable for test harnesses";
|
||||
}
|
||||
|
||||
ReporterPreferences getPreferences() const override {
|
||||
return m_reporterPrefs;
|
||||
}
|
||||
|
||||
void noMatchingTestCases( std::string const& spec ) override {
|
||||
stream << "# No test cases matched '" << spec << "'" << std::endl;
|
||||
}
|
||||
|
||||
void assertionStarting( AssertionInfo const& ) override {}
|
||||
|
||||
bool assertionEnded( AssertionStats const& _assertionStats ) override {
|
||||
++counter;
|
||||
|
||||
stream << "# " << currentTestCaseInfo->name << std::endl;
|
||||
AssertionPrinter printer( stream, _assertionStats, counter );
|
||||
printer.print();
|
||||
|
||||
stream << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void testRunEnded( TestRunStats const& _testRunStats ) override {
|
||||
printTotals( _testRunStats.totals );
|
||||
stream << "\n" << std::endl;
|
||||
StreamingReporterBase::testRunEnded( _testRunStats );
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t counter = 0;
|
||||
class AssertionPrinter {
|
||||
public:
|
||||
AssertionPrinter& operator= ( AssertionPrinter const& ) = delete;
|
||||
AssertionPrinter( AssertionPrinter const& ) = delete;
|
||||
AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter )
|
||||
: stream( _stream )
|
||||
, result( _stats.assertionResult )
|
||||
, messages( _stats.infoMessages )
|
||||
, itMessage( _stats.infoMessages.begin() )
|
||||
, printInfoMessages( true )
|
||||
, counter(_counter)
|
||||
{}
|
||||
|
||||
void print() {
|
||||
itMessage = messages.begin();
|
||||
|
||||
switch( result.getResultType() ) {
|
||||
case ResultWas::Ok:
|
||||
printResultType( passedString() );
|
||||
printOriginalExpression();
|
||||
printReconstructedExpression();
|
||||
if ( ! result.hasExpression() )
|
||||
printRemainingMessages( Colour::None );
|
||||
else
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::ExpressionFailed:
|
||||
if (result.isOk()) {
|
||||
printResultType(passedString());
|
||||
} else {
|
||||
printResultType(failedString());
|
||||
}
|
||||
printOriginalExpression();
|
||||
printReconstructedExpression();
|
||||
if (result.isOk()) {
|
||||
printIssue(" # TODO");
|
||||
}
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::ThrewException:
|
||||
printResultType( failedString() );
|
||||
printIssue( "unexpected exception with message:" );
|
||||
printMessage();
|
||||
printExpressionWas();
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::FatalErrorCondition:
|
||||
printResultType( failedString() );
|
||||
printIssue( "fatal error condition with message:" );
|
||||
printMessage();
|
||||
printExpressionWas();
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::DidntThrowException:
|
||||
printResultType( failedString() );
|
||||
printIssue( "expected exception, got none" );
|
||||
printExpressionWas();
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::Info:
|
||||
printResultType( "info" );
|
||||
printMessage();
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::Warning:
|
||||
printResultType( "warning" );
|
||||
printMessage();
|
||||
printRemainingMessages();
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
printResultType( failedString() );
|
||||
printIssue( "explicitly" );
|
||||
printRemainingMessages( Colour::None );
|
||||
break;
|
||||
// These cases are here to prevent compiler warnings
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
printResultType( "** internal error **" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static Colour::Code dimColour() { return Colour::FileName; }
|
||||
|
||||
static const char* failedString() { return "not ok"; }
|
||||
static const char* passedString() { return "ok"; }
|
||||
|
||||
void printSourceInfo() const {
|
||||
Colour colourGuard( dimColour() );
|
||||
stream << result.getSourceInfo() << ":";
|
||||
}
|
||||
|
||||
void printResultType( std::string const& passOrFail ) const {
|
||||
if( !passOrFail.empty() ) {
|
||||
stream << passOrFail << ' ' << counter << " -";
|
||||
}
|
||||
}
|
||||
|
||||
void printIssue( std::string const& issue ) const {
|
||||
stream << " " << issue;
|
||||
}
|
||||
|
||||
void printExpressionWas() {
|
||||
if( result.hasExpression() ) {
|
||||
stream << ";";
|
||||
{
|
||||
Colour colour( dimColour() );
|
||||
stream << " expression was:";
|
||||
}
|
||||
printOriginalExpression();
|
||||
}
|
||||
}
|
||||
|
||||
void printOriginalExpression() const {
|
||||
if( result.hasExpression() ) {
|
||||
stream << " " << result.getExpression();
|
||||
}
|
||||
}
|
||||
|
||||
void printReconstructedExpression() const {
|
||||
if( result.hasExpandedExpression() ) {
|
||||
{
|
||||
Colour colour( dimColour() );
|
||||
stream << " for: ";
|
||||
}
|
||||
std::string expr = result.getExpandedExpression();
|
||||
std::replace( expr.begin(), expr.end(), '\n', ' ');
|
||||
stream << expr;
|
||||
}
|
||||
}
|
||||
|
||||
void printMessage() {
|
||||
if ( itMessage != messages.end() ) {
|
||||
stream << " '" << itMessage->message << "'";
|
||||
++itMessage;
|
||||
}
|
||||
}
|
||||
|
||||
void printRemainingMessages( Colour::Code colour = dimColour() ) {
|
||||
if (itMessage == messages.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// using messages.end() directly (or auto) yields compilation error:
|
||||
std::vector<MessageInfo>::const_iterator itEnd = messages.end();
|
||||
const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
|
||||
|
||||
{
|
||||
Colour colourGuard( colour );
|
||||
stream << " with " << pluralise( N, "message" ) << ":";
|
||||
}
|
||||
|
||||
for(; itMessage != itEnd; ) {
|
||||
// If this assertion is a warning ignore any INFO messages
|
||||
if( printInfoMessages || itMessage->type != ResultWas::Info ) {
|
||||
stream << " '" << itMessage->message << "'";
|
||||
if ( ++itMessage != itEnd ) {
|
||||
Colour colourGuard( dimColour() );
|
||||
stream << " and";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& stream;
|
||||
AssertionResult const& result;
|
||||
std::vector<MessageInfo> messages;
|
||||
std::vector<MessageInfo>::const_iterator itMessage;
|
||||
bool printInfoMessages;
|
||||
std::size_t counter;
|
||||
};
|
||||
|
||||
void printTotals( const Totals& totals ) const {
|
||||
if( totals.testCases.total() == 0 ) {
|
||||
stream << "1..0 # Skipped: No tests ran.";
|
||||
} else {
|
||||
stream << "1.." << counter;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef CATCH_IMPL
|
||||
TAPReporter::~TAPReporter() {}
|
||||
#endif
|
||||
|
||||
CATCH_REGISTER_REPORTER( "tap", TAPReporter )
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
|
||||
220
tests/catch2/catch_reporter_teamcity.hpp
Normal file
220
tests/catch2/catch_reporter_teamcity.hpp
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Created by Phil Nash on 19th December 2014
|
||||
* Copyright 2014 Two Blue Cubes Ltd. All rights reserved.
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
#ifndef TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
|
||||
#define TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
|
||||
|
||||
// Don't #include any Catch headers here - we can assume they are already
|
||||
// included before this header.
|
||||
// This is not good practice in general but is necessary in this case so this
|
||||
// file can be distributed as a single header that works with the main
|
||||
// Catch single header.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wpadded"
|
||||
#endif
|
||||
|
||||
namespace Catch {
|
||||
|
||||
struct TeamCityReporter : StreamingReporterBase<TeamCityReporter> {
|
||||
TeamCityReporter( ReporterConfig const& _config )
|
||||
: StreamingReporterBase( _config )
|
||||
{
|
||||
m_reporterPrefs.shouldRedirectStdOut = true;
|
||||
}
|
||||
|
||||
static std::string escape( std::string const& str ) {
|
||||
std::string escaped = str;
|
||||
replaceInPlace( escaped, "|", "||" );
|
||||
replaceInPlace( escaped, "'", "|'" );
|
||||
replaceInPlace( escaped, "\n", "|n" );
|
||||
replaceInPlace( escaped, "\r", "|r" );
|
||||
replaceInPlace( escaped, "[", "|[" );
|
||||
replaceInPlace( escaped, "]", "|]" );
|
||||
return escaped;
|
||||
}
|
||||
~TeamCityReporter() override;
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Reports test results as TeamCity service messages";
|
||||
}
|
||||
|
||||
void skipTest( TestCaseInfo const& /* testInfo */ ) override {
|
||||
}
|
||||
|
||||
void noMatchingTestCases( std::string const& /* spec */ ) override {}
|
||||
|
||||
void testGroupStarting( GroupInfo const& groupInfo ) override {
|
||||
StreamingReporterBase::testGroupStarting( groupInfo );
|
||||
stream << "##teamcity[testSuiteStarted name='"
|
||||
<< escape( groupInfo.name ) << "']\n";
|
||||
}
|
||||
void testGroupEnded( TestGroupStats const& testGroupStats ) override {
|
||||
StreamingReporterBase::testGroupEnded( testGroupStats );
|
||||
stream << "##teamcity[testSuiteFinished name='"
|
||||
<< escape( testGroupStats.groupInfo.name ) << "']\n";
|
||||
}
|
||||
|
||||
|
||||
void assertionStarting( AssertionInfo const& ) override {}
|
||||
|
||||
bool assertionEnded( AssertionStats const& assertionStats ) override {
|
||||
AssertionResult const& result = assertionStats.assertionResult;
|
||||
if( !result.isOk() ) {
|
||||
|
||||
ReusableStringStream msg;
|
||||
if( !m_headerPrintedForThisSection )
|
||||
printSectionHeader( msg.get() );
|
||||
m_headerPrintedForThisSection = true;
|
||||
|
||||
msg << result.getSourceInfo() << "\n";
|
||||
|
||||
switch( result.getResultType() ) {
|
||||
case ResultWas::ExpressionFailed:
|
||||
msg << "expression failed";
|
||||
break;
|
||||
case ResultWas::ThrewException:
|
||||
msg << "unexpected exception";
|
||||
break;
|
||||
case ResultWas::FatalErrorCondition:
|
||||
msg << "fatal error condition";
|
||||
break;
|
||||
case ResultWas::DidntThrowException:
|
||||
msg << "no exception was thrown where one was expected";
|
||||
break;
|
||||
case ResultWas::ExplicitFailure:
|
||||
msg << "explicit failure";
|
||||
break;
|
||||
|
||||
// We shouldn't get here because of the isOk() test
|
||||
case ResultWas::Ok:
|
||||
case ResultWas::Info:
|
||||
case ResultWas::Warning:
|
||||
CATCH_ERROR( "Internal error in TeamCity reporter" );
|
||||
// These cases are here to prevent compiler warnings
|
||||
case ResultWas::Unknown:
|
||||
case ResultWas::FailureBit:
|
||||
case ResultWas::Exception:
|
||||
CATCH_ERROR( "Not implemented" );
|
||||
}
|
||||
if( assertionStats.infoMessages.size() == 1 )
|
||||
msg << " with message:";
|
||||
if( assertionStats.infoMessages.size() > 1 )
|
||||
msg << " with messages:";
|
||||
for( auto const& messageInfo : assertionStats.infoMessages )
|
||||
msg << "\n \"" << messageInfo.message << "\"";
|
||||
|
||||
|
||||
if( result.hasExpression() ) {
|
||||
msg <<
|
||||
"\n " << result.getExpressionInMacro() << "\n"
|
||||
"with expansion:\n" <<
|
||||
" " << result.getExpandedExpression() << "\n";
|
||||
}
|
||||
|
||||
if( currentTestCaseInfo->okToFail() ) {
|
||||
msg << "- failure ignore as test marked as 'ok to fail'\n";
|
||||
stream << "##teamcity[testIgnored"
|
||||
<< " name='" << escape( currentTestCaseInfo->name )<< "'"
|
||||
<< " message='" << escape( msg.str() ) << "'"
|
||||
<< "]\n";
|
||||
}
|
||||
else {
|
||||
stream << "##teamcity[testFailed"
|
||||
<< " name='" << escape( currentTestCaseInfo->name )<< "'"
|
||||
<< " message='" << escape( msg.str() ) << "'"
|
||||
<< "]\n";
|
||||
}
|
||||
}
|
||||
stream.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void sectionStarting( SectionInfo const& sectionInfo ) override {
|
||||
m_headerPrintedForThisSection = false;
|
||||
StreamingReporterBase::sectionStarting( sectionInfo );
|
||||
}
|
||||
|
||||
void testCaseStarting( TestCaseInfo const& testInfo ) override {
|
||||
m_testTimer.start();
|
||||
StreamingReporterBase::testCaseStarting( testInfo );
|
||||
stream << "##teamcity[testStarted name='"
|
||||
<< escape( testInfo.name ) << "']\n";
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
void testCaseEnded( TestCaseStats const& testCaseStats ) override {
|
||||
StreamingReporterBase::testCaseEnded( testCaseStats );
|
||||
if( !testCaseStats.stdOut.empty() )
|
||||
stream << "##teamcity[testStdOut name='"
|
||||
<< escape( testCaseStats.testInfo.name )
|
||||
<< "' out='" << escape( testCaseStats.stdOut ) << "']\n";
|
||||
if( !testCaseStats.stdErr.empty() )
|
||||
stream << "##teamcity[testStdErr name='"
|
||||
<< escape( testCaseStats.testInfo.name )
|
||||
<< "' out='" << escape( testCaseStats.stdErr ) << "']\n";
|
||||
stream << "##teamcity[testFinished name='"
|
||||
<< escape( testCaseStats.testInfo.name ) << "' duration='"
|
||||
<< m_testTimer.getElapsedMilliseconds() << "']\n";
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
void printSectionHeader( std::ostream& os ) {
|
||||
assert( !m_sectionStack.empty() );
|
||||
|
||||
if( m_sectionStack.size() > 1 ) {
|
||||
os << getLineOfChars<'-'>() << "\n";
|
||||
|
||||
std::vector<SectionInfo>::const_iterator
|
||||
it = m_sectionStack.begin()+1, // Skip first section (test case)
|
||||
itEnd = m_sectionStack.end();
|
||||
for( ; it != itEnd; ++it )
|
||||
printHeaderString( os, it->name );
|
||||
os << getLineOfChars<'-'>() << "\n";
|
||||
}
|
||||
|
||||
SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
|
||||
|
||||
if( !lineInfo.empty() )
|
||||
os << lineInfo << "\n";
|
||||
os << getLineOfChars<'.'>() << "\n\n";
|
||||
}
|
||||
|
||||
// if string has a : in first line will set indent to follow it on
|
||||
// subsequent lines
|
||||
static void printHeaderString( std::ostream& os, std::string const& _string, std::size_t indent = 0 ) {
|
||||
std::size_t i = _string.find( ": " );
|
||||
if( i != std::string::npos )
|
||||
i+=2;
|
||||
else
|
||||
i = 0;
|
||||
os << Column( _string )
|
||||
.indent( indent+i)
|
||||
.initialIndent( indent ) << "\n";
|
||||
}
|
||||
private:
|
||||
bool m_headerPrintedForThisSection = false;
|
||||
Timer m_testTimer;
|
||||
};
|
||||
|
||||
#ifdef CATCH_IMPL
|
||||
TeamCityReporter::~TeamCityReporter() {}
|
||||
#endif
|
||||
|
||||
CATCH_REGISTER_REPORTER( "teamcity", TeamCityReporter )
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
|
||||
340
tests/cnpy.cpp
Normal file
340
tests/cnpy.cpp
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
//Copyright (C) 2011 Carl Rogers
|
||||
//Released under MIT License
|
||||
//license available in LICENSE file, or at http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
#include"cnpy.h"
|
||||
#include<complex>
|
||||
#include<cstdlib>
|
||||
#include<algorithm>
|
||||
#include<cstring>
|
||||
#include<iomanip>
|
||||
#include<stdint.h>
|
||||
#include<stdexcept>
|
||||
#include <regex>
|
||||
|
||||
char cnpy::BigEndianTest() {
|
||||
int x = 1;
|
||||
return (((char *)&x)[0]) ? '<' : '>';
|
||||
}
|
||||
|
||||
char cnpy::map_type(const std::type_info& t)
|
||||
{
|
||||
if(t == typeid(float) ) return 'f';
|
||||
if(t == typeid(double) ) return 'f';
|
||||
if(t == typeid(long double) ) return 'f';
|
||||
|
||||
if(t == typeid(int) ) return 'i';
|
||||
if(t == typeid(char) ) return 'i';
|
||||
if(t == typeid(short) ) return 'i';
|
||||
if(t == typeid(long) ) return 'i';
|
||||
if(t == typeid(long long) ) return 'i';
|
||||
|
||||
if(t == typeid(unsigned char) ) return 'u';
|
||||
if(t == typeid(unsigned short) ) return 'u';
|
||||
if(t == typeid(unsigned long) ) return 'u';
|
||||
if(t == typeid(unsigned long long) ) return 'u';
|
||||
if(t == typeid(unsigned int) ) return 'u';
|
||||
|
||||
if(t == typeid(bool) ) return 'b';
|
||||
|
||||
if(t == typeid(std::complex<float>) ) return 'c';
|
||||
if(t == typeid(std::complex<double>) ) return 'c';
|
||||
if(t == typeid(std::complex<long double>) ) return 'c';
|
||||
|
||||
else return '?';
|
||||
}
|
||||
|
||||
template<> std::vector<char>& cnpy::operator+=(std::vector<char>& lhs, const std::string rhs) {
|
||||
lhs.insert(lhs.end(),rhs.begin(),rhs.end());
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<> std::vector<char>& cnpy::operator+=(std::vector<char>& lhs, const char* rhs) {
|
||||
//write in little endian
|
||||
size_t len = strlen(rhs);
|
||||
lhs.reserve(len);
|
||||
for(size_t byte = 0; byte < len; byte++) {
|
||||
lhs.push_back(rhs[byte]);
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
void cnpy::parse_npy_header(unsigned char* buffer,size_t& word_size, std::vector<size_t>& shape, bool& fortran_order) {
|
||||
//std::string magic_string(buffer,6);
|
||||
uint8_t major_version = *reinterpret_cast<uint8_t*>(buffer+6);
|
||||
uint8_t minor_version = *reinterpret_cast<uint8_t*>(buffer+7);
|
||||
uint16_t header_len = *reinterpret_cast<uint16_t*>(buffer+8);
|
||||
std::string header(reinterpret_cast<char*>(buffer+9),header_len);
|
||||
|
||||
size_t loc1, loc2;
|
||||
|
||||
//fortran order
|
||||
loc1 = header.find("fortran_order")+16;
|
||||
fortran_order = (header.substr(loc1,4) == "True" ? true : false);
|
||||
|
||||
//shape
|
||||
loc1 = header.find("(");
|
||||
loc2 = header.find(")");
|
||||
|
||||
std::regex num_regex("[0-9][0-9]*");
|
||||
std::smatch sm;
|
||||
shape.clear();
|
||||
|
||||
std::string str_shape = header.substr(loc1+1,loc2-loc1-1);
|
||||
while(std::regex_search(str_shape, sm, num_regex)) {
|
||||
shape.push_back(std::stoi(sm[0].str()));
|
||||
str_shape = sm.suffix().str();
|
||||
}
|
||||
|
||||
//endian, word size, data type
|
||||
//byte order code | stands for not applicable.
|
||||
//not sure when this applies except for byte array
|
||||
loc1 = header.find("descr")+9;
|
||||
bool littleEndian = (header[loc1] == '<' || header[loc1] == '|' ? true : false);
|
||||
assert(littleEndian);
|
||||
|
||||
//char type = header[loc1+1];
|
||||
//assert(type == map_type(T));
|
||||
|
||||
std::string str_ws = header.substr(loc1+2);
|
||||
loc2 = str_ws.find("'");
|
||||
word_size = atoi(str_ws.substr(0,loc2).c_str());
|
||||
}
|
||||
|
||||
void cnpy::parse_npy_header(FILE* fp, size_t& word_size, std::vector<size_t>& shape, bool& fortran_order) {
|
||||
char buffer[256];
|
||||
size_t res = fread(buffer,sizeof(char),11,fp);
|
||||
if(res != 11)
|
||||
throw std::runtime_error("parse_npy_header: failed fread");
|
||||
std::string header = fgets(buffer,256,fp);
|
||||
assert(header[header.size()-1] == '\n');
|
||||
|
||||
size_t loc1, loc2;
|
||||
|
||||
//fortran order
|
||||
loc1 = header.find("fortran_order");
|
||||
if (loc1 == std::string::npos)
|
||||
throw std::runtime_error("parse_npy_header: failed to find header keyword: 'fortran_order'");
|
||||
loc1 += 16;
|
||||
fortran_order = (header.substr(loc1,4) == "True" ? true : false);
|
||||
|
||||
//shape
|
||||
loc1 = header.find("(");
|
||||
loc2 = header.find(")");
|
||||
if (loc1 == std::string::npos || loc2 == std::string::npos)
|
||||
throw std::runtime_error("parse_npy_header: failed to find header keyword: '(' or ')'");
|
||||
|
||||
std::regex num_regex("[0-9][0-9]*");
|
||||
std::smatch sm;
|
||||
shape.clear();
|
||||
|
||||
std::string str_shape = header.substr(loc1+1,loc2-loc1-1);
|
||||
while(std::regex_search(str_shape, sm, num_regex)) {
|
||||
shape.push_back(std::stoi(sm[0].str()));
|
||||
str_shape = sm.suffix().str();
|
||||
}
|
||||
|
||||
//endian, word size, data type
|
||||
//byte order code | stands for not applicable.
|
||||
//not sure when this applies except for byte array
|
||||
loc1 = header.find("descr");
|
||||
if (loc1 == std::string::npos)
|
||||
throw std::runtime_error("parse_npy_header: failed to find header keyword: 'descr'");
|
||||
loc1 += 9;
|
||||
bool littleEndian = (header[loc1] == '<' || header[loc1] == '|' ? true : false);
|
||||
assert(littleEndian);
|
||||
|
||||
//char type = header[loc1+1];
|
||||
//assert(type == map_type(T));
|
||||
|
||||
std::string str_ws = header.substr(loc1+2);
|
||||
loc2 = str_ws.find("'");
|
||||
word_size = atoi(str_ws.substr(0,loc2).c_str());
|
||||
}
|
||||
|
||||
void cnpy::parse_zip_footer(FILE* fp, uint16_t& nrecs, size_t& global_header_size, size_t& global_header_offset)
|
||||
{
|
||||
std::vector<char> footer(22);
|
||||
fseek(fp,-22,SEEK_END);
|
||||
size_t res = fread(&footer[0],sizeof(char),22,fp);
|
||||
if(res != 22)
|
||||
throw std::runtime_error("parse_zip_footer: failed fread");
|
||||
|
||||
uint16_t disk_no, disk_start, nrecs_on_disk, comment_len;
|
||||
disk_no = *(uint16_t*) &footer[4];
|
||||
disk_start = *(uint16_t*) &footer[6];
|
||||
nrecs_on_disk = *(uint16_t*) &footer[8];
|
||||
nrecs = *(uint16_t*) &footer[10];
|
||||
global_header_size = *(uint32_t*) &footer[12];
|
||||
global_header_offset = *(uint32_t*) &footer[16];
|
||||
comment_len = *(uint16_t*) &footer[20];
|
||||
|
||||
assert(disk_no == 0);
|
||||
assert(disk_start == 0);
|
||||
assert(nrecs_on_disk == nrecs);
|
||||
assert(comment_len == 0);
|
||||
}
|
||||
|
||||
cnpy::NpyArray load_the_npy_file(FILE* fp) {
|
||||
std::vector<size_t> shape;
|
||||
size_t word_size;
|
||||
bool fortran_order;
|
||||
cnpy::parse_npy_header(fp,word_size,shape,fortran_order);
|
||||
|
||||
cnpy::NpyArray arr(shape, word_size, fortran_order);
|
||||
size_t nread = fread(arr.data<char>(),1,arr.num_bytes(),fp);
|
||||
if(nread != arr.num_bytes())
|
||||
throw std::runtime_error("load_the_npy_file: failed fread");
|
||||
return arr;
|
||||
}
|
||||
|
||||
cnpy::NpyArray load_the_npz_array(FILE* fp, uint32_t compr_bytes, uint32_t uncompr_bytes) {
|
||||
|
||||
std::vector<unsigned char> buffer_compr(compr_bytes);
|
||||
std::vector<unsigned char> buffer_uncompr(uncompr_bytes);
|
||||
size_t nread = fread(&buffer_compr[0],1,compr_bytes,fp);
|
||||
if(nread != compr_bytes)
|
||||
throw std::runtime_error("load_the_npy_file: failed fread");
|
||||
|
||||
int err;
|
||||
z_stream d_stream;
|
||||
|
||||
d_stream.zalloc = Z_NULL;
|
||||
d_stream.zfree = Z_NULL;
|
||||
d_stream.opaque = Z_NULL;
|
||||
d_stream.avail_in = 0;
|
||||
d_stream.next_in = Z_NULL;
|
||||
err = inflateInit2(&d_stream, -MAX_WBITS);
|
||||
|
||||
d_stream.avail_in = compr_bytes;
|
||||
d_stream.next_in = &buffer_compr[0];
|
||||
d_stream.avail_out = uncompr_bytes;
|
||||
d_stream.next_out = &buffer_uncompr[0];
|
||||
|
||||
err = inflate(&d_stream, Z_FINISH);
|
||||
err = inflateEnd(&d_stream);
|
||||
|
||||
std::vector<size_t> shape;
|
||||
size_t word_size;
|
||||
bool fortran_order;
|
||||
cnpy::parse_npy_header(&buffer_uncompr[0],word_size,shape,fortran_order);
|
||||
|
||||
cnpy::NpyArray array(shape, word_size, fortran_order);
|
||||
|
||||
size_t offset = uncompr_bytes - array.num_bytes();
|
||||
memcpy(array.data<unsigned char>(),&buffer_uncompr[0]+offset,array.num_bytes());
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
cnpy::npz_t cnpy::npz_load(std::string fname) {
|
||||
FILE* fp = fopen(fname.c_str(),"rb");
|
||||
|
||||
if(!fp) {
|
||||
throw std::runtime_error("npz_load: Error! Unable to open file "+fname+"!");
|
||||
}
|
||||
|
||||
cnpy::npz_t arrays;
|
||||
|
||||
while(1) {
|
||||
std::vector<char> local_header(30);
|
||||
size_t headerres = fread(&local_header[0],sizeof(char),30,fp);
|
||||
if(headerres != 30)
|
||||
throw std::runtime_error("npz_load: failed fread");
|
||||
|
||||
//if we've reached the global header, stop reading
|
||||
if(local_header[2] != 0x03 || local_header[3] != 0x04) break;
|
||||
|
||||
//read in the variable name
|
||||
uint16_t name_len = *(uint16_t*) &local_header[26];
|
||||
std::string varname(name_len,' ');
|
||||
size_t vname_res = fread(&varname[0],sizeof(char),name_len,fp);
|
||||
if(vname_res != name_len)
|
||||
throw std::runtime_error("npz_load: failed fread");
|
||||
|
||||
//erase the lagging .npy
|
||||
varname.erase(varname.end()-4,varname.end());
|
||||
|
||||
//read in the extra field
|
||||
uint16_t extra_field_len = *(uint16_t*) &local_header[28];
|
||||
if(extra_field_len > 0) {
|
||||
std::vector<char> buff(extra_field_len);
|
||||
size_t efield_res = fread(&buff[0],sizeof(char),extra_field_len,fp);
|
||||
if(efield_res != extra_field_len)
|
||||
throw std::runtime_error("npz_load: failed fread");
|
||||
}
|
||||
|
||||
uint16_t compr_method = *reinterpret_cast<uint16_t*>(&local_header[0]+8);
|
||||
uint32_t compr_bytes = *reinterpret_cast<uint32_t*>(&local_header[0]+18);
|
||||
uint32_t uncompr_bytes = *reinterpret_cast<uint32_t*>(&local_header[0]+22);
|
||||
|
||||
if(compr_method == 0) {arrays[varname] = load_the_npy_file(fp);}
|
||||
else {arrays[varname] = load_the_npz_array(fp,compr_bytes,uncompr_bytes);}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return arrays;
|
||||
}
|
||||
|
||||
cnpy::NpyArray cnpy::npz_load(std::string fname, std::string varname) {
|
||||
FILE* fp = fopen(fname.c_str(),"rb");
|
||||
|
||||
if(!fp) throw std::runtime_error("npz_load: Unable to open file "+fname);
|
||||
|
||||
while(1) {
|
||||
std::vector<char> local_header(30);
|
||||
size_t header_res = fread(&local_header[0],sizeof(char),30,fp);
|
||||
if(header_res != 30)
|
||||
throw std::runtime_error("npz_load: failed fread");
|
||||
|
||||
//if we've reached the global header, stop reading
|
||||
if(local_header[2] != 0x03 || local_header[3] != 0x04) break;
|
||||
|
||||
//read in the variable name
|
||||
uint16_t name_len = *(uint16_t*) &local_header[26];
|
||||
std::string vname(name_len,' ');
|
||||
size_t vname_res = fread(&vname[0],sizeof(char),name_len,fp);
|
||||
if(vname_res != name_len)
|
||||
throw std::runtime_error("npz_load: failed fread");
|
||||
vname.erase(vname.end()-4,vname.end()); //erase the lagging .npy
|
||||
|
||||
//read in the extra field
|
||||
uint16_t extra_field_len = *(uint16_t*) &local_header[28];
|
||||
fseek(fp,extra_field_len,SEEK_CUR); //skip past the extra field
|
||||
|
||||
uint16_t compr_method = *reinterpret_cast<uint16_t*>(&local_header[0]+8);
|
||||
uint32_t compr_bytes = *reinterpret_cast<uint32_t*>(&local_header[0]+18);
|
||||
uint32_t uncompr_bytes = *reinterpret_cast<uint32_t*>(&local_header[0]+22);
|
||||
|
||||
if(vname == varname) {
|
||||
NpyArray array = (compr_method == 0) ? load_the_npy_file(fp) : load_the_npz_array(fp,compr_bytes,uncompr_bytes);
|
||||
fclose(fp);
|
||||
return array;
|
||||
}
|
||||
else {
|
||||
//skip past the data
|
||||
uint32_t size = *(uint32_t*) &local_header[22];
|
||||
fseek(fp,size,SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
//if we get here, we haven't found the variable in the file
|
||||
throw std::runtime_error("npz_load: Variable name "+varname+" not found in "+fname);
|
||||
}
|
||||
|
||||
cnpy::NpyArray cnpy::npy_load(std::string fname) {
|
||||
|
||||
FILE* fp = fopen(fname.c_str(), "rb");
|
||||
|
||||
if(!fp) throw std::runtime_error("npy_load: Unable to open file "+fname);
|
||||
|
||||
NpyArray arr = load_the_npy_file(fp);
|
||||
|
||||
fclose(fp);
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
269
tests/cnpy.h
Normal file
269
tests/cnpy.h
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
//Copyright (C) 2011 Carl Rogers
|
||||
//Released under MIT License
|
||||
//license available in LICENSE file, or at http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
#ifndef LIBCNPY_H_
|
||||
#define LIBCNPY_H_
|
||||
|
||||
#include<string>
|
||||
#include<stdexcept>
|
||||
#include<sstream>
|
||||
#include<vector>
|
||||
#include<cstdio>
|
||||
#include<typeinfo>
|
||||
#include<iostream>
|
||||
#include<cassert>
|
||||
#include<zlib.h>
|
||||
#include<map>
|
||||
#include<memory>
|
||||
#include<stdint.h>
|
||||
#include<numeric>
|
||||
|
||||
namespace cnpy {
|
||||
|
||||
struct NpyArray {
|
||||
NpyArray(const std::vector<size_t>& _shape, size_t _word_size, bool _fortran_order) :
|
||||
shape(_shape), word_size(_word_size), fortran_order(_fortran_order)
|
||||
{
|
||||
num_vals = 1;
|
||||
for(size_t i = 0;i < shape.size();i++) num_vals *= shape[i];
|
||||
data_holder = std::shared_ptr<std::vector<char>>(
|
||||
new std::vector<char>(num_vals * word_size));
|
||||
}
|
||||
|
||||
NpyArray() : shape(0), word_size(0), fortran_order(0), num_vals(0) { }
|
||||
|
||||
template<typename T>
|
||||
T* data() {
|
||||
return reinterpret_cast<T*>(&(*data_holder)[0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* data() const {
|
||||
return reinterpret_cast<T*>(&(*data_holder)[0]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> as_vec() const {
|
||||
const T* p = data<T>();
|
||||
return std::vector<T>(p, p+num_vals);
|
||||
}
|
||||
|
||||
size_t num_bytes() const {
|
||||
return data_holder->size();
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<char>> data_holder;
|
||||
std::vector<size_t> shape;
|
||||
size_t word_size;
|
||||
bool fortran_order;
|
||||
size_t num_vals;
|
||||
};
|
||||
|
||||
using npz_t = std::map<std::string, NpyArray>;
|
||||
|
||||
char BigEndianTest();
|
||||
char map_type(const std::type_info& t);
|
||||
template<typename T> std::vector<char> create_npy_header(const std::vector<size_t>& shape);
|
||||
void parse_npy_header(FILE* fp,size_t& word_size, std::vector<size_t>& shape, bool& fortran_order);
|
||||
void parse_npy_header(unsigned char* buffer,size_t& word_size, std::vector<size_t>& shape, bool& fortran_order);
|
||||
void parse_zip_footer(FILE* fp, uint16_t& nrecs, size_t& global_header_size, size_t& global_header_offset);
|
||||
npz_t npz_load(std::string fname);
|
||||
NpyArray npz_load(std::string fname, std::string varname);
|
||||
NpyArray npy_load(std::string fname);
|
||||
|
||||
template<typename T> std::vector<char>& operator+=(std::vector<char>& lhs, const T rhs) {
|
||||
//write in little endian
|
||||
for(size_t byte = 0; byte < sizeof(T); byte++) {
|
||||
char val = *((char*)&rhs+byte);
|
||||
lhs.push_back(val);
|
||||
}
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<> std::vector<char>& operator+=(std::vector<char>& lhs, const std::string rhs);
|
||||
template<> std::vector<char>& operator+=(std::vector<char>& lhs, const char* rhs);
|
||||
|
||||
|
||||
template<typename T> void npy_save(std::string fname, const T* data, const std::vector<size_t> shape, std::string mode = "w") {
|
||||
FILE* fp = NULL;
|
||||
std::vector<size_t> true_data_shape; //if appending, the shape of existing + new data
|
||||
|
||||
if(mode == "a") fp = fopen(fname.c_str(),"r+b");
|
||||
|
||||
if(fp) {
|
||||
//file exists. we need to append to it. read the header, modify the array size
|
||||
size_t word_size;
|
||||
bool fortran_order;
|
||||
parse_npy_header(fp,word_size,true_data_shape,fortran_order);
|
||||
assert(!fortran_order);
|
||||
|
||||
if(word_size != sizeof(T)) {
|
||||
std::cout<<"libnpy error: "<<fname<<" has word size "<<word_size<<" but npy_save appending data sized "<<sizeof(T)<<"\n";
|
||||
assert( word_size == sizeof(T) );
|
||||
}
|
||||
if(true_data_shape.size() != shape.size()) {
|
||||
std::cout<<"libnpy error: npy_save attempting to append misdimensioned data to "<<fname<<"\n";
|
||||
assert(true_data_shape.size() != shape.size());
|
||||
}
|
||||
|
||||
for(size_t i = 1; i < shape.size(); i++) {
|
||||
if(shape[i] != true_data_shape[i]) {
|
||||
std::cout<<"libnpy error: npy_save attempting to append misshaped data to "<<fname<<"\n";
|
||||
assert(shape[i] == true_data_shape[i]);
|
||||
}
|
||||
}
|
||||
true_data_shape[0] += shape[0];
|
||||
}
|
||||
else {
|
||||
fp = fopen(fname.c_str(),"wb");
|
||||
true_data_shape = shape;
|
||||
}
|
||||
|
||||
std::vector<char> header = create_npy_header<T>(true_data_shape);
|
||||
size_t nels = std::accumulate(shape.begin(),shape.end(),1,std::multiplies<size_t>());
|
||||
|
||||
fseek(fp,0,SEEK_SET);
|
||||
fwrite(&header[0],sizeof(char),header.size(),fp);
|
||||
fseek(fp,0,SEEK_END);
|
||||
fwrite(data,sizeof(T),nels,fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
template<typename T> void npz_save(std::string zipname, std::string fname, const T* data, const std::vector<size_t>& shape, std::string mode = "w")
|
||||
{
|
||||
//first, append a .npy to the fname
|
||||
fname += ".npy";
|
||||
|
||||
//now, on with the show
|
||||
FILE* fp = NULL;
|
||||
uint16_t nrecs = 0;
|
||||
size_t global_header_offset = 0;
|
||||
std::vector<char> global_header;
|
||||
|
||||
if(mode == "a") fp = fopen(zipname.c_str(),"r+b");
|
||||
|
||||
if(fp) {
|
||||
//zip file exists. we need to add a new npy file to it.
|
||||
//first read the footer. this gives us the offset and size of the global header
|
||||
//then read and store the global header.
|
||||
//below, we will write the the new data at the start of the global header then append the global header and footer below it
|
||||
size_t global_header_size;
|
||||
parse_zip_footer(fp,nrecs,global_header_size,global_header_offset);
|
||||
fseek(fp,global_header_offset,SEEK_SET);
|
||||
global_header.resize(global_header_size);
|
||||
size_t res = fread(&global_header[0],sizeof(char),global_header_size,fp);
|
||||
if(res != global_header_size){
|
||||
throw std::runtime_error("npz_save: header read error while adding to existing zip");
|
||||
}
|
||||
fseek(fp,global_header_offset,SEEK_SET);
|
||||
}
|
||||
else {
|
||||
fp = fopen(zipname.c_str(),"wb");
|
||||
}
|
||||
|
||||
std::vector<char> npy_header = create_npy_header<T>(shape);
|
||||
|
||||
size_t nels = std::accumulate(shape.begin(),shape.end(),1,std::multiplies<size_t>());
|
||||
size_t nbytes = nels*sizeof(T) + npy_header.size();
|
||||
|
||||
//get the CRC of the data to be added
|
||||
uint32_t crc = crc32(0L,(uint8_t*)&npy_header[0],npy_header.size());
|
||||
crc = crc32(crc,(uint8_t*)data,nels*sizeof(T));
|
||||
|
||||
//build the local header
|
||||
std::vector<char> local_header;
|
||||
local_header += "PK"; //first part of sig
|
||||
local_header += (uint16_t) 0x0403; //second part of sig
|
||||
local_header += (uint16_t) 20; //min version to extract
|
||||
local_header += (uint16_t) 0; //general purpose bit flag
|
||||
local_header += (uint16_t) 0; //compression method
|
||||
local_header += (uint16_t) 0; //file last mod time
|
||||
local_header += (uint16_t) 0; //file last mod date
|
||||
local_header += (uint32_t) crc; //crc
|
||||
local_header += (uint32_t) nbytes; //compressed size
|
||||
local_header += (uint32_t) nbytes; //uncompressed size
|
||||
local_header += (uint16_t) fname.size(); //fname length
|
||||
local_header += (uint16_t) 0; //extra field length
|
||||
local_header += fname;
|
||||
|
||||
//build global header
|
||||
global_header += "PK"; //first part of sig
|
||||
global_header += (uint16_t) 0x0201; //second part of sig
|
||||
global_header += (uint16_t) 20; //version made by
|
||||
global_header.insert(global_header.end(),local_header.begin()+4,local_header.begin()+30);
|
||||
global_header += (uint16_t) 0; //file comment length
|
||||
global_header += (uint16_t) 0; //disk number where file starts
|
||||
global_header += (uint16_t) 0; //internal file attributes
|
||||
global_header += (uint32_t) 0; //external file attributes
|
||||
global_header += (uint32_t) global_header_offset; //relative offset of local file header, since it begins where the global header used to begin
|
||||
global_header += fname;
|
||||
|
||||
//build footer
|
||||
std::vector<char> footer;
|
||||
footer += "PK"; //first part of sig
|
||||
footer += (uint16_t) 0x0605; //second part of sig
|
||||
footer += (uint16_t) 0; //number of this disk
|
||||
footer += (uint16_t) 0; //disk where footer starts
|
||||
footer += (uint16_t) (nrecs+1); //number of records on this disk
|
||||
footer += (uint16_t) (nrecs+1); //total number of records
|
||||
footer += (uint32_t) global_header.size(); //nbytes of global headers
|
||||
footer += (uint32_t) (global_header_offset + nbytes + local_header.size()); //offset of start of global headers, since global header now starts after newly written array
|
||||
footer += (uint16_t) 0; //zip file comment length
|
||||
|
||||
//write everything
|
||||
fwrite(&local_header[0],sizeof(char),local_header.size(),fp);
|
||||
fwrite(&npy_header[0],sizeof(char),npy_header.size(),fp);
|
||||
fwrite(data,sizeof(T),nels,fp);
|
||||
fwrite(&global_header[0],sizeof(char),global_header.size(),fp);
|
||||
fwrite(&footer[0],sizeof(char),footer.size(),fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
template<typename T> void npy_save(std::string fname, const std::vector<T> data, std::string mode = "w") {
|
||||
std::vector<size_t> shape;
|
||||
shape.push_back(data.size());
|
||||
npy_save(fname, &data[0], shape, mode);
|
||||
}
|
||||
|
||||
template<typename T> void npz_save(std::string zipname, std::string fname, const std::vector<T> data, std::string mode = "w") {
|
||||
std::vector<size_t> shape;
|
||||
shape.push_back(data.size());
|
||||
npz_save(zipname, fname, &data[0], shape, mode);
|
||||
}
|
||||
|
||||
template<typename T> std::vector<char> create_npy_header(const std::vector<size_t>& shape) {
|
||||
|
||||
std::vector<char> dict;
|
||||
dict += "{'descr': '";
|
||||
dict += BigEndianTest();
|
||||
dict += map_type(typeid(T));
|
||||
dict += std::to_string(sizeof(T));
|
||||
dict += "', 'fortran_order': False, 'shape': (";
|
||||
dict += std::to_string(shape[0]);
|
||||
for(size_t i = 1;i < shape.size();i++) {
|
||||
dict += ", ";
|
||||
dict += std::to_string(shape[i]);
|
||||
}
|
||||
if(shape.size() == 1) dict += ",";
|
||||
dict += "), }";
|
||||
//pad with spaces so that preamble+dict is modulo 16 bytes. preamble is 10 bytes. dict needs to end with \n
|
||||
int remainder = 16 - (10 + dict.size()) % 16;
|
||||
dict.insert(dict.end(),remainder,' ');
|
||||
dict.back() = '\n';
|
||||
|
||||
std::vector<char> header;
|
||||
header += (char) 0x93;
|
||||
header += "NUMPY";
|
||||
header += (char) 0x01; //major version of numpy format
|
||||
header += (char) 0x00; //minor version of numpy format
|
||||
header += (uint16_t) dict.size();
|
||||
header.insert(header.end(),dict.begin(),dict.end());
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Reference in a new issue