mirror of
https://github.com/love2d/megasource.git
synced 2026-08-17 19:24:09 +02:00
Update OpenAL-soft to 1.23.1-bc7cb17.
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
#ifndef AL_BIT_H
|
||||
#define AL_BIT_H
|
||||
|
||||
#include <array>
|
||||
#ifndef __GNUC__
|
||||
#include <cstdint>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64))
|
||||
#include <intrin.h>
|
||||
@@ -10,6 +15,16 @@
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename To, typename From>
|
||||
std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From>
|
||||
&& std::is_trivially_copyable_v<To>,
|
||||
To> bit_cast(const From &src) noexcept
|
||||
{
|
||||
alignas(To) std::array<char,sizeof(To)> dst;
|
||||
std::memcpy(dst.data(), &src, sizeof(To));
|
||||
return *std::launder(reinterpret_cast<To*>(dst.data()));
|
||||
}
|
||||
|
||||
#ifdef __BYTE_ORDER__
|
||||
enum class endian {
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef AL_BYTE_H
|
||||
#define AL_BYTE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace al {
|
||||
|
||||
using byte = unsigned char;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_BYTE_H */
|
||||
@@ -4,9 +4,11 @@
|
||||
#include "alcomplex.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "albit.h"
|
||||
@@ -19,66 +21,65 @@ namespace {
|
||||
|
||||
using ushort = unsigned short;
|
||||
using ushort2 = std::pair<ushort,ushort>;
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
/* Because std::array doesn't have constexpr non-const accessors in C++14. */
|
||||
template<typename T, size_t N>
|
||||
struct our_array {
|
||||
T mData[N];
|
||||
};
|
||||
|
||||
constexpr size_t BitReverseCounter(size_t log2_size) noexcept
|
||||
constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept
|
||||
{
|
||||
/* Some magic math that calculates the number of swaps needed for a
|
||||
* sequence of bit-reversed indices when index < reversed_index.
|
||||
*/
|
||||
return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u));
|
||||
return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu));
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
constexpr auto GetBitReverser() noexcept
|
||||
{
|
||||
|
||||
template<std::size_t N>
|
||||
struct BitReverser {
|
||||
static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table.");
|
||||
|
||||
our_array<ushort2, BitReverseCounter(N)> ret{};
|
||||
const size_t fftsize{1u << N};
|
||||
size_t ret_i{0};
|
||||
std::array<ushort2,BitReverseCounter(N)> mData{};
|
||||
|
||||
/* Bit-reversal permutation applied to a sequence of fftsize items. */
|
||||
for(size_t idx{1u};idx < fftsize-1;++idx)
|
||||
constexpr BitReverser()
|
||||
{
|
||||
size_t revidx{0u}, imask{idx};
|
||||
for(size_t i{0};i < N;++i)
|
||||
{
|
||||
revidx = (revidx<<1) | (imask&1);
|
||||
imask >>= 1;
|
||||
}
|
||||
const std::size_t fftsize{1u << N};
|
||||
std::size_t ret_i{0};
|
||||
|
||||
if(idx < revidx)
|
||||
/* Bit-reversal permutation applied to a sequence of fftsize items. */
|
||||
for(std::size_t idx{1u};idx < fftsize-1;++idx)
|
||||
{
|
||||
ret.mData[ret_i].first = static_cast<ushort>(idx);
|
||||
ret.mData[ret_i].second = static_cast<ushort>(revidx);
|
||||
++ret_i;
|
||||
std::size_t revidx{idx};
|
||||
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
|
||||
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
|
||||
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
|
||||
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
|
||||
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
|
||||
revidx >>= 32-N;
|
||||
|
||||
if(idx < revidx)
|
||||
{
|
||||
mData[ret_i].first = static_cast<ushort>(idx);
|
||||
mData[ret_i].second = static_cast<ushort>(revidx);
|
||||
++ret_i;
|
||||
}
|
||||
}
|
||||
assert(ret_i == std::size(mData));
|
||||
}
|
||||
assert(ret_i == al::size(ret.mData));
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
/* These bit-reversal swap tables support up to 10-bit indices (1024 elements),
|
||||
* which is the largest used by OpenAL Soft's filters and effects. Larger FFT
|
||||
* requests, used by some utilities where performance is less important, will
|
||||
* use a slower table-less path.
|
||||
/* These bit-reversal swap tables support up to 11-bit indices (2048 elements),
|
||||
* which is large enough for the filters and effects in OpenAL Soft. Larger FFT
|
||||
* requests will use a slower table-less path.
|
||||
*/
|
||||
constexpr auto BitReverser2 = GetBitReverser<2>();
|
||||
constexpr auto BitReverser3 = GetBitReverser<3>();
|
||||
constexpr auto BitReverser4 = GetBitReverser<4>();
|
||||
constexpr auto BitReverser5 = GetBitReverser<5>();
|
||||
constexpr auto BitReverser6 = GetBitReverser<6>();
|
||||
constexpr auto BitReverser7 = GetBitReverser<7>();
|
||||
constexpr auto BitReverser8 = GetBitReverser<8>();
|
||||
constexpr auto BitReverser9 = GetBitReverser<9>();
|
||||
constexpr auto BitReverser10 = GetBitReverser<10>();
|
||||
constexpr al::span<const ushort2> gBitReverses[11]{
|
||||
constexpr BitReverser<2> BitReverser2{};
|
||||
constexpr BitReverser<3> BitReverser3{};
|
||||
constexpr BitReverser<4> BitReverser4{};
|
||||
constexpr BitReverser<5> BitReverser5{};
|
||||
constexpr BitReverser<6> BitReverser6{};
|
||||
constexpr BitReverser<7> BitReverser7{};
|
||||
constexpr BitReverser<8> BitReverser8{};
|
||||
constexpr BitReverser<9> BitReverser9{};
|
||||
constexpr BitReverser<10> BitReverser10{};
|
||||
constexpr BitReverser<11> BitReverser11{};
|
||||
constexpr std::array<al::span<const ushort2>,12> gBitReverses{{
|
||||
{}, {},
|
||||
BitReverser2.mData,
|
||||
BitReverser3.mData,
|
||||
@@ -88,61 +89,114 @@ constexpr al::span<const ushort2> gBitReverses[11]{
|
||||
BitReverser7.mData,
|
||||
BitReverser8.mData,
|
||||
BitReverser9.mData,
|
||||
BitReverser10.mData
|
||||
};
|
||||
BitReverser10.mData,
|
||||
BitReverser11.mData
|
||||
}};
|
||||
|
||||
/* Lookup table for std::polar(1, pi / (1<<index)); */
|
||||
template<typename T>
|
||||
constexpr std::array<std::complex<T>,gBitReverses.size()-1> gArgAngle{{
|
||||
{static_cast<T>(-1.00000000000000000e+00), static_cast<T>(0.00000000000000000e+00)},
|
||||
{static_cast<T>( 0.00000000000000000e+00), static_cast<T>(1.00000000000000000e+00)},
|
||||
{static_cast<T>( 7.07106781186547524e-01), static_cast<T>(7.07106781186547524e-01)},
|
||||
{static_cast<T>( 9.23879532511286756e-01), static_cast<T>(3.82683432365089772e-01)},
|
||||
{static_cast<T>( 9.80785280403230449e-01), static_cast<T>(1.95090322016128268e-01)},
|
||||
{static_cast<T>( 9.95184726672196886e-01), static_cast<T>(9.80171403295606020e-02)},
|
||||
{static_cast<T>( 9.98795456205172393e-01), static_cast<T>(4.90676743274180143e-02)},
|
||||
{static_cast<T>( 9.99698818696204220e-01), static_cast<T>(2.45412285229122880e-02)},
|
||||
{static_cast<T>( 9.99924701839144541e-01), static_cast<T>(1.22715382857199261e-02)},
|
||||
{static_cast<T>( 9.99981175282601143e-01), static_cast<T>(6.13588464915447536e-03)},
|
||||
{static_cast<T>( 9.99995293809576172e-01), static_cast<T>(3.06795676296597627e-03)}
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
|
||||
{
|
||||
const size_t fftsize{buffer.size()};
|
||||
const std::size_t fftsize{buffer.size()};
|
||||
/* Get the number of bits used for indexing. Simplifies bit-reversal and
|
||||
* the main loop count.
|
||||
*/
|
||||
const size_t log2_size{static_cast<size_t>(al::countr_zero(fftsize))};
|
||||
const std::size_t log2_size{static_cast<std::size_t>(al::countr_zero(fftsize))};
|
||||
|
||||
if(unlikely(log2_size >= al::size(gBitReverses)))
|
||||
if(log2_size < gBitReverses.size()) LIKELY
|
||||
{
|
||||
for(size_t idx{1u};idx < fftsize-1;++idx)
|
||||
for(auto &rev : gBitReverses[log2_size])
|
||||
std::swap(buffer[rev.first], buffer[rev.second]);
|
||||
|
||||
/* Iterative form of Danielson-Lanczos lemma */
|
||||
for(std::size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
size_t revidx{0u}, imask{idx};
|
||||
for(size_t i{0};i < log2_size;++i)
|
||||
const std::size_t step2{1_uz << i};
|
||||
const std::size_t step{2_uz << i};
|
||||
/* The first iteration of the inner loop would have u=1, which we
|
||||
* can simplify to remove a number of complex multiplies.
|
||||
*/
|
||||
for(std::size_t k{0};k < fftsize;k+=step)
|
||||
{
|
||||
revidx = (revidx<<1) | (imask&1);
|
||||
imask >>= 1;
|
||||
}
|
||||
|
||||
if(idx < revidx)
|
||||
std::swap(buffer[idx], buffer[revidx]);
|
||||
}
|
||||
}
|
||||
else for(auto &rev : gBitReverses[log2_size])
|
||||
std::swap(buffer[rev.first], buffer[rev.second]);
|
||||
|
||||
/* Iterative form of Danielson-Lanczos lemma */
|
||||
const double pi{al::numbers::pi * sign};
|
||||
size_t step2{1u};
|
||||
for(size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
const double arg{pi / static_cast<double>(step2)};
|
||||
|
||||
/* TODO: Would std::polar(1.0, arg) be any better? */
|
||||
const std::complex<double> w{std::cos(arg), std::sin(arg)};
|
||||
std::complex<double> u{1.0, 0.0};
|
||||
const size_t step{step2 << 1};
|
||||
for(size_t j{0};j < step2;j++)
|
||||
{
|
||||
for(size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
std::complex<double> temp{buffer[k+step2] * u};
|
||||
const complex_d temp{buffer[k+step2]};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
|
||||
u *= w;
|
||||
const complex_d w{gArgAngle<double>[i].real(), gArgAngle<double>[i].imag()*sign};
|
||||
complex_d u{w};
|
||||
for(std::size_t j{1};j < step2;j++)
|
||||
{
|
||||
for(std::size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2] * u};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
u *= w;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(log2_size < 32);
|
||||
|
||||
for(std::size_t idx{1u};idx < fftsize-1;++idx)
|
||||
{
|
||||
std::size_t revidx{idx};
|
||||
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
|
||||
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
|
||||
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
|
||||
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
|
||||
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
|
||||
revidx >>= 32-log2_size;
|
||||
|
||||
if(idx < revidx)
|
||||
std::swap(buffer[idx], buffer[revidx]);
|
||||
}
|
||||
|
||||
step2 <<= 1;
|
||||
const double pi{al::numbers::pi * sign};
|
||||
for(std::size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
const std::size_t step2{1_uz << i};
|
||||
const std::size_t step{2_uz << i};
|
||||
for(std::size_t k{0};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2]};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
|
||||
const double arg{pi / static_cast<double>(step2)};
|
||||
const complex_d w{std::polar(1.0, arg)};
|
||||
complex_d u{w};
|
||||
for(std::size_t j{1};j < step2;j++)
|
||||
{
|
||||
for(std::size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2] * u};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
u *= w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +206,11 @@ void complex_hilbert(const al::span<std::complex<double>> buffer)
|
||||
|
||||
const double inverse_size = 1.0/static_cast<double>(buffer.size());
|
||||
auto bufiter = buffer.begin();
|
||||
const auto halfiter = bufiter + (buffer.size()>>1);
|
||||
const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1);
|
||||
|
||||
*bufiter *= inverse_size; ++bufiter;
|
||||
bufiter = std::transform(bufiter, halfiter, bufiter,
|
||||
[inverse_size](const std::complex<double> &c) -> std::complex<double>
|
||||
{ return c * (2.0*inverse_size); });
|
||||
[scale=inverse_size*2.0](std::complex<double> d){ return d * scale; });
|
||||
*bufiter *= inverse_size; ++bufiter;
|
||||
|
||||
std::fill(bufiter, buffer.end(), std::complex<double>{});
|
||||
|
||||
@@ -24,7 +24,7 @@ inline void forward_fft(const al::span<std::complex<double>> buffer)
|
||||
* provided buffer, which MUST BE power of two.
|
||||
*/
|
||||
inline void inverse_fft(const al::span<std::complex<double>> buffer)
|
||||
{ complex_fft(buffer, 1.0); }
|
||||
{ complex_fft(buffer, +1.0); }
|
||||
|
||||
/**
|
||||
* Calculate the complex helical sequence (discrete-time analytical signal) of
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef ALDEQUE_H
|
||||
#define ALDEQUE_H
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
using deque = std::deque<T, al::allocator<T>>;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* ALDEQUE_H */
|
||||
@@ -1,150 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "alfstream.h"
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
namespace al {
|
||||
|
||||
auto filebuf::underflow() -> int_type
|
||||
{
|
||||
if(mFile != INVALID_HANDLE_VALUE && gptr() == egptr())
|
||||
{
|
||||
// Read in the next chunk of data, and set the pointers on success
|
||||
DWORD got{};
|
||||
if(ReadFile(mFile, mBuffer.data(), static_cast<DWORD>(mBuffer.size()), &got, nullptr))
|
||||
setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got);
|
||||
}
|
||||
if(gptr() == egptr())
|
||||
return traits_type::eof();
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
auto filebuf::seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) -> pos_type
|
||||
{
|
||||
if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
|
||||
return traits_type::eof();
|
||||
|
||||
LARGE_INTEGER fpos{};
|
||||
switch(whence)
|
||||
{
|
||||
case std::ios_base::beg:
|
||||
fpos.QuadPart = offset;
|
||||
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
|
||||
return traits_type::eof();
|
||||
break;
|
||||
|
||||
case std::ios_base::cur:
|
||||
// If the offset remains in the current buffer range, just
|
||||
// update the pointer.
|
||||
if((offset >= 0 && offset < off_type(egptr()-gptr())) ||
|
||||
(offset < 0 && -offset <= off_type(gptr()-eback())))
|
||||
{
|
||||
// Get the current file offset to report the correct read
|
||||
// offset.
|
||||
fpos.QuadPart = 0;
|
||||
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
|
||||
return traits_type::eof();
|
||||
setg(eback(), gptr()+offset, egptr());
|
||||
return fpos.QuadPart - off_type(egptr()-gptr());
|
||||
}
|
||||
// Need to offset for the file offset being at egptr() while
|
||||
// the requested offset is relative to gptr().
|
||||
offset -= off_type(egptr()-gptr());
|
||||
fpos.QuadPart = offset;
|
||||
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_CURRENT))
|
||||
return traits_type::eof();
|
||||
break;
|
||||
|
||||
case std::ios_base::end:
|
||||
fpos.QuadPart = offset;
|
||||
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_END))
|
||||
return traits_type::eof();
|
||||
break;
|
||||
|
||||
default:
|
||||
return traits_type::eof();
|
||||
}
|
||||
setg(nullptr, nullptr, nullptr);
|
||||
return fpos.QuadPart;
|
||||
}
|
||||
|
||||
auto filebuf::seekpos(pos_type pos, std::ios_base::openmode mode) -> pos_type
|
||||
{
|
||||
// Simplified version of seekoff
|
||||
if(mFile == INVALID_HANDLE_VALUE || (mode&std::ios_base::out) || !(mode&std::ios_base::in))
|
||||
return traits_type::eof();
|
||||
|
||||
LARGE_INTEGER fpos{};
|
||||
fpos.QuadPart = pos;
|
||||
if(!SetFilePointerEx(mFile, fpos, &fpos, FILE_BEGIN))
|
||||
return traits_type::eof();
|
||||
|
||||
setg(nullptr, nullptr, nullptr);
|
||||
return fpos.QuadPart;
|
||||
}
|
||||
|
||||
filebuf::~filebuf()
|
||||
{ close(); }
|
||||
|
||||
bool filebuf::open(const wchar_t *filename, std::ios_base::openmode mode)
|
||||
{
|
||||
if((mode&std::ios_base::out) || !(mode&std::ios_base::in))
|
||||
return false;
|
||||
HANDLE f{CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if(f == INVALID_HANDLE_VALUE) return false;
|
||||
|
||||
if(mFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(mFile);
|
||||
mFile = f;
|
||||
|
||||
setg(nullptr, nullptr, nullptr);
|
||||
return true;
|
||||
}
|
||||
bool filebuf::open(const char *filename, std::ios_base::openmode mode)
|
||||
{
|
||||
std::wstring wname{utf8_to_wstr(filename)};
|
||||
return open(wname.c_str(), mode);
|
||||
}
|
||||
|
||||
void filebuf::close()
|
||||
{
|
||||
if(mFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(mFile);
|
||||
mFile = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
|
||||
ifstream::ifstream(const wchar_t *filename, std::ios_base::openmode mode)
|
||||
: std::istream{nullptr}
|
||||
{
|
||||
init(&mStreamBuf);
|
||||
|
||||
// Set the failbit if the file failed to open.
|
||||
if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in))
|
||||
clear(failbit);
|
||||
}
|
||||
|
||||
ifstream::ifstream(const char *filename, std::ios_base::openmode mode)
|
||||
: std::istream{nullptr}
|
||||
{
|
||||
init(&mStreamBuf);
|
||||
|
||||
// Set the failbit if the file failed to open.
|
||||
if((mode&std::ios_base::out) || !mStreamBuf.open(filename, mode|std::ios_base::in))
|
||||
clear(failbit);
|
||||
}
|
||||
|
||||
/* This is only here to ensure the compiler doesn't define an implicit
|
||||
* destructor, which it tries to automatically inline and subsequently complain
|
||||
* it can't inline without excessive code growth.
|
||||
*/
|
||||
ifstream::~ifstream() { }
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif
|
||||
@@ -1,74 +0,0 @@
|
||||
#ifndef AL_FSTREAM_H
|
||||
#define AL_FSTREAM_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
// Windows' std::ifstream fails with non-ANSI paths since the standard only
|
||||
// specifies names using const char* (or std::string). MSVC has a non-standard
|
||||
// extension using const wchar_t* (or std::wstring?) to handle Unicode paths,
|
||||
// but not all Windows compilers support it. So we have to make our own istream
|
||||
// that accepts UTF-8 paths and forwards to Unicode-aware I/O functions.
|
||||
class filebuf final : public std::streambuf {
|
||||
std::array<char_type,4096> mBuffer;
|
||||
HANDLE mFile{INVALID_HANDLE_VALUE};
|
||||
|
||||
int_type underflow() override;
|
||||
pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override;
|
||||
pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override;
|
||||
|
||||
public:
|
||||
filebuf() = default;
|
||||
~filebuf() override;
|
||||
|
||||
bool open(const wchar_t *filename, std::ios_base::openmode mode);
|
||||
bool open(const char *filename, std::ios_base::openmode mode);
|
||||
|
||||
bool is_open() const noexcept { return mFile != INVALID_HANDLE_VALUE; }
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
// Inherit from std::istream to use our custom streambuf
|
||||
class ifstream final : public std::istream {
|
||||
filebuf mStreamBuf;
|
||||
|
||||
public:
|
||||
ifstream(const wchar_t *filename, std::ios_base::openmode mode = std::ios_base::in);
|
||||
ifstream(const std::wstring &filename, std::ios_base::openmode mode = std::ios_base::in)
|
||||
: ifstream(filename.c_str(), mode) { }
|
||||
ifstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in);
|
||||
ifstream(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in)
|
||||
: ifstream(filename.c_str(), mode) { }
|
||||
~ifstream() override;
|
||||
|
||||
bool is_open() const noexcept { return mStreamBuf.is_open(); }
|
||||
|
||||
void close() { mStreamBuf.close(); }
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace al {
|
||||
|
||||
using filebuf = std::filebuf;
|
||||
using ifstream = std::ifstream;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* AL_FSTREAM_H */
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
void *al_malloc(size_t alignment, size_t size)
|
||||
{
|
||||
assert((alignment & (alignment-1)) == 0);
|
||||
alignment = std::max(alignment, alignof(std::max_align_t));
|
||||
|
||||
#if defined(HAVE_POSIX_MEMALIGN)
|
||||
void *ret{};
|
||||
if(posix_memalign(&ret, alignment, size) == 0)
|
||||
return ret;
|
||||
return nullptr;
|
||||
#elif defined(HAVE__ALIGNED_MALLOC)
|
||||
return _aligned_malloc(size, alignment);
|
||||
#else
|
||||
size_t total_size{size + alignment-1 + sizeof(void*)};
|
||||
void *base{std::malloc(total_size)};
|
||||
if(base != nullptr)
|
||||
{
|
||||
void *aligned_ptr{static_cast<char*>(base) + sizeof(void*)};
|
||||
total_size -= sizeof(void*);
|
||||
|
||||
std::align(alignment, size, aligned_ptr, total_size);
|
||||
*(static_cast<void**>(aligned_ptr)-1) = base;
|
||||
base = aligned_ptr;
|
||||
}
|
||||
return base;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *al_calloc(size_t alignment, size_t size)
|
||||
{
|
||||
void *ret{al_malloc(alignment, size)};
|
||||
if(ret) std::memset(ret, 0, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void al_free(void *ptr) noexcept
|
||||
{
|
||||
#if defined(HAVE_POSIX_MEMALIGN)
|
||||
std::free(ptr);
|
||||
#elif defined(HAVE__ALIGNED_MALLOC)
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
if(ptr != nullptr)
|
||||
std::free(*(static_cast<void**>(ptr) - 1));
|
||||
#endif
|
||||
}
|
||||
@@ -3,290 +3,149 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "pragmadefs.h"
|
||||
#include <variant>
|
||||
|
||||
|
||||
void al_free(void *ptr) noexcept;
|
||||
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
|
||||
void *al_malloc(size_t alignment, size_t size);
|
||||
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
|
||||
void *al_calloc(size_t alignment, size_t size);
|
||||
namespace gsl {
|
||||
template<typename T> using owner = T;
|
||||
};
|
||||
|
||||
|
||||
#define DISABLE_ALLOC() \
|
||||
#define DISABLE_ALLOC \
|
||||
void *operator new(size_t) = delete; \
|
||||
void *operator new[](size_t) = delete; \
|
||||
void operator delete(void*) noexcept = delete; \
|
||||
void operator delete[](void*) noexcept = delete;
|
||||
|
||||
#define DEF_NEWDEL(T) \
|
||||
void *operator new(size_t size) \
|
||||
{ \
|
||||
void *ret = al_malloc(alignof(T), size); \
|
||||
if(!ret) throw std::bad_alloc(); \
|
||||
return ret; \
|
||||
} \
|
||||
void *operator new[](size_t size) { return operator new(size); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block) noexcept { operator delete(block); }
|
||||
|
||||
#define DEF_PLACE_NEWDEL() \
|
||||
void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \
|
||||
void *operator new[](size_t /*size*/, void *ptr) noexcept { return ptr; } \
|
||||
void operator delete(void *block, void*) noexcept { al_free(block); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block, void*) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block) noexcept { al_free(block); }
|
||||
|
||||
enum FamCount : size_t { };
|
||||
|
||||
#define DEF_FAM_NEWDEL(T, FamMem) \
|
||||
static constexpr size_t Sizeof(size_t count) noexcept \
|
||||
{ \
|
||||
static_assert(&Sizeof == &T::Sizeof, \
|
||||
"Incorrect container type specified"); \
|
||||
return std::max(decltype(FamMem)::Sizeof(count, offsetof(T, FamMem)), \
|
||||
sizeof(T)); \
|
||||
} \
|
||||
\
|
||||
void *operator new(size_t /*size*/, FamCount count) \
|
||||
gsl::owner<void*> operator new(size_t /*size*/, FamCount count) \
|
||||
{ \
|
||||
if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \
|
||||
return ret; \
|
||||
throw std::bad_alloc(); \
|
||||
const auto alignment = std::align_val_t{alignof(T)}; \
|
||||
return ::operator new[](T::Sizeof(count), alignment); \
|
||||
} \
|
||||
void operator delete(gsl::owner<void*> block, FamCount) noexcept \
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
|
||||
void operator delete(gsl::owner<void*> block) noexcept \
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
|
||||
void *operator new[](size_t /*size*/) = delete; \
|
||||
void operator delete(void *block, FamCount) { al_free(block); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void* /*block*/) = delete;
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, std::size_t alignment=alignof(T)>
|
||||
template<typename T, std::size_t AlignV=alignof(T)>
|
||||
struct allocator {
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
static constexpr auto Alignment = std::max(AlignV, alignof(T));
|
||||
static constexpr auto AlignVal = std::align_val_t{Alignment};
|
||||
|
||||
using value_type = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using is_always_equal = std::true_type;
|
||||
|
||||
template<typename U>
|
||||
template<typename U, std::enable_if_t<alignof(U) <= Alignment,bool> = true>
|
||||
struct rebind {
|
||||
using other = allocator<U, (alignment<alignof(U))?alignof(U):alignment>;
|
||||
using other = allocator<U,Alignment>;
|
||||
};
|
||||
|
||||
constexpr explicit allocator() noexcept = default;
|
||||
template<typename U, std::size_t N>
|
||||
constexpr explicit allocator(const allocator<U,N>&) noexcept { }
|
||||
constexpr explicit allocator(const allocator<U,N>&) noexcept
|
||||
{ static_assert(Alignment == allocator<U,N>::Alignment); }
|
||||
|
||||
T *allocate(std::size_t n)
|
||||
gsl::owner<T*> allocate(std::size_t n)
|
||||
{
|
||||
if(n > std::numeric_limits<std::size_t>::max()/sizeof(T)) throw std::bad_alloc();
|
||||
if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast<T*>(p);
|
||||
throw std::bad_alloc();
|
||||
return static_cast<gsl::owner<T*>>(::operator new[](n*sizeof(T), AlignVal));
|
||||
}
|
||||
void deallocate(T *p, std::size_t) noexcept { al_free(p); }
|
||||
void deallocate(gsl::owner<T*> p, std::size_t) noexcept
|
||||
{ ::operator delete[](gsl::owner<void*>{p}, AlignVal); }
|
||||
};
|
||||
template<typename T, std::size_t N, typename U, std::size_t M>
|
||||
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
|
||||
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept
|
||||
{ return allocator<T,N>::Alignment == allocator<U,M>::Alignment; }
|
||||
template<typename T, std::size_t N, typename U, std::size_t M>
|
||||
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
|
||||
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept
|
||||
{ return allocator<T,N>::Alignment != allocator<U,M>::Alignment; }
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr T *to_address(T *p) noexcept
|
||||
{
|
||||
static_assert(!std::is_function<T>::value, "Can't be a function type");
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr auto to_address(const T &p) noexcept
|
||||
{
|
||||
return ::al::to_address(p.operator->());
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
constexpr T* construct_at(T *ptr, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
{ return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...}; }
|
||||
|
||||
/* At least VS 2015 complains that 'ptr' is unused when the given type's
|
||||
* destructor is trivial (a no-op). So disable that warning for this call.
|
||||
*/
|
||||
DIAGNOSTIC_PUSH
|
||||
msc_pragma(warning(disable : 4100))
|
||||
template<typename T>
|
||||
constexpr std::enable_if_t<!std::is_array<T>::value>
|
||||
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<T>::value)
|
||||
{ ptr->~T(); }
|
||||
DIAGNOSTIC_POP
|
||||
template<typename T>
|
||||
constexpr std::enable_if_t<std::is_array<T>::value>
|
||||
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<std::remove_all_extents_t<T>>::value)
|
||||
noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
||||
{
|
||||
for(auto &elem : *ptr)
|
||||
al::destroy_at(std::addressof(elem));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
|
||||
{
|
||||
while(first != end)
|
||||
{
|
||||
al::destroy_at(std::addressof(*first));
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename N>
|
||||
constexpr std::enable_if_t<std::is_integral<N>::value,T>
|
||||
destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
|
||||
{
|
||||
if(count != 0)
|
||||
{
|
||||
do {
|
||||
al::destroy_at(std::addressof(*first));
|
||||
++first;
|
||||
} while(--count);
|
||||
}
|
||||
return first;
|
||||
/* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't
|
||||
* necessarily handle the address from an owner, while placement new
|
||||
* expects to.
|
||||
*/
|
||||
return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...};
|
||||
/* NOLINTEND(cppcoreguidelines-owning-memory) */
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename N>
|
||||
inline std::enable_if_t<std::is_integral<N>::value,
|
||||
T> uninitialized_default_construct_n(T first, N count)
|
||||
{
|
||||
using ValueT = typename std::iterator_traits<T>::value_type;
|
||||
T current{first};
|
||||
if(count != 0)
|
||||
template<typename SP, typename PT, typename ...Args>
|
||||
class out_ptr_t {
|
||||
static_assert(!std::is_same_v<PT,void*>);
|
||||
|
||||
SP &mRes;
|
||||
std::variant<PT,void*> mPtr{};
|
||||
|
||||
public:
|
||||
out_ptr_t(SP &res) : mRes{res} { }
|
||||
~out_ptr_t()
|
||||
{
|
||||
try {
|
||||
do {
|
||||
::new(static_cast<void*>(std::addressof(*current))) ValueT;
|
||||
++current;
|
||||
} while(--count);
|
||||
}
|
||||
catch(...) {
|
||||
al::destroy(first, current);
|
||||
throw;
|
||||
}
|
||||
auto set_res = [this](auto &ptr)
|
||||
{ mRes.reset(static_cast<PT>(ptr)); };
|
||||
std::visit(set_res, mPtr);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
out_ptr_t(const out_ptr_t&) = delete;
|
||||
out_ptr_t& operator=(const out_ptr_t&) = delete;
|
||||
|
||||
operator PT*() noexcept
|
||||
{ return &std::get<PT>(mPtr); }
|
||||
|
||||
/* Storage for flexible array data. This is trivially destructible if type T is
|
||||
* trivially destructible.
|
||||
*/
|
||||
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
|
||||
struct FlexArrayStorage {
|
||||
const size_t mSize;
|
||||
union {
|
||||
char mDummy;
|
||||
alignas(alignment) T mArray[1];
|
||||
};
|
||||
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{
|
||||
const size_t len{sizeof(T)*count};
|
||||
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
|
||||
}
|
||||
|
||||
FlexArrayStorage(size_t size) : mSize{size}
|
||||
{ al::uninitialized_default_construct_n(mArray, mSize); }
|
||||
~FlexArrayStorage() = default;
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
operator void**() noexcept
|
||||
{ return &mPtr.template emplace<void*>(); }
|
||||
};
|
||||
|
||||
template<typename T, size_t alignment>
|
||||
struct FlexArrayStorage<T,alignment,false> {
|
||||
const size_t mSize;
|
||||
union {
|
||||
char mDummy;
|
||||
alignas(alignment) T mArray[1];
|
||||
};
|
||||
|
||||
static constexpr size_t Sizeof(size_t count, size_t base) noexcept
|
||||
{
|
||||
const size_t len{sizeof(T)*count};
|
||||
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
|
||||
}
|
||||
|
||||
FlexArrayStorage(size_t size) : mSize{size}
|
||||
{ al::uninitialized_default_construct_n(mArray, mSize); }
|
||||
~FlexArrayStorage() { al::destroy_n(mArray, mSize); }
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
/* A flexible array type. Used either standalone or at the end of a parent
|
||||
* struct, with placement new, to have a run-time-sized array that's embedded
|
||||
* with its size.
|
||||
*/
|
||||
template<typename T, size_t alignment=alignof(T)>
|
||||
struct FlexArray {
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
using Storage_t_ = FlexArrayStorage<element_type,alignment>;
|
||||
|
||||
Storage_t_ mStore;
|
||||
|
||||
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
|
||||
{ return Storage_t_::Sizeof(count, base); }
|
||||
static std::unique_ptr<FlexArray> Create(index_type count)
|
||||
{
|
||||
void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))};
|
||||
return std::unique_ptr<FlexArray>{al::construct_at(static_cast<FlexArray*>(ptr), count)};
|
||||
}
|
||||
|
||||
FlexArray(index_type size) : mStore{size} { }
|
||||
~FlexArray() = default;
|
||||
|
||||
index_type size() const noexcept { return mStore.mSize; }
|
||||
bool empty() const noexcept { return mStore.mSize == 0; }
|
||||
|
||||
pointer data() noexcept { return mStore.mArray; }
|
||||
const_pointer data() const noexcept { return mStore.mArray; }
|
||||
|
||||
reference operator[](index_type i) noexcept { return mStore.mArray[i]; }
|
||||
const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; }
|
||||
|
||||
reference front() noexcept { return mStore.mArray[0]; }
|
||||
const_reference front() const noexcept { return mStore.mArray[0]; }
|
||||
|
||||
reference back() noexcept { return mStore.mArray[mStore.mSize-1]; }
|
||||
const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; }
|
||||
|
||||
iterator begin() noexcept { return mStore.mArray; }
|
||||
const_iterator begin() const noexcept { return mStore.mArray; }
|
||||
const_iterator cbegin() const noexcept { return mStore.mArray; }
|
||||
iterator end() noexcept { return mStore.mArray + mStore.mSize; }
|
||||
const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; }
|
||||
const_iterator cend() const noexcept { return mStore.mArray + mStore.mSize; }
|
||||
|
||||
reverse_iterator rbegin() noexcept { return end(); }
|
||||
const_reverse_iterator rbegin() const noexcept { return end(); }
|
||||
const_reverse_iterator crbegin() const noexcept { return cend(); }
|
||||
reverse_iterator rend() noexcept { return begin(); }
|
||||
const_reverse_iterator rend() const noexcept { return begin(); }
|
||||
const_reverse_iterator crend() const noexcept { return cbegin(); }
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
};
|
||||
template<typename T=void, typename SP, typename ...Args>
|
||||
auto out_ptr(SP &res)
|
||||
{
|
||||
using ptype = typename SP::element_type*;
|
||||
return out_ptr_t<SP,ptype>{res};
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#ifndef COMMON_ALNUMBERS_H
|
||||
#define COMMON_ALNUMBERS_H
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace al {
|
||||
|
||||
namespace numbers {
|
||||
namespace al::numbers {
|
||||
|
||||
namespace detail_ {
|
||||
template<typename T>
|
||||
@@ -13,24 +11,22 @@ namespace detail_ {
|
||||
} // detail_
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
|
||||
inline constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
|
||||
inline constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
|
||||
inline constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
|
||||
inline constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
|
||||
|
||||
static constexpr auto pi = pi_v<double>;
|
||||
static constexpr auto inv_pi = inv_pi_v<double>;
|
||||
static constexpr auto sqrt2 = sqrt2_v<double>;
|
||||
static constexpr auto sqrt3 = sqrt3_v<double>;
|
||||
inline constexpr auto pi = pi_v<double>;
|
||||
inline constexpr auto inv_pi = inv_pi_v<double>;
|
||||
inline constexpr auto sqrt2 = sqrt2_v<double>;
|
||||
inline constexpr auto sqrt3 = sqrt3_v<double>;
|
||||
|
||||
} // namespace numbers
|
||||
|
||||
} // namespace al
|
||||
} // namespace al::numbers
|
||||
|
||||
#endif /* COMMON_ALNUMBERS_H */
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
#define AL_NUMERIC_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
@@ -12,74 +15,34 @@
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "albit.h"
|
||||
#include "altraits.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast<int64_t>(n); }
|
||||
inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast<uint64_t>(n); }
|
||||
constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast<std::int64_t>(n); }
|
||||
constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast<std::uint64_t>(n); }
|
||||
|
||||
constexpr auto operator "" _z(unsigned long long n) noexcept
|
||||
{ return static_cast<std::make_signed_t<std::size_t>>(n); }
|
||||
constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
|
||||
constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
|
||||
|
||||
|
||||
constexpr inline float minf(float a, float b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline float maxf(float a, float b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline float clampf(float val, float min, float max) noexcept
|
||||
{ return minf(max, maxf(min, val)); }
|
||||
|
||||
constexpr inline double mind(double a, double b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline double maxd(double a, double b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline double clampd(double val, double min, double max) noexcept
|
||||
{ return mind(max, maxd(min, val)); }
|
||||
|
||||
constexpr inline unsigned int minu(unsigned int a, unsigned int b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline unsigned int maxu(unsigned int a, unsigned int b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline unsigned int clampu(unsigned int val, unsigned int min, unsigned int max) noexcept
|
||||
{ return minu(max, maxu(min, val)); }
|
||||
|
||||
constexpr inline int mini(int a, int b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline int maxi(int a, int b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline int clampi(int val, int min, int max) noexcept
|
||||
{ return mini(max, maxi(min, val)); }
|
||||
|
||||
constexpr inline int64_t mini64(int64_t a, int64_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline int64_t maxi64(int64_t a, int64_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline int64_t clampi64(int64_t val, int64_t min, int64_t max) noexcept
|
||||
{ return mini64(max, maxi64(min, val)); }
|
||||
|
||||
constexpr inline uint64_t minu64(uint64_t a, uint64_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline uint64_t maxu64(uint64_t a, uint64_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline uint64_t clampu64(uint64_t val, uint64_t min, uint64_t max) noexcept
|
||||
{ return minu64(max, maxu64(min, val)); }
|
||||
|
||||
constexpr inline size_t minz(size_t a, size_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline size_t maxz(size_t a, size_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept
|
||||
{ return minz(max, maxz(min, val)); }
|
||||
constexpr auto GetCounterSuffix(size_t count) noexcept -> const char*
|
||||
{
|
||||
auto &suffix = (((count%100)/10) == 1) ? "th" :
|
||||
((count%10) == 1) ? "st" :
|
||||
((count%10) == 2) ? "nd" :
|
||||
((count%10) == 3) ? "rd" : "th";
|
||||
return std::data(suffix);
|
||||
}
|
||||
|
||||
|
||||
constexpr inline float lerpf(float val1, float val2, float mu) noexcept
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept
|
||||
{
|
||||
const float mu2{mu*mu}, mu3{mu2*mu};
|
||||
const float a0{-0.5f*mu3 + mu2 + -0.5f*mu};
|
||||
const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f};
|
||||
const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu};
|
||||
const float a3{ 0.5f*mu3 + -0.5f*mu2};
|
||||
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
|
||||
}
|
||||
constexpr inline double lerpd(double val1, double val2, double mu) noexcept
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
|
||||
|
||||
/** Find the next power-of-2 for non-power-of-2 numbers. */
|
||||
@@ -97,12 +60,20 @@ inline uint32_t NextPowerOf2(uint32_t value) noexcept
|
||||
return value+1;
|
||||
}
|
||||
|
||||
/** Round up a value to the next multiple. */
|
||||
inline size_t RoundUp(size_t value, size_t r) noexcept
|
||||
{
|
||||
value += r-1;
|
||||
return value - (value%r);
|
||||
}
|
||||
/**
|
||||
* If the value is not already a multiple of r, round down to the next
|
||||
* multiple.
|
||||
*/
|
||||
template<typename T>
|
||||
constexpr T RoundDown(T value, al::type_identity_t<T> r) noexcept
|
||||
{ return value - (value%r); }
|
||||
|
||||
/**
|
||||
* If the value is not already a multiple of r, round up to the next multiple.
|
||||
*/
|
||||
template<typename T>
|
||||
constexpr T RoundUp(T value, al::type_identity_t<T> r) noexcept
|
||||
{ return RoundDown(value + r-1, r); }
|
||||
|
||||
|
||||
/**
|
||||
@@ -116,21 +87,18 @@ inline int fastf2i(float f) noexcept
|
||||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
return _mm_cvt_ss2si(_mm_set_ss(f));
|
||||
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP)
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0
|
||||
|
||||
int i;
|
||||
__asm fld f
|
||||
__asm fistp i
|
||||
return i;
|
||||
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE_MATH__)
|
||||
|
||||
int i;
|
||||
#ifdef __SSE_MATH__
|
||||
__asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
|
||||
#else
|
||||
__asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
|
||||
#endif
|
||||
return i;
|
||||
|
||||
#else
|
||||
@@ -150,22 +118,17 @@ inline int float2int(float f) noexcept
|
||||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \
|
||||
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE_MATH__))
|
||||
int sign, shift, mant;
|
||||
union {
|
||||
float f;
|
||||
int i;
|
||||
} conv;
|
||||
const int conv_i{al::bit_cast<int>(f)};
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31) | 1;
|
||||
shift = ((conv.i>>23)&0xff) - (127+23);
|
||||
const int sign{(conv_i>>31) | 1};
|
||||
const int shift{((conv_i>>23)&0xff) - (127+23)};
|
||||
|
||||
/* Over/underflow */
|
||||
if UNLIKELY(shift >= 31 || shift < -23)
|
||||
if(shift >= 31 || shift < -23) UNLIKELY
|
||||
return 0;
|
||||
|
||||
mant = (conv.i&0x7fffff) | 0x800000;
|
||||
if LIKELY(shift < 0)
|
||||
const int mant{(conv_i&0x7fffff) | 0x800000};
|
||||
if(shift < 0) LIKELY
|
||||
return (mant >> -shift) * sign;
|
||||
return (mant << shift) * sign;
|
||||
|
||||
@@ -186,25 +149,19 @@ inline int double2int(double d) noexcept
|
||||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \
|
||||
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE2_MATH__))
|
||||
int sign, shift;
|
||||
int64_t mant;
|
||||
union {
|
||||
double d;
|
||||
int64_t i64;
|
||||
} conv;
|
||||
const int64_t conv_i64{al::bit_cast<int64_t>(d)};
|
||||
|
||||
conv.d = d;
|
||||
sign = (conv.i64 >> 63) | 1;
|
||||
shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52);
|
||||
const int sign{static_cast<int>(conv_i64 >> 63) | 1};
|
||||
const int shift{(static_cast<int>(conv_i64 >> 52) & 0x7ff) - (1023 + 52)};
|
||||
|
||||
/* Over/underflow */
|
||||
if UNLIKELY(shift >= 63 || shift < -52)
|
||||
if(shift >= 63 || shift < -52) UNLIKELY
|
||||
return 0;
|
||||
|
||||
mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64;
|
||||
if LIKELY(shift < 0)
|
||||
return (int)(mant >> -shift) * sign;
|
||||
return (int)(mant << shift) * sign;
|
||||
const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64};
|
||||
if(shift < 0) LIKELY
|
||||
return static_cast<int>(mant >> -shift) * sign;
|
||||
return static_cast<int>(mant << shift) * sign;
|
||||
|
||||
#else
|
||||
|
||||
@@ -237,21 +194,16 @@ inline float fast_roundf(float f) noexcept
|
||||
/* Integral limit, where sub-integral precision is not available for
|
||||
* floats.
|
||||
*/
|
||||
static const float ilim[2]{
|
||||
static constexpr std::array ilim{
|
||||
8388608.0f /* 0x1.0p+23 */,
|
||||
-8388608.0f /* -0x1.0p+23 */
|
||||
};
|
||||
unsigned int sign, expo;
|
||||
union {
|
||||
float f;
|
||||
unsigned int i;
|
||||
} conv;
|
||||
const unsigned int conv_i{al::bit_cast<unsigned int>(f)};
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31)&0x01;
|
||||
expo = (conv.i>>23)&0xff;
|
||||
const unsigned int sign{(conv_i>>31)&0x01};
|
||||
const unsigned int expo{(conv_i>>23)&0xff};
|
||||
|
||||
if UNLIKELY(expo >= 150/*+23*/)
|
||||
if(expo >= 150/*+23*/) UNLIKELY
|
||||
{
|
||||
/* An exponent (base-2) of 23 or higher is incapable of sub-integral
|
||||
* precision, so it's already an integral value. We don't need to worry
|
||||
@@ -266,20 +218,18 @@ inline float fast_roundf(float f) noexcept
|
||||
* optimize this out because of non-associative rules on floating-point
|
||||
* math (as long as you don't use -fassociative-math,
|
||||
* -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
|
||||
* may break).
|
||||
* may break without __builtin_assoc_barrier support).
|
||||
*/
|
||||
#if HAS_BUILTIN(__builtin_assoc_barrier)
|
||||
return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign];
|
||||
#else
|
||||
f += ilim[sign];
|
||||
return f - ilim[sign];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept
|
||||
{
|
||||
return std::min(std::max(value, min_value), max_value);
|
||||
}
|
||||
|
||||
// Converts level (mB) to gain.
|
||||
inline float level_mb_to_gain(float x)
|
||||
{
|
||||
@@ -293,7 +243,7 @@ inline float gain_to_level_mb(float x)
|
||||
{
|
||||
if (x <= 0.0f)
|
||||
return -10'000.0f;
|
||||
return maxf(std::log10(x) * 2'000.0f, -10'000.0f);
|
||||
return std::max(std::log10(x) * 2'000.0f, -10'000.0f);
|
||||
}
|
||||
|
||||
#endif /* AL_NUMERIC_H */
|
||||
|
||||
@@ -1,353 +0,0 @@
|
||||
#ifndef AL_OPTIONAL_H
|
||||
#define AL_OPTIONAL_H
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
struct nullopt_t { };
|
||||
struct in_place_t { };
|
||||
|
||||
constexpr nullopt_t nullopt{};
|
||||
constexpr in_place_t in_place{};
|
||||
|
||||
#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__))
|
||||
|
||||
namespace detail_ {
|
||||
/* Base storage struct for an optional. Defines a trivial destructor, for types
|
||||
* that can be trivially destructed.
|
||||
*/
|
||||
template<typename T, bool = std::is_trivially_destructible<T>::value>
|
||||
struct optstore_base {
|
||||
bool mHasValue{false};
|
||||
union {
|
||||
char mDummy{};
|
||||
T mValue;
|
||||
};
|
||||
|
||||
constexpr optstore_base() noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optstore_base(in_place_t, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
: mHasValue{true}, mValue{std::forward<Args>(args)...}
|
||||
{ }
|
||||
~optstore_base() = default;
|
||||
};
|
||||
|
||||
/* Specialization needing a non-trivial destructor. */
|
||||
template<typename T>
|
||||
struct optstore_base<T, false> {
|
||||
bool mHasValue{false};
|
||||
union {
|
||||
char mDummy{};
|
||||
T mValue;
|
||||
};
|
||||
|
||||
constexpr optstore_base() noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optstore_base(in_place_t, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
: mHasValue{true}, mValue{std::forward<Args>(args)...}
|
||||
{ }
|
||||
~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
|
||||
};
|
||||
|
||||
/* Next level of storage, which defines helpers to construct and destruct the
|
||||
* stored object.
|
||||
*/
|
||||
template<typename T>
|
||||
struct optstore_helper : public optstore_base<T> {
|
||||
using optstore_base<T>::optstore_base;
|
||||
|
||||
template<typename... Args>
|
||||
constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
{
|
||||
al::construct_at(std::addressof(this->mValue), std::forward<Args>(args)...);
|
||||
this->mHasValue = true;
|
||||
}
|
||||
|
||||
constexpr void reset() noexcept
|
||||
{
|
||||
if(this->mHasValue)
|
||||
al::destroy_at(std::addressof(this->mValue));
|
||||
this->mHasValue = false;
|
||||
}
|
||||
|
||||
constexpr void assign(const optstore_helper &rhs)
|
||||
noexcept(std::is_nothrow_copy_constructible<T>::value
|
||||
&& std::is_nothrow_copy_assignable<T>::value)
|
||||
{
|
||||
if(!rhs.mHasValue)
|
||||
this->reset();
|
||||
else if(this->mHasValue)
|
||||
this->mValue = rhs.mValue;
|
||||
else
|
||||
this->construct(rhs.mValue);
|
||||
}
|
||||
|
||||
constexpr void assign(optstore_helper&& rhs)
|
||||
noexcept(std::is_nothrow_move_constructible<T>::value
|
||||
&& std::is_nothrow_move_assignable<T>::value)
|
||||
{
|
||||
if(!rhs.mHasValue)
|
||||
this->reset();
|
||||
else if(this->mHasValue)
|
||||
this->mValue = std::move(rhs.mValue);
|
||||
else
|
||||
this->construct(std::move(rhs.mValue));
|
||||
}
|
||||
};
|
||||
|
||||
/* Define copy and move constructors and assignment operators, which may or may
|
||||
* not be trivial.
|
||||
*/
|
||||
template<typename T, bool trivial_copy = std::is_trivially_copy_constructible<T>::value,
|
||||
bool trivial_move = std::is_trivially_move_constructible<T>::value,
|
||||
/* Trivial assignment is dependent on trivial construction+destruction. */
|
||||
bool = trivial_copy && std::is_trivially_copy_assignable<T>::value
|
||||
&& std::is_trivially_destructible<T>::value,
|
||||
bool = trivial_move && std::is_trivially_move_assignable<T>::value
|
||||
&& std::is_trivially_destructible<T>::value>
|
||||
struct optional_storage;
|
||||
|
||||
/* Some versions of GCC have issues with 'this' in the following noexcept(...)
|
||||
* statements, so this macro is a workaround.
|
||||
*/
|
||||
#define _this std::declval<optional_storage*>()
|
||||
|
||||
/* Completely trivial. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, true, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial move assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, true, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial move construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, false, true, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial copy assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, false, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial copy construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, true, false, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial assignment, non-trivial move construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, false, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial assignment, non-trivial copy construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, true, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Completely non-trivial. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, false, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
#undef _this
|
||||
|
||||
} // namespace detail_
|
||||
|
||||
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
|
||||
|
||||
template<typename T>
|
||||
class optional {
|
||||
using storage_t = detail_::optional_storage<T>;
|
||||
|
||||
storage_t mStore{};
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
constexpr optional() = default;
|
||||
constexpr optional(const optional&) = default;
|
||||
constexpr optional(optional&&) = default;
|
||||
constexpr optional(nullopt_t) noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optional(in_place_t, Args&& ...args)
|
||||
NOEXCEPT_AS(storage_t{al::in_place, std::forward<Args>(args)...})
|
||||
: mStore{al::in_place, std::forward<Args>(args)...}
|
||||
{ }
|
||||
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
|
||||
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& std::is_convertible<U&&, T>::value)>
|
||||
constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
|
||||
: mStore{al::in_place, std::forward<U>(rhs)}
|
||||
{ }
|
||||
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
|
||||
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& !std::is_convertible<U&&, T>::value)>
|
||||
constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
|
||||
: mStore{al::in_place, std::forward<U>(rhs)}
|
||||
{ }
|
||||
~optional() = default;
|
||||
|
||||
constexpr optional& operator=(const optional&) = default;
|
||||
constexpr optional& operator=(optional&&) = default;
|
||||
constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; }
|
||||
template<typename U=T>
|
||||
constexpr std::enable_if_t<std::is_constructible<T, U>::value
|
||||
&& std::is_assignable<T&, U>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& (!std::is_same<std::decay_t<U>, T>::value || !std::is_scalar<U>::value),
|
||||
optional&> operator=(U&& rhs)
|
||||
{
|
||||
if(mStore.mHasValue)
|
||||
mStore.mValue = std::forward<U>(rhs);
|
||||
else
|
||||
mStore.construct(std::forward<U>(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr const T* operator->() const { return std::addressof(mStore.mValue); }
|
||||
constexpr T* operator->() { return std::addressof(mStore.mValue); }
|
||||
constexpr const T& operator*() const& { return mStore.mValue; }
|
||||
constexpr T& operator*() & { return mStore.mValue; }
|
||||
constexpr const T&& operator*() const&& { return std::move(mStore.mValue); }
|
||||
constexpr T&& operator*() && { return std::move(mStore.mValue); }
|
||||
|
||||
constexpr explicit operator bool() const noexcept { return mStore.mHasValue; }
|
||||
constexpr bool has_value() const noexcept { return mStore.mHasValue; }
|
||||
|
||||
constexpr T& value() & { return mStore.mValue; }
|
||||
constexpr const T& value() const& { return mStore.mValue; }
|
||||
constexpr T&& value() && { return std::move(mStore.mValue); }
|
||||
constexpr const T&& value() const&& { return std::move(mStore.mValue); }
|
||||
|
||||
template<typename U>
|
||||
constexpr T value_or(U&& defval) const&
|
||||
{ return bool{*this} ? **this : static_cast<T>(std::forward<U>(defval)); }
|
||||
template<typename U>
|
||||
constexpr T value_or(U&& defval) &&
|
||||
{ return bool{*this} ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
|
||||
|
||||
template<typename ...Args>
|
||||
constexpr T& emplace(Args&& ...args)
|
||||
{
|
||||
mStore.reset();
|
||||
mStore.construct(std::forward<Args>(args)...);
|
||||
return mStore.mValue;
|
||||
}
|
||||
template<typename U, typename ...Args>
|
||||
constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
|
||||
T&> emplace(std::initializer_list<U> il, Args&& ...args)
|
||||
{
|
||||
mStore.reset();
|
||||
mStore.construct(il, std::forward<Args>(args)...);
|
||||
return mStore.mValue;
|
||||
}
|
||||
|
||||
constexpr void reset() noexcept { mStore.reset(); }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr optional<std::decay_t<T>> make_optional(T&& arg)
|
||||
{ return optional<std::decay_t<T>>{in_place, std::forward<T>(arg)}; }
|
||||
|
||||
template<typename T, typename... Args>
|
||||
constexpr optional<T> make_optional(Args&& ...args)
|
||||
{ return optional<T>{in_place, std::forward<Args>(args)...}; }
|
||||
|
||||
template<typename T, typename U, typename... Args>
|
||||
constexpr optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
|
||||
{ return optional<T>{in_place, il, std::forward<Args>(args)...}; }
|
||||
|
||||
#undef REQUIRES
|
||||
#undef NOEXCEPT_AS
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_OPTIONAL_H */
|
||||
@@ -20,11 +20,12 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "opthelpers.h"
|
||||
#include "threads.h"
|
||||
#include "alsem.h"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -32,34 +33,6 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
void althrd_setname(const char *name)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
#define MS_VC_EXCEPTION 0x406D1388
|
||||
#pragma pack(push,8)
|
||||
struct {
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} info;
|
||||
#pragma pack(pop)
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = ~DWORD{0};
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except(EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
#undef MS_VC_EXCEPTION
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace al {
|
||||
|
||||
semaphore::semaphore(unsigned int initial)
|
||||
@@ -76,7 +49,7 @@ semaphore::~semaphore()
|
||||
|
||||
void semaphore::post()
|
||||
{
|
||||
if UNLIKELY(!ReleaseSemaphore(static_cast<HANDLE>(mSem), 1, nullptr))
|
||||
if(!ReleaseSemaphore(static_cast<HANDLE>(mSem), 1, nullptr))
|
||||
throw std::system_error(std::make_error_code(std::errc::value_too_large));
|
||||
}
|
||||
|
||||
@@ -90,44 +63,8 @@ bool semaphore::try_wait() noexcept
|
||||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef HAVE_PTHREAD_NP_H
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
#include <tuple>
|
||||
|
||||
namespace {
|
||||
|
||||
using setname_t1 = int(*)(const char*);
|
||||
using setname_t2 = int(*)(pthread_t, const char*);
|
||||
using setname_t3 = int(*)(pthread_t, const char*, void*);
|
||||
|
||||
void setname_caller(setname_t1 func, const char *name)
|
||||
{ func(name); }
|
||||
|
||||
void setname_caller(setname_t2 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
void setname_caller(setname_t3 func, const char *name)
|
||||
{ func(pthread_self(), "%s", static_cast<void*>(const_cast<char*>(name))); }
|
||||
|
||||
} // namespace
|
||||
|
||||
void althrd_setname(const char *name)
|
||||
{
|
||||
#if defined(HAVE_PTHREAD_SET_NAME_NP)
|
||||
setname_caller(pthread_set_name_np, name);
|
||||
#elif defined(HAVE_PTHREAD_SETNAME_NP)
|
||||
setname_caller(pthread_setname_np, name);
|
||||
#endif
|
||||
/* Avoid unused function/parameter warnings. */
|
||||
std::ignore = name;
|
||||
std::ignore = static_cast<void(*)(setname_t1,const char*)>(&setname_caller);
|
||||
std::ignore = static_cast<void(*)(setname_t2,const char*)>(&setname_caller);
|
||||
std::ignore = static_cast<void(*)(setname_t3,const char*)>(&setname_caller);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
/* Do not try using libdispatch on systems where it is absent. */
|
||||
#if defined(AL_APPLE_HAVE_DISPATCH)
|
||||
|
||||
namespace al {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef COMMON_ALSEM_H
|
||||
#define COMMON_ALSEM_H
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <TargetConditionals.h>
|
||||
#if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define AL_APPLE_HAVE_DISPATCH 1
|
||||
#else
|
||||
#include <semaphore.h> /* Fallback option for Apple without a working libdispatch */
|
||||
#endif
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
class semaphore {
|
||||
#ifdef _WIN32
|
||||
using native_type = void*;
|
||||
#elif defined(AL_APPLE_HAVE_DISPATCH)
|
||||
using native_type = dispatch_semaphore_t;
|
||||
#else
|
||||
using native_type = sem_t;
|
||||
#endif
|
||||
native_type mSem{};
|
||||
|
||||
public:
|
||||
semaphore(unsigned int initial=0);
|
||||
semaphore(const semaphore&) = delete;
|
||||
~semaphore();
|
||||
|
||||
semaphore& operator=(const semaphore&) = delete;
|
||||
|
||||
void post();
|
||||
void wait() noexcept;
|
||||
bool try_wait() noexcept;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* COMMON_ALSEM_H */
|
||||
+335
-174
@@ -1,165 +1,250 @@
|
||||
#ifndef AL_SPAN_H
|
||||
#define AL_SPAN_H
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "altraits.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
/* This is here primarily to help ensure proper behavior for span's iterators,
|
||||
* being an actual object with member functions instead of a raw pointer (which
|
||||
* has requirements like + and - working with ptrdiff_t). This also helps
|
||||
* silence clang-tidy's pointer arithmetic warnings for span and FlexArray
|
||||
* iterators. It otherwise behaves like a plain pointer and should optimize
|
||||
* accordingly.
|
||||
*
|
||||
* Shouldn't be needed once we use std::span in C++20.
|
||||
*/
|
||||
template<typename T>
|
||||
constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size())
|
||||
{ return cont.size(); }
|
||||
class ptr_wrapper {
|
||||
static_assert(std::is_pointer_v<T>);
|
||||
T mPointer{};
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t size(const T (&)[N]) noexcept
|
||||
{ return N; }
|
||||
public:
|
||||
using value_type = std::remove_pointer_t<T>;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { }
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; }
|
||||
constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; }
|
||||
constexpr auto operator++(int) noexcept -> ptr_wrapper
|
||||
{
|
||||
auto temp = *this;
|
||||
++*this;
|
||||
return temp;
|
||||
}
|
||||
constexpr auto operator--(int) noexcept -> ptr_wrapper
|
||||
{
|
||||
auto temp = *this;
|
||||
--*this;
|
||||
return temp;
|
||||
}
|
||||
|
||||
constexpr
|
||||
auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; }
|
||||
constexpr
|
||||
auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; }
|
||||
|
||||
[[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; }
|
||||
[[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];}
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{lhs.mPointer + n}; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{n + rhs.mPointer}; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{lhs.mPointer - n}; }
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t
|
||||
{ return lhs.mPointer - rhs.mPointer; }
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer == rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer != rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer <= rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer >= rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer < rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer > rhs.mPointer; }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
|
||||
{ return cont.data(); }
|
||||
inline constexpr std::size_t dynamic_extent{static_cast<std::size_t>(-1)};
|
||||
|
||||
template<typename T>
|
||||
constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
|
||||
{ return cont.data(); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr T* data(T (&arr)[N]) noexcept
|
||||
{ return arr; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T* data(std::initializer_list<T> list) noexcept
|
||||
{ return list.begin(); }
|
||||
|
||||
|
||||
constexpr size_t dynamic_extent{static_cast<size_t>(-1)};
|
||||
|
||||
template<typename T, size_t E=dynamic_extent>
|
||||
template<typename T, std::size_t E=dynamic_extent>
|
||||
class span;
|
||||
|
||||
namespace detail_ {
|
||||
template<typename... Ts>
|
||||
struct make_void { using type = void; };
|
||||
template<typename... Ts>
|
||||
using void_t = typename make_void<Ts...>::type;
|
||||
|
||||
template<typename T>
|
||||
struct is_span_ : std::false_type { };
|
||||
template<typename T, size_t E>
|
||||
template<typename T, std::size_t E>
|
||||
struct is_span_<span<T,E>> : std::true_type { };
|
||||
template<typename T>
|
||||
using is_span = is_span_<std::remove_cv_t<T>>;
|
||||
inline constexpr bool is_span_v = is_span_<std::remove_cv_t<T>>::value;
|
||||
|
||||
template<typename T>
|
||||
struct is_std_array_ : std::false_type { };
|
||||
template<typename T, size_t N>
|
||||
template<typename T, std::size_t N>
|
||||
struct is_std_array_<std::array<T,N>> : std::true_type { };
|
||||
template<typename T>
|
||||
using is_std_array = is_std_array_<std::remove_cv_t<T>>;
|
||||
inline constexpr bool is_std_array_v = is_std_array_<std::remove_cv_t<T>>::value;
|
||||
|
||||
template<typename T, typename = void>
|
||||
struct has_size_and_data : std::false_type { };
|
||||
inline constexpr bool has_size_and_data = false;
|
||||
template<typename T>
|
||||
struct has_size_and_data<T,
|
||||
void_t<decltype(al::size(std::declval<T>())), decltype(al::data(std::declval<T>()))>>
|
||||
: std::true_type { };
|
||||
inline constexpr bool has_size_and_data<T,
|
||||
std::void_t<decltype(std::size(std::declval<T>())),decltype(std::data(std::declval<T>()))>>
|
||||
= true;
|
||||
|
||||
template<typename C>
|
||||
inline constexpr bool is_valid_container_type = !is_span_v<C> && !is_std_array_v<C>
|
||||
&& !std::is_array<C>::value && has_size_and_data<C>;
|
||||
|
||||
template<typename T, typename U>
|
||||
inline constexpr bool is_array_compatible = std::is_convertible<T(*)[],U(*)[]>::value; /* NOLINT(*-avoid-c-arrays) */
|
||||
|
||||
template<typename C, typename T>
|
||||
inline constexpr bool is_valid_container = is_valid_container_type<C>
|
||||
&& is_array_compatible<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>,T>;
|
||||
} // namespace detail_
|
||||
|
||||
#define REQUIRES(...) bool rt_=true, std::enable_if_t<rt_ && (__VA_ARGS__),bool> = true
|
||||
#define IS_VALID_CONTAINER(C) \
|
||||
!detail_::is_span<C>::value && !detail_::is_std_array<C>::value && \
|
||||
!std::is_array<C>::value && detail_::has_size_and_data<C>::value && \
|
||||
std::is_convertible<std::remove_pointer_t<decltype(al::data(std::declval<C&>()))>(*)[],element_type(*)[]>::value
|
||||
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
|
||||
|
||||
template<typename T, size_t E>
|
||||
template<typename T, std::size_t E>
|
||||
class span {
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using iterator = ptr_wrapper<pointer>;
|
||||
using const_iterator = ptr_wrapper<const_pointer>;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
static constexpr size_t extent{E};
|
||||
static constexpr std::size_t extent{E};
|
||||
|
||||
template<REQUIRES(extent==0)>
|
||||
template<bool is0=(extent == 0), REQUIRES(is0)>
|
||||
constexpr span() noexcept { }
|
||||
constexpr span(pointer ptr, index_type /*count*/) : mData{ptr} { }
|
||||
constexpr span(pointer first, pointer /*last*/) : mData{first} { }
|
||||
constexpr span(element_type (&arr)[E]) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
constexpr span(std::array<value_type,E> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
template<REQUIRES(std::is_const<element_type>::value)>
|
||||
constexpr span(const std::array<value_type,E> &arr) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
{ }
|
||||
template<typename U, REQUIRES(IS_VALID_CONTAINER(U))>
|
||||
constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { }
|
||||
template<typename U, REQUIRES(IS_VALID_CONTAINER(const U))>
|
||||
constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { }
|
||||
template<typename U, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& std::is_convertible<U(*)[],element_type(*)[]>::value)>
|
||||
constexpr span(const span<U,E> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
|
||||
template<typename U>
|
||||
constexpr explicit span(U iter, size_type size_ [[maybe_unused]])
|
||||
: mData{::al::to_address(iter)}
|
||||
{ assert(size_ == extent); }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
|
||||
constexpr explicit span(U first, V last [[maybe_unused]]) : mData{::al::to_address(first)}
|
||||
{ assert(static_cast<std::size_t>(last-first) == extent); }
|
||||
|
||||
template<std::size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
|
||||
: mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
template<std::size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
template<typename U=T, std::size_t N, REQUIRES(std::is_const<U>::value)>
|
||||
constexpr span(const std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
|
||||
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
|
||||
constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { }
|
||||
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == dynamic_extent)>
|
||||
constexpr explicit span(const span<U,N> &span_) noexcept : mData{std::data(span_)}
|
||||
{ assert(std::size(span_) == extent); }
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == extent)>
|
||||
constexpr span(const span<U,N> &span_) noexcept : mData{std::data(span_)} { }
|
||||
constexpr span(const span&) noexcept = default;
|
||||
|
||||
constexpr span& operator=(const span &rhs) noexcept = default;
|
||||
|
||||
constexpr reference front() const { return *mData; }
|
||||
constexpr reference back() const { return *(mData+E-1); }
|
||||
constexpr reference operator[](index_type idx) const { return mData[idx]; }
|
||||
constexpr pointer data() const noexcept { return mData; }
|
||||
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
|
||||
[[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; }
|
||||
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; }
|
||||
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
|
||||
|
||||
constexpr index_type size() const noexcept { return E; }
|
||||
constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); }
|
||||
constexpr bool empty() const noexcept { return E == 0; }
|
||||
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; }
|
||||
[[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); }
|
||||
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return E == 0; }
|
||||
|
||||
constexpr iterator begin() const noexcept { return mData; }
|
||||
constexpr iterator end() const noexcept { return mData+E; }
|
||||
constexpr const_iterator cbegin() const noexcept { return mData; }
|
||||
constexpr const_iterator cend() const noexcept { return mData+E; }
|
||||
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
|
||||
[[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; }
|
||||
|
||||
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
|
||||
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
|
||||
constexpr const_reverse_iterator crbegin() const noexcept
|
||||
{ return const_reverse_iterator{cend()}; }
|
||||
constexpr const_reverse_iterator crend() const noexcept
|
||||
{ return const_reverse_iterator{cbegin()}; }
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> first() const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> last() const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData+(E-C), C};
|
||||
}
|
||||
|
||||
template<size_t O, size_t C>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
static_assert(E-O >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData+O, C};
|
||||
}
|
||||
|
||||
template<size_t O, size_t C=dynamic_extent>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
return span<element_type,E-O>{mData+O, E-O};
|
||||
@@ -169,10 +254,12 @@ public:
|
||||
* defining the specialization. As a result, these methods need to be
|
||||
* defined later.
|
||||
*/
|
||||
constexpr span<element_type,dynamic_extent> first(size_t count) const;
|
||||
constexpr span<element_type,dynamic_extent> last(size_t count) const;
|
||||
constexpr span<element_type,dynamic_extent> subspan(size_t offset,
|
||||
size_t count=dynamic_extent) const;
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const
|
||||
-> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const
|
||||
-> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr auto subspan(std::size_t offset,
|
||||
std::size_t count=dynamic_extent) const -> span<element_type,dynamic_extent>;
|
||||
|
||||
private:
|
||||
pointer mData{nullptr};
|
||||
@@ -183,7 +270,7 @@ class span<T,dynamic_extent> {
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
@@ -191,115 +278,189 @@ public:
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using iterator = ptr_wrapper<pointer>;
|
||||
using const_iterator = ptr_wrapper<const_pointer>;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
static constexpr size_t extent{dynamic_extent};
|
||||
static constexpr std::size_t extent{dynamic_extent};
|
||||
|
||||
constexpr span() noexcept = default;
|
||||
constexpr span(pointer ptr, index_type count) : mData{ptr}, mDataEnd{ptr+count} { }
|
||||
constexpr span(pointer first, pointer last) : mData{first}, mDataEnd{last} { }
|
||||
template<size_t N>
|
||||
constexpr span(element_type (&arr)[N]) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
template<size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
template<size_t N, REQUIRES(std::is_const<element_type>::value)>
|
||||
constexpr span(const std::array<value_type,N> &arr) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
template<typename U>
|
||||
constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count}
|
||||
{ }
|
||||
template<typename U, REQUIRES(IS_VALID_CONTAINER(U))>
|
||||
constexpr span(U &cont) : span{al::data(cont), al::size(cont)} { }
|
||||
template<typename U, REQUIRES(IS_VALID_CONTAINER(const U))>
|
||||
constexpr span(const U &cont) : span{al::data(cont), al::size(cont)} { }
|
||||
template<typename U, size_t N, REQUIRES((!std::is_same<element_type,U>::value || extent != N)
|
||||
&& std::is_convertible<U(*)[],element_type(*)[]>::value)>
|
||||
constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
|
||||
constexpr span(U first, V last)
|
||||
: span{::al::to_address(first), static_cast<std::size_t>(last-first)}
|
||||
{ }
|
||||
|
||||
template<std::size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
template<std::size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
template<std::size_t N, typename U=T, REQUIRES(std::is_const<U>::value)>
|
||||
constexpr span(const std::array<value_type,N> &arr) noexcept
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
|
||||
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
|
||||
constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { }
|
||||
|
||||
template<typename U, std::size_t N, REQUIRES(detail_::is_array_compatible<U,element_type>
|
||||
&& (!std::is_same<element_type,U>::value || extent != N))>
|
||||
constexpr span(const span<U,N> &span_) noexcept : span{std::data(span_), std::size(span_)} { }
|
||||
constexpr span(const span&) noexcept = default;
|
||||
|
||||
constexpr span& operator=(const span &rhs) noexcept = default;
|
||||
|
||||
constexpr reference front() const { return *mData; }
|
||||
constexpr reference back() const { return *(mDataEnd-1); }
|
||||
constexpr reference operator[](index_type idx) const { return mData[idx]; }
|
||||
constexpr pointer data() const noexcept { return mData; }
|
||||
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
|
||||
[[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; }
|
||||
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];}
|
||||
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
|
||||
|
||||
constexpr index_type size() const noexcept { return static_cast<index_type>(mDataEnd-mData); }
|
||||
constexpr index_type size_bytes() const noexcept
|
||||
{ return static_cast<index_type>(mDataEnd-mData) * sizeof(value_type); }
|
||||
constexpr bool empty() const noexcept { return mData == mDataEnd; }
|
||||
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; }
|
||||
[[nodiscard]] constexpr
|
||||
auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); }
|
||||
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; }
|
||||
|
||||
constexpr iterator begin() const noexcept { return mData; }
|
||||
constexpr iterator end() const noexcept { return mDataEnd; }
|
||||
constexpr const_iterator cbegin() const noexcept { return mData; }
|
||||
constexpr const_iterator cend() const noexcept { return mDataEnd; }
|
||||
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; }
|
||||
|
||||
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
|
||||
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
|
||||
constexpr const_reverse_iterator crbegin() const noexcept
|
||||
{ return const_reverse_iterator{cend()}; }
|
||||
constexpr const_reverse_iterator crend() const noexcept
|
||||
{ return const_reverse_iterator{cbegin()}; }
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> first() const
|
||||
{ return span<element_type,C>{mData, C}; }
|
||||
|
||||
constexpr span first(size_t count) const
|
||||
{ return (count >= size()) ? *this : span{mData, mData+count}; }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> last() const
|
||||
{ return span<element_type,C>{mDataEnd-C, C}; }
|
||||
|
||||
constexpr span last(size_t count) const
|
||||
{ return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; }
|
||||
|
||||
template<size_t O, size_t C>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{ return span<element_type,C>{mData+O, C}; }
|
||||
|
||||
template<size_t O, size_t C=dynamic_extent>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
{ return span<element_type,C>{mData+O, mDataEnd}; }
|
||||
|
||||
constexpr span subspan(size_t offset, size_t count=dynamic_extent) const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const -> span<element_type,C>
|
||||
{
|
||||
return (offset > size()) ? span{} :
|
||||
(count >= size()-offset) ? span{mData+offset, mDataEnd} :
|
||||
span{mData+offset, mData+offset+count};
|
||||
if(C > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const -> span
|
||||
{
|
||||
if(count > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span{mData, count};
|
||||
}
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const -> span<element_type,C>
|
||||
{
|
||||
if(C > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span<element_type,C>{mData+mDataLength-C, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const -> span
|
||||
{
|
||||
if(count > mDataLength)
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span{mData+mDataLength-count, count};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
if(O > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
if(C > mDataLength-O)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
return span<element_type,C>{mData+O, C};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
if(O > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
return span<element_type,C>{mData+O, mDataLength-O};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const -> span
|
||||
{
|
||||
if(offset > mDataLength)
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
if(count > mDataLength-offset)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
return span{mData+offset, count};
|
||||
}
|
||||
return span{mData+offset, mDataLength-offset};
|
||||
}
|
||||
|
||||
private:
|
||||
pointer mData{nullptr};
|
||||
pointer mDataEnd{nullptr};
|
||||
size_type mDataLength{0};
|
||||
};
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::first(size_t count) const -> span<element_type,dynamic_extent>
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::first(std::size_t count) const -> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (count >= size()) ? span<element_type>{mData, extent} :
|
||||
span<element_type>{mData, count};
|
||||
if(count > size())
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span<element_type>{mData, count};
|
||||
}
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::last(size_t count) const -> span<element_type,dynamic_extent>
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::last(std::size_t count) const -> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (count >= size()) ? span<element_type>{mData, extent} :
|
||||
span<element_type>{mData+extent-count, count};
|
||||
if(count > size())
|
||||
throw std::out_of_range{"Subspan count out of range"};
|
||||
return span<element_type>{mData+size()-count, count};
|
||||
}
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::subspan(size_t offset, size_t count) const
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::subspan(std::size_t offset, std::size_t count) const
|
||||
-> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (offset > size()) ? span<element_type>{} :
|
||||
(count >= size()-offset) ? span<element_type>{mData+offset, mData+extent} :
|
||||
span<element_type>{mData+offset, mData+offset+count};
|
||||
if(offset > size())
|
||||
throw std::out_of_range{"Subspan offset out of range"};
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
if(count > size()-offset)
|
||||
throw std::out_of_range{"Subspan length out of range"};
|
||||
return span<element_type>{mData+offset, count};
|
||||
}
|
||||
return span<element_type>{mData+offset, size()-offset};
|
||||
}
|
||||
|
||||
#undef IS_VALID_CONTAINER
|
||||
|
||||
template<typename T, typename EndOrSize>
|
||||
span(T, EndOrSize) -> span<std::remove_reference_t<decltype(*std::declval<T&>())>>;
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(T (&)[N]) -> span<T, N>; /* NOLINT(*-avoid-c-arrays) */
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(std::array<T, N>&) -> span<T, N>;
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(const std::array<T, N>&) -> span<const T, N>;
|
||||
|
||||
template<typename C, REQUIRES(detail_::is_valid_container_type<C>)>
|
||||
span(C&&) -> span<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>>;
|
||||
|
||||
#undef REQUIRES
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -3,43 +3,41 @@
|
||||
|
||||
#include "alstring.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
int to_upper(const char ch)
|
||||
{
|
||||
using char8_traits = std::char_traits<char>;
|
||||
return std::toupper(char8_traits::to_int_type(ch));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace al {
|
||||
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept
|
||||
int case_compare(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{
|
||||
do {
|
||||
const int diff{to_upper(*str0) - to_upper(*str1)};
|
||||
if(diff < 0) return -1;
|
||||
if(diff > 0) return 1;
|
||||
} while(*(str0++) && *(str1++));
|
||||
using Traits = std::string_view::traits_type;
|
||||
|
||||
auto ch0 = str0.cbegin();
|
||||
auto ch1 = str1.cbegin();
|
||||
auto ch1end = ch1 + std::min(str0.size(), str1.size());
|
||||
while(ch1 != ch1end)
|
||||
{
|
||||
const int u0{std::toupper(Traits::to_int_type(*ch0))};
|
||||
const int u1{std::toupper(Traits::to_int_type(*ch1))};
|
||||
if(const int diff{u0-u1}) return diff;
|
||||
++ch0; ++ch1;
|
||||
}
|
||||
|
||||
if(str0.size() < str1.size()) return -1;
|
||||
if(str0.size() > str1.size()) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept
|
||||
{ return case_compare(str0, str1); }
|
||||
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept
|
||||
{
|
||||
if(len > 0)
|
||||
{
|
||||
do {
|
||||
const int diff{to_upper(*str0) - to_upper(*str1)};
|
||||
if(diff < 0) return -1;
|
||||
if(diff > 0) return 1;
|
||||
} while(--len && *(str0++) && *(str1++));
|
||||
}
|
||||
return 0;
|
||||
return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)},
|
||||
std::string_view{str1, std::min(std::strlen(str1), len)});
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
#ifndef AL_STRING_H
|
||||
#define AL_STRING_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
/* These would be better served by using a string_view-like span/view with
|
||||
* case-insensitive char traits.
|
||||
*/
|
||||
template<typename T, typename Traits>
|
||||
[[nodiscard]] constexpr
|
||||
auto sizei(const std::basic_string_view<T,Traits> str) noexcept -> int
|
||||
{ return static_cast<int>(std::min<std::size_t>(str.size(), std::numeric_limits<int>::max())); }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.find(str1) != std::string_view::npos; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.substr(0, std::min(str0.size(), str1.size())) == str1; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; }
|
||||
|
||||
[[nodiscard]]
|
||||
int case_compare(const std::string_view str0, const std::string_view str1) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept;
|
||||
[[nodiscard]]
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept;
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "althrd_setname.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
void althrd_setname(const char *name [[maybe_unused]])
|
||||
{
|
||||
#if defined(_MSC_VER) && !defined(_M_ARM)
|
||||
|
||||
#define MS_VC_EXCEPTION 0x406D1388
|
||||
#pragma pack(push,8)
|
||||
struct InfoStruct {
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
};
|
||||
#pragma pack(pop)
|
||||
InfoStruct info{};
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = ~DWORD{0};
|
||||
info.dwFlags = 0;
|
||||
|
||||
/* FIXME: How to do this on MinGW? */
|
||||
__try {
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except(EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
#undef MS_VC_EXCEPTION
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef HAVE_PTHREAD_NP_H
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using setname_t1 = int(*)(const char*);
|
||||
using setname_t2 = int(*)(pthread_t, const char*);
|
||||
using setname_t3 = void(*)(pthread_t, const char*);
|
||||
using setname_t4 = int(*)(pthread_t, const char*, void*);
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t1 func, const char *name)
|
||||
{ func(name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t2 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t3 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t4 func, const char *name)
|
||||
{ func(pthread_self(), "%s", const_cast<char*>(name)); /* NOLINT(*-const-cast) */ }
|
||||
|
||||
} // namespace
|
||||
|
||||
void althrd_setname(const char *name [[maybe_unused]])
|
||||
{
|
||||
#if defined(HAVE_PTHREAD_SET_NAME_NP)
|
||||
setname_caller(pthread_set_name_np, name);
|
||||
#elif defined(HAVE_PTHREAD_SETNAME_NP)
|
||||
setname_caller(pthread_setname_np, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef COMMON_ALTHRD_SETNAME_H
|
||||
#define COMMON_ALTHRD_SETNAME_H
|
||||
|
||||
void althrd_setname(const char *name);
|
||||
|
||||
#endif /* COMMON_ALTHRD_SETNAME_H */
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifndef AL_THREADS_H
|
||||
#define AL_THREADS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <threads.h>
|
||||
#endif
|
||||
|
||||
#include "albit.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
class tss {
|
||||
static_assert(sizeof(T) <= sizeof(void*));
|
||||
static_assert(std::is_trivially_destructible_v<T> && std::is_trivially_copy_constructible_v<T>);
|
||||
|
||||
[[nodiscard]]
|
||||
static auto to_ptr(const T &value) noexcept -> void*
|
||||
{
|
||||
if constexpr(std::is_pointer_v<T>)
|
||||
{
|
||||
if constexpr(std::is_const_v<std::remove_pointer_t<T>>)
|
||||
return const_cast<void*>(static_cast<const void*>(value)); /* NOLINT(*-const-cast) */
|
||||
else
|
||||
return static_cast<void*>(value);
|
||||
}
|
||||
else if constexpr(sizeof(T) == sizeof(void*))
|
||||
return al::bit_cast<void*>(value);
|
||||
else if constexpr(std::is_integral_v<T>)
|
||||
return al::bit_cast<void*>(static_cast<std::uintptr_t>(value));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static auto from_ptr(void *ptr) noexcept -> T
|
||||
{
|
||||
if constexpr(std::is_pointer_v<T>)
|
||||
return static_cast<T>(ptr);
|
||||
else if constexpr(sizeof(T) == sizeof(void*))
|
||||
return al::bit_cast<T>(ptr);
|
||||
else if constexpr(std::is_integral_v<T>)
|
||||
return static_cast<T>(al::bit_cast<std::uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD mTss;
|
||||
|
||||
public:
|
||||
tss() : mTss{TlsAlloc()}
|
||||
{
|
||||
if(mTss == TLS_OUT_OF_INDEXES)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(TlsSetValue(mTss, to_ptr(init)) == FALSE)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { TlsFree(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(TlsSetValue(mTss, to_ptr(value)) == FALSE)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); }
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
pthread_key_t mTss;
|
||||
|
||||
public:
|
||||
tss()
|
||||
{
|
||||
if(int res{pthread_key_create(&mTss, nullptr)}; res != 0)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { pthread_key_delete(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); }
|
||||
|
||||
#else
|
||||
|
||||
tss_t mTss;
|
||||
|
||||
public:
|
||||
tss()
|
||||
{
|
||||
if(int res{tss_create(&mTss, nullptr)}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { tss_delete(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); }
|
||||
#endif /* _WIN32 */
|
||||
|
||||
tss(const tss&) = delete;
|
||||
tss(tss&&) = delete;
|
||||
void operator=(const tss&) = delete;
|
||||
void operator=(tss&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_THREADS_H */
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef COMMON_ALTRAITS_H
|
||||
#define COMMON_ALTRAITS_H
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
struct type_identity { using type = T; };
|
||||
|
||||
template<typename T>
|
||||
using type_identity_t = typename type_identity<T>::type;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* COMMON_ALTRAITS_H */
|
||||
@@ -2,17 +2,17 @@
|
||||
#define AL_ATOMIC_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
using RefCount = std::atomic<unsigned int>;
|
||||
|
||||
inline void InitRef(RefCount &ref, unsigned int value)
|
||||
{ ref.store(value, std::memory_order_relaxed); }
|
||||
inline unsigned int ReadRef(RefCount &ref)
|
||||
{ return ref.load(std::memory_order_acquire); }
|
||||
inline unsigned int IncrementRef(RefCount &ref)
|
||||
template<typename T>
|
||||
auto IncrementRef(std::atomic<T> &ref) noexcept
|
||||
{ return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; }
|
||||
inline unsigned int DecrementRef(RefCount &ref)
|
||||
|
||||
template<typename T>
|
||||
auto DecrementRef(std::atomic<T> &ref) noexcept
|
||||
{ return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; }
|
||||
|
||||
|
||||
@@ -30,4 +30,75 @@ inline void AtomicReplaceHead(std::atomic<T> &head, T newhead)
|
||||
std::memory_order_acq_rel, std::memory_order_acquire));
|
||||
}
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, typename D=std::default_delete<T>>
|
||||
class atomic_unique_ptr {
|
||||
std::atomic<gsl::owner<T*>> mPointer{};
|
||||
|
||||
using unique_ptr_t = std::unique_ptr<T,D>;
|
||||
|
||||
public:
|
||||
atomic_unique_ptr() = default;
|
||||
atomic_unique_ptr(const atomic_unique_ptr&) = delete;
|
||||
explicit atomic_unique_ptr(std::nullptr_t) noexcept { }
|
||||
explicit atomic_unique_ptr(gsl::owner<T*> ptr) noexcept : mPointer{ptr} { }
|
||||
explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { }
|
||||
~atomic_unique_ptr()
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed))
|
||||
D{}(ptr);
|
||||
}
|
||||
|
||||
auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete;
|
||||
auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr&
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(nullptr))
|
||||
D{}(ptr);
|
||||
return *this;
|
||||
}
|
||||
auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr&
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(rhs.release()))
|
||||
D{}(ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T*
|
||||
{ return mPointer.load(m); }
|
||||
void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(nullptr, m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
void store(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(ptr, m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(ptr.release(), m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(nullptr, m)}; }
|
||||
[[nodiscard]]
|
||||
auto exchange(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(ptr, m)}; }
|
||||
[[nodiscard]]
|
||||
auto exchange(std::unique_ptr<T>&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; }
|
||||
|
||||
[[nodiscard]]
|
||||
auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); }
|
||||
|
||||
static constexpr auto is_always_lock_free = std::atomic<gsl::owner<T*>>::is_always_lock_free;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_ATOMIC_H */
|
||||
|
||||
@@ -2,49 +2,84 @@
|
||||
#define COMMON_COMPTR_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "opthelpers.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
struct ComWrapper {
|
||||
HRESULT mStatus{};
|
||||
|
||||
ComWrapper(void *reserved, DWORD coinit)
|
||||
: mStatus{CoInitializeEx(reserved, coinit)}
|
||||
{ }
|
||||
ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED)
|
||||
: mStatus{CoInitializeEx(nullptr, coinit)}
|
||||
{ }
|
||||
ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); }
|
||||
ComWrapper(const ComWrapper&) = delete;
|
||||
~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); }
|
||||
|
||||
ComWrapper& operator=(ComWrapper&& rhs)
|
||||
{
|
||||
if(SUCCEEDED(mStatus))
|
||||
CoUninitialize();
|
||||
mStatus = std::exchange(rhs.mStatus, E_FAIL);
|
||||
return *this;
|
||||
}
|
||||
ComWrapper& operator=(const ComWrapper&) = delete;
|
||||
|
||||
HRESULT status() const noexcept { return mStatus; }
|
||||
explicit operator bool() const noexcept { return SUCCEEDED(status()); }
|
||||
|
||||
void uninit()
|
||||
{
|
||||
if(SUCCEEDED(mStatus))
|
||||
CoUninitialize();
|
||||
mStatus = E_FAIL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class ComPtr {
|
||||
T *mPtr{nullptr};
|
||||
struct ComPtr {
|
||||
using element_type = T;
|
||||
|
||||
static constexpr bool RefIsNoexcept{noexcept(std::declval<T&>().AddRef())
|
||||
&& noexcept(std::declval<T&>().Release())};
|
||||
|
||||
public:
|
||||
ComPtr() noexcept = default;
|
||||
ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
|
||||
ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr}
|
||||
{ if(mPtr) mPtr->AddRef(); }
|
||||
ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
|
||||
ComPtr(std::nullptr_t) noexcept { }
|
||||
explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~ComPtr() { if(mPtr) mPtr->Release(); }
|
||||
|
||||
ComPtr& operator=(const ComPtr &rhs)
|
||||
ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(!rhs.mPtr)
|
||||
if constexpr(RefIsNoexcept)
|
||||
{
|
||||
if(mPtr)
|
||||
mPtr->Release();
|
||||
mPtr = nullptr;
|
||||
if(rhs.mPtr) rhs.mPtr->AddRef();
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = rhs.mPtr;
|
||||
return *this;
|
||||
}
|
||||
else
|
||||
{
|
||||
rhs.mPtr->AddRef();
|
||||
try {
|
||||
if(mPtr)
|
||||
mPtr->Release();
|
||||
mPtr = rhs.mPtr;
|
||||
}
|
||||
catch(...) {
|
||||
rhs.mPtr->Release();
|
||||
throw;
|
||||
}
|
||||
ComPtr tmp{rhs};
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = tmp.release();
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ComPtr& operator=(ComPtr&& rhs)
|
||||
ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(likely(&rhs != this))
|
||||
if(&rhs != this)
|
||||
{
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = std::exchange(rhs.mPtr, nullptr);
|
||||
@@ -52,17 +87,25 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset(T *ptr=nullptr) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = ptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return mPtr != nullptr; }
|
||||
|
||||
T& operator*() const noexcept { return *mPtr; }
|
||||
T* operator->() const noexcept { return mPtr; }
|
||||
T* get() const noexcept { return mPtr; }
|
||||
T** getPtr() noexcept { return &mPtr; }
|
||||
|
||||
T* release() noexcept { return std::exchange(mPtr, nullptr); }
|
||||
|
||||
void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
|
||||
private:
|
||||
T *mPtr{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
#include "dynload.h"
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
void *LoadLib(const char *name)
|
||||
{
|
||||
std::wstring wname{utf8_to_wstr(name)};
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#ifndef AL_FLEXARRAY_H
|
||||
#define AL_FLEXARRAY_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
/* Storage for flexible array data. This is trivially destructible if type T is
|
||||
* trivially destructible.
|
||||
*/
|
||||
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
|
||||
struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage : al::span<T> {
|
||||
/* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the
|
||||
* sizeof(T) being suspicious when T is a pointer type, which it will be
|
||||
* for flexible arrays of pointers.
|
||||
*/
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
|
||||
/* NOLINTEND(bugprone-sizeof-expression) */
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible
|
||||
* arrays store their payloads after the end of the object, which must be
|
||||
* the last in the whole parent chain.
|
||||
*/
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
~FlexArrayStorage() = default;
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
template<typename T, size_t alignment>
|
||||
struct alignas(std::max(alignment, alignof(al::span<T>))) FlexArrayStorage<T,alignment,false> : al::span<T> {
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
~FlexArrayStorage() { std::destroy(this->begin(), this->end()); }
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
/* A flexible array type. Used either standalone or at the end of a parent
|
||||
* struct, to have a run-time-sized array that's embedded with its size. Should
|
||||
* be used delicately, ensuring there's no additional data after the FlexArray
|
||||
* member.
|
||||
*/
|
||||
template<typename T, size_t Align=alignof(T)>
|
||||
struct FlexArray {
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using Storage_t_ = FlexArrayStorage<element_type, std::max(alignof(T), Align)>;
|
||||
|
||||
using iterator = typename Storage_t_::iterator;
|
||||
using const_iterator = typename Storage_t_::const_iterator;
|
||||
using reverse_iterator = typename Storage_t_::reverse_iterator;
|
||||
using const_reverse_iterator = typename Storage_t_::const_reverse_iterator;
|
||||
|
||||
const Storage_t_ mStore;
|
||||
|
||||
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
|
||||
{ return Storage_t_::Sizeof(count, base); }
|
||||
static std::unique_ptr<FlexArray> Create(index_type count)
|
||||
{ return std::unique_ptr<FlexArray>{new(FamCount{count}) FlexArray{count}}; }
|
||||
|
||||
FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v<Storage_t_,index_type>)
|
||||
: mStore{size}
|
||||
{ }
|
||||
~FlexArray() = default;
|
||||
|
||||
[[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); }
|
||||
[[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); }
|
||||
|
||||
[[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); }
|
||||
[[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); }
|
||||
|
||||
[[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; }
|
||||
[[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; }
|
||||
|
||||
[[nodiscard]] auto front() noexcept -> reference { return mStore.front(); }
|
||||
[[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); }
|
||||
|
||||
[[nodiscard]] auto back() noexcept -> reference { return mStore.back(); }
|
||||
[[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); }
|
||||
|
||||
[[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); }
|
||||
[[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); }
|
||||
[[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); }
|
||||
[[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); }
|
||||
[[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); }
|
||||
[[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); }
|
||||
|
||||
[[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); }
|
||||
[[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
|
||||
[[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
|
||||
[[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); }
|
||||
[[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
|
||||
[[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
|
||||
|
||||
gsl::owner<void*> operator new(size_t, FamCount count)
|
||||
{ return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); }
|
||||
void operator delete(gsl::owner<void*> block, FamCount) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
|
||||
void operator delete(gsl::owner<void*> block) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
|
||||
|
||||
void *operator new(size_t size) = delete;
|
||||
void *operator new[](size_t size) = delete;
|
||||
void operator delete[](void *block) = delete;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_FLEXARRAY_H */
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef INTRUSIVE_PTR_H
|
||||
#define INTRUSIVE_PTR_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "atomic.h"
|
||||
@@ -11,14 +13,14 @@ namespace al {
|
||||
|
||||
template<typename T>
|
||||
class intrusive_ref {
|
||||
RefCount mRef{1u};
|
||||
std::atomic<unsigned int> mRef{1u};
|
||||
|
||||
public:
|
||||
unsigned int add_ref() noexcept { return IncrementRef(mRef); }
|
||||
unsigned int release() noexcept
|
||||
unsigned int dec_ref() noexcept
|
||||
{
|
||||
auto ref = DecrementRef(mRef);
|
||||
if UNLIKELY(ref == 0)
|
||||
if(ref == 0) UNLIKELY
|
||||
delete static_cast<T*>(this);
|
||||
return ref;
|
||||
}
|
||||
@@ -58,22 +60,26 @@ public:
|
||||
{ rhs.mPtr = nullptr; }
|
||||
intrusive_ptr(std::nullptr_t) noexcept { }
|
||||
explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~intrusive_ptr() { if(mPtr) mPtr->release(); }
|
||||
~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); }
|
||||
|
||||
/* NOLINTBEGIN(bugprone-unhandled-self-assignment)
|
||||
* Self-assignment is handled properly here.
|
||||
*/
|
||||
intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept
|
||||
{
|
||||
static_assert(noexcept(std::declval<T*>()->release()), "release must be noexcept");
|
||||
static_assert(noexcept(std::declval<T*>()->dec_ref()), "dec_ref must be noexcept");
|
||||
|
||||
if(rhs.mPtr) rhs.mPtr->add_ref();
|
||||
if(mPtr) mPtr->release();
|
||||
if(mPtr) mPtr->dec_ref();
|
||||
mPtr = rhs.mPtr;
|
||||
return *this;
|
||||
}
|
||||
/* NOLINTEND(bugprone-unhandled-self-assignment) */
|
||||
intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept
|
||||
{
|
||||
if(likely(&rhs != this))
|
||||
if(&rhs != this) LIKELY
|
||||
{
|
||||
if(mPtr) mPtr->release();
|
||||
if(mPtr) mPtr->dec_ref();
|
||||
mPtr = std::exchange(rhs.mPtr, nullptr);
|
||||
}
|
||||
return *this;
|
||||
@@ -81,14 +87,14 @@ public:
|
||||
|
||||
explicit operator bool() const noexcept { return mPtr != nullptr; }
|
||||
|
||||
T& operator*() const noexcept { return *mPtr; }
|
||||
T* operator->() const noexcept { return mPtr; }
|
||||
T* get() const noexcept { return mPtr; }
|
||||
[[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; }
|
||||
[[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; }
|
||||
[[nodiscard]] auto get() const noexcept -> T* { return mPtr; }
|
||||
|
||||
void reset(T *ptr=nullptr) noexcept
|
||||
{
|
||||
if(mPtr)
|
||||
mPtr->release();
|
||||
mPtr->dec_ref();
|
||||
mPtr = ptr;
|
||||
}
|
||||
|
||||
@@ -98,23 +104,6 @@ public:
|
||||
void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
};
|
||||
|
||||
#define AL_DECL_OP(op) \
|
||||
template<typename T> \
|
||||
inline bool operator op(const intrusive_ptr<T> &lhs, const T *rhs) noexcept \
|
||||
{ return lhs.get() op rhs; } \
|
||||
template<typename T> \
|
||||
inline bool operator op(const T *lhs, const intrusive_ptr<T> &rhs) noexcept \
|
||||
{ return lhs op rhs.get(); }
|
||||
|
||||
AL_DECL_OP(==)
|
||||
AL_DECL_OP(!=)
|
||||
AL_DECL_OP(<=)
|
||||
AL_DECL_OP(>=)
|
||||
AL_DECL_OP(<)
|
||||
AL_DECL_OP(>)
|
||||
|
||||
#undef AL_DECL_OP
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* INTRUSIVE_PTR_H */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#ifdef __has_builtin
|
||||
#define HAS_BUILTIN __has_builtin
|
||||
@@ -11,63 +11,72 @@
|
||||
#define HAS_BUILTIN(x) (0)
|
||||
#endif
|
||||
|
||||
#ifdef __has_cpp_attribute
|
||||
#define HAS_ATTRIBUTE __has_cpp_attribute
|
||||
#else
|
||||
#define HAS_ATTRIBUTE(x) (0)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define force_inline [[gnu::always_inline]]
|
||||
#define force_inline [[gnu::always_inline]] inline
|
||||
#define NOINLINE [[gnu::noinline]]
|
||||
#elif defined(_MSC_VER)
|
||||
#define force_inline __forceinline
|
||||
#define NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define force_inline inline
|
||||
#define NOINLINE
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || HAS_BUILTIN(__builtin_expect)
|
||||
/* likely() optimizes for the case where the condition is true. The condition
|
||||
* is not required to be true, but it can result in more optimal code for the
|
||||
* true path at the expense of a less optimal false path.
|
||||
/* Unlike the likely attribute, ASSUME requires the condition to be true or
|
||||
* else it invokes undefined behavior. It's essentially an assert without
|
||||
* actually checking the condition at run-time, allowing for stronger
|
||||
* optimizations than the likely attribute.
|
||||
*/
|
||||
template<typename T>
|
||||
force_inline constexpr bool likely(T&& expr) noexcept
|
||||
{ return __builtin_expect(static_cast<bool>(std::forward<T>(expr)), true); }
|
||||
/* The opposite of likely(), optimizing for the case where the condition is
|
||||
* false.
|
||||
*/
|
||||
template<typename T>
|
||||
force_inline constexpr bool unlikely(T&& expr) noexcept
|
||||
{ return __builtin_expect(static_cast<bool>(std::forward<T>(expr)), false); }
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
force_inline constexpr bool likely(T&& expr) noexcept
|
||||
{ return static_cast<bool>(std::forward<T>(expr)); }
|
||||
template<typename T>
|
||||
force_inline constexpr bool unlikely(T&& expr) noexcept
|
||||
{ return static_cast<bool>(std::forward<T>(expr)); }
|
||||
#endif
|
||||
#define LIKELY(x) (likely(x))
|
||||
#define UNLIKELY(x) (unlikely(x))
|
||||
|
||||
#if HAS_BUILTIN(__builtin_assume)
|
||||
/* Unlike LIKELY, ASSUME requires the condition to be true or else it invokes
|
||||
* undefined behavior. It's essentially an assert without actually checking the
|
||||
* condition at run-time, allowing for stronger optimizations than LIKELY.
|
||||
*/
|
||||
#define ASSUME __builtin_assume
|
||||
#elif defined(_MSC_VER)
|
||||
#define ASSUME __assume
|
||||
#elif defined(__GNUC__)
|
||||
#elif __has_attribute(assume)
|
||||
#define ASSUME(x) [[assume(x)]]
|
||||
#elif HAS_BUILTIN(__builtin_unreachable)
|
||||
#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0)
|
||||
#else
|
||||
#define ASSUME(x) ((void)0)
|
||||
#define ASSUME(x) (static_cast<void>(0))
|
||||
#endif
|
||||
|
||||
/* This shouldn't be needed since unknown attributes are ignored, but older
|
||||
* versions of GCC choke on the attribute syntax in certain situations.
|
||||
*/
|
||||
#if HAS_ATTRIBUTE(likely)
|
||||
#define LIKELY [[likely]]
|
||||
#define UNLIKELY [[unlikely]]
|
||||
#else
|
||||
#define LIKELY
|
||||
#define UNLIKELY
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
constexpr std::underlying_type_t<T> to_underlying(T e) noexcept
|
||||
{ return static_cast<std::underlying_type_t<T>>(e); }
|
||||
|
||||
[[noreturn]] inline void unreachable()
|
||||
{
|
||||
#if HAS_BUILTIN(__builtin_unreachable)
|
||||
__builtin_unreachable();
|
||||
#else
|
||||
ASSUME(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<std::size_t alignment, typename T>
|
||||
force_inline constexpr auto assume_aligned(T *ptr) noexcept
|
||||
{
|
||||
#ifdef __cpp_lib_assume_aligned
|
||||
return std::assume_aligned<alignment,T>(ptr);
|
||||
#elif defined(__clang__) || (defined(__GNUC__) && !defined(__ICC))
|
||||
#elif HAS_BUILTIN(__builtin_assume_aligned)
|
||||
return static_cast<T*>(__builtin_assume_aligned(ptr, alignment));
|
||||
#elif defined(_MSC_VER)
|
||||
constexpr std::size_t alignment_mask{(1<<alignment) - 1};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,212 @@
|
||||
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
|
||||
|
||||
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
|
||||
authored by Dr Paul Swarztrauber of NCAR, in 1985.
|
||||
|
||||
As confirmed by the NCAR fftpack software curators, the following
|
||||
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
|
||||
released under the same terms.
|
||||
|
||||
FFTPACK license:
|
||||
|
||||
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
|
||||
|
||||
Copyright (c) 2004 the University Corporation for Atmospheric
|
||||
Research ("UCAR"). All rights reserved. Developed by NCAR's
|
||||
Computational and Information Systems Laboratory, UCAR,
|
||||
www.cisl.ucar.edu.
|
||||
|
||||
Redistribution and use of the Software in source and binary forms,
|
||||
with or without modification, is permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
- Neither the names of NCAR's Computational and Information Systems
|
||||
Laboratory, the University Corporation for Atmospheric Research,
|
||||
nor the names of its sponsors or contributors may be used to
|
||||
endorse or promote products derived from this Software without
|
||||
specific prior written permission.
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notices, this list of conditions, and the disclaimer below.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions, and the disclaimer below in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/* PFFFT : a Pretty Fast FFT.
|
||||
*
|
||||
* This is basically an adaptation of the single precision fftpack (v4) as
|
||||
* found on netlib taking advantage of SIMD instructions found on CPUs such as
|
||||
* Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON).
|
||||
*
|
||||
* For architectures where SIMD instructions aren't available, the code falls
|
||||
* back to a scalar version.
|
||||
*
|
||||
* Restrictions:
|
||||
*
|
||||
* - 1D transforms only, with 32-bit single precision.
|
||||
*
|
||||
* - supports only transforms for inputs of length N of the form
|
||||
* N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144,
|
||||
* 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192.
|
||||
*
|
||||
* - all (float*) pointers for the functions below are expected to have a
|
||||
* "SIMD-compatible" alignment, that is 16 bytes.
|
||||
*
|
||||
* You can allocate such buffers with the pffft_aligned_malloc function, and
|
||||
* deallocate them with pffft_aligned_free (or with stuff like posix_memalign,
|
||||
* aligned_alloc, etc).
|
||||
*
|
||||
* Note that for the z-domain data of real transforms, when in the canonical
|
||||
* order (as interleaved complex numbers) both 0-frequency and half-frequency
|
||||
* components, which are real, are assembled in the first entry as
|
||||
* F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the
|
||||
* arrays instead.
|
||||
*/
|
||||
|
||||
#ifndef PFFFT_H
|
||||
#define PFFFT_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
/* opaque struct holding internal stuff (precomputed twiddle factors) this
|
||||
* struct can be shared by many threads as it contains only read-only data.
|
||||
*/
|
||||
struct PFFFT_Setup;
|
||||
|
||||
/* direction of the transform */
|
||||
enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD };
|
||||
|
||||
/* type of transform */
|
||||
enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX };
|
||||
|
||||
void pffft_destroy_setup(gsl::owner<PFFFT_Setup*> setup) noexcept;
|
||||
struct PFFFTSetupDeleter {
|
||||
void operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept { pffft_destroy_setup(setup); }
|
||||
};
|
||||
using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;
|
||||
|
||||
/**
|
||||
* Prepare for performing transforms of size N -- the returned PFFFT_Setup
|
||||
* structure is read-only so it can safely be shared by multiple concurrent
|
||||
* threads.
|
||||
*/
|
||||
PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform);
|
||||
|
||||
/**
|
||||
* Perform a Fourier transform. The z-domain data is stored in the most
|
||||
* efficient order for transforming back or using for convolution, and as
|
||||
* such, there's no guarantee to the order of the values. If you need to have
|
||||
* its content sorted in the usual way, that is as an array of interleaved
|
||||
* complex numbers, either use pffft_transform_ordered, or call pffft_zreorder
|
||||
* after the forward fft and before the backward fft.
|
||||
*
|
||||
* Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically
|
||||
* you will want to scale the backward transform by 1/N.
|
||||
*
|
||||
* The 'work' pointer must point to an area of N (2*N for complex fft) floats,
|
||||
* properly aligned. It cannot be NULL.
|
||||
*
|
||||
* The input and output parameters may alias.
|
||||
*/
|
||||
void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Similar to pffft_transform, but handles the complex values in the usual form
|
||||
* (interleaved complex numbers). This is similar to calling
|
||||
* pffft_transform(..., PFFFT_FORWARD) followed by
|
||||
* pffft_zreorder(..., PFFFT_FORWARD), or
|
||||
* pffft_zreorder(..., PFFFT_BACKWARD) followed by
|
||||
* pffft_transform(..., PFFFT_BACKWARD), for the given direction.
|
||||
*
|
||||
* The input and output parameters may alias.
|
||||
*/
|
||||
void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal
|
||||
* representation to the "canonical" order (as interleaved complex numbers).
|
||||
* For PFFFT_BACKWARD, it reorders from the canonical order to the internal
|
||||
* order suitable for pffft_transform(..., PFFFT_BACKWARD) or
|
||||
* pffft_zconvolve_accumulate.
|
||||
*
|
||||
* The input and output parameters should not alias.
|
||||
*/
|
||||
void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Perform a multiplication of the z-domain data in dft_a and dft_b, and scale
|
||||
* and accumulate into dft_ab. The arrays should have been obtained with
|
||||
* pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD)
|
||||
* and should *not* be in the usual order (otherwise just perform the operation
|
||||
* yourself as the dft coeffs are stored as interleaved complex numbers).
|
||||
*
|
||||
* The operation performed is: dft_ab += (dft_a * dft_b)*scaling
|
||||
*
|
||||
* The dft_a, dft_b, and dft_ab parameters may alias.
|
||||
*/
|
||||
void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
|
||||
|
||||
/**
|
||||
* Perform a multiplication of the z-domain data in dft_a and dft_b, and
|
||||
* accumulate into dft_ab.
|
||||
*
|
||||
* The operation performed is: dft_ab += dft_a * dft_b
|
||||
*
|
||||
* The dft_a, dft_b, and dft_ab parameters may alias.
|
||||
*/
|
||||
void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab);
|
||||
|
||||
|
||||
struct PFFFTSetup {
|
||||
PFFFTSetupPtr mSetup{};
|
||||
|
||||
PFFFTSetup() = default;
|
||||
PFFFTSetup(const PFFFTSetup&) = delete;
|
||||
PFFFTSetup(PFFFTSetup&& rhs) noexcept = default;
|
||||
explicit PFFFTSetup(std::nullptr_t) noexcept { }
|
||||
explicit PFFFTSetup(unsigned int n, pffft_transform_t transform)
|
||||
: mSetup{pffft_new_setup(n, transform)}
|
||||
{ }
|
||||
~PFFFTSetup() = default;
|
||||
|
||||
PFFFTSetup& operator=(const PFFFTSetup&) = delete;
|
||||
PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default;
|
||||
|
||||
void transform(const float *input, float *output, float *work, pffft_direction_t direction) const
|
||||
{ pffft_transform(mSetup.get(), input, output, work, direction); }
|
||||
|
||||
void transform_ordered(const float *input, float *output, float *work,
|
||||
pffft_direction_t direction) const
|
||||
{ pffft_transform_ordered(mSetup.get(), input, output, work, direction); }
|
||||
|
||||
void zreorder(const float *input, float *output, pffft_direction_t direction) const
|
||||
{ pffft_zreorder(mSetup.get(), input, output, direction); }
|
||||
|
||||
void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab,
|
||||
float scaling) const
|
||||
{ pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); }
|
||||
|
||||
void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const
|
||||
{ pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); }
|
||||
|
||||
[[nodiscard]] operator bool() const noexcept { return mSetup != nullptr; }
|
||||
};
|
||||
|
||||
#endif // PFFFT_H
|
||||
@@ -8,17 +8,22 @@
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <stddef.h>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
struct NoInit { };
|
||||
|
||||
/* Implements a wide-band +90 degree phase-shift. Note that this should be
|
||||
* given one sample less of a delay (FilterSize/2 - 1) compared to the direct
|
||||
* signal delay (FilterSize/2) to properly align.
|
||||
*/
|
||||
template<size_t FilterSize>
|
||||
template<std::size_t FilterSize>
|
||||
struct PhaseShifterT {
|
||||
static_assert(FilterSize >= 16, "FilterSize needs to be at least 16");
|
||||
static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two");
|
||||
@@ -46,21 +51,22 @@ struct PhaseShifterT {
|
||||
PhaseShifterT()
|
||||
{
|
||||
using complex_d = std::complex<double>;
|
||||
constexpr size_t fft_size{FilterSize};
|
||||
constexpr size_t half_size{fft_size / 2};
|
||||
constexpr std::size_t fft_size{FilterSize};
|
||||
constexpr std::size_t half_size{fft_size / 2};
|
||||
|
||||
auto fftBuffer = std::make_unique<complex_d[]>(fft_size);
|
||||
std::fill_n(fftBuffer.get(), fft_size, complex_d{});
|
||||
auto fftBuffer = std::vector<complex_d>(fft_size, complex_d{});
|
||||
fftBuffer[half_size] = 1.0;
|
||||
|
||||
forward_fft({fftBuffer.get(), fft_size});
|
||||
for(size_t i{0};i < half_size+1;++i)
|
||||
forward_fft(al::span{fftBuffer});
|
||||
fftBuffer[0] *= std::numeric_limits<double>::epsilon();
|
||||
for(std::size_t i{1};i < half_size;++i)
|
||||
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
|
||||
for(size_t i{half_size+1};i < fft_size;++i)
|
||||
fftBuffer[half_size] *= std::numeric_limits<double>::epsilon();
|
||||
for(std::size_t i{half_size+1};i < fft_size;++i)
|
||||
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
|
||||
inverse_fft({fftBuffer.get(), fft_size});
|
||||
inverse_fft(al::span{fftBuffer});
|
||||
|
||||
auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1);
|
||||
auto fftiter = fftBuffer.data() + fft_size - 1;
|
||||
for(float &coeff : mCoeffs)
|
||||
{
|
||||
coeff = static_cast<float>(fftiter->real() / double{fft_size});
|
||||
@@ -68,30 +74,12 @@ struct PhaseShifterT {
|
||||
}
|
||||
}
|
||||
|
||||
PhaseShifterT(NoInit) { }
|
||||
|
||||
void process(al::span<float> dst, const float *RESTRICT src) const;
|
||||
void processAccum(al::span<float> dst, const float *RESTRICT src) const;
|
||||
|
||||
private:
|
||||
#if defined(HAVE_NEON)
|
||||
/* There doesn't seem to be NEON intrinsics to do this kind of stipple
|
||||
* shuffling, so there's two custom methods for it.
|
||||
*/
|
||||
static auto shuffle_2020(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static auto shuffle_3131(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static auto unpacklo(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))};
|
||||
@@ -113,17 +101,17 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
template<size_t S>
|
||||
template<std::size_t S>
|
||||
inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT src) const
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
if(size_t todo{dst.size()>>1})
|
||||
if(std::size_t todo{dst.size()>>1})
|
||||
{
|
||||
auto *out = reinterpret_cast<__m64*>(dst.data());
|
||||
do {
|
||||
__m128 r04{_mm_setzero_ps()};
|
||||
__m128 r14{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s0{_mm_loadu_ps(&src[j*2])};
|
||||
@@ -147,7 +135,7 @@ inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT
|
||||
if((dst.size()&1))
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
@@ -161,20 +149,21 @@ inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
size_t pos{0};
|
||||
if(size_t todo{dst.size()>>1})
|
||||
std::size_t pos{0};
|
||||
if(std::size_t todo{dst.size()>>1})
|
||||
{
|
||||
do {
|
||||
float32x4_t r04{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r14{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s0{vld1q_f32(&src[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
|
||||
const float32x4x2_t values{vuzpq_f32(s0, s1)};
|
||||
|
||||
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
|
||||
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
|
||||
r04 = vmlaq_f32(r04, values.val[0], coeffs);
|
||||
r14 = vmlaq_f32(r14, values.val[1], coeffs);
|
||||
}
|
||||
src += 2;
|
||||
|
||||
@@ -188,7 +177,7 @@ inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT
|
||||
if((dst.size()&1))
|
||||
{
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
@@ -203,7 +192,7 @@ inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT
|
||||
for(float &output : dst)
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < mCoeffs.size();++j)
|
||||
for(std::size_t j{0};j < mCoeffs.size();++j)
|
||||
ret += src[j*2] * mCoeffs[j];
|
||||
|
||||
output = ret;
|
||||
@@ -212,103 +201,4 @@ inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT
|
||||
#endif
|
||||
}
|
||||
|
||||
template<size_t S>
|
||||
inline void PhaseShifterT<S>::processAccum(al::span<float> dst, const float *RESTRICT src) const
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
if(size_t todo{dst.size()>>1})
|
||||
{
|
||||
auto *out = reinterpret_cast<__m64*>(dst.data());
|
||||
do {
|
||||
__m128 r04{_mm_setzero_ps()};
|
||||
__m128 r14{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s0{_mm_loadu_ps(&src[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])};
|
||||
|
||||
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
|
||||
r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
src += 2;
|
||||
|
||||
__m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))};
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
|
||||
_mm_storel_pi(out, _mm_add_ps(_mm_loadl_pi(_mm_undefined_ps(), out), r4));
|
||||
++out;
|
||||
} while(--todo);
|
||||
}
|
||||
if((dst.size()&1))
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
|
||||
dst.back() += _mm_cvtss_f32(r4);
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
size_t pos{0};
|
||||
if(size_t todo{dst.size()>>1})
|
||||
{
|
||||
do {
|
||||
float32x4_t r04{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r14{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s0{vld1q_f32(&src[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
|
||||
|
||||
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
|
||||
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
|
||||
}
|
||||
src += 2;
|
||||
|
||||
float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))};
|
||||
float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))};
|
||||
|
||||
vst1_f32(&dst[pos], vadd_f32(vld1_f32(&dst[pos]), r2));
|
||||
pos += 2;
|
||||
} while(--todo);
|
||||
}
|
||||
if((dst.size()&1))
|
||||
{
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
dst[pos] += vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < mCoeffs.size();++j)
|
||||
ret += src[j*2] * mCoeffs[j];
|
||||
|
||||
output += ret;
|
||||
++src;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* PHASE_SHIFTER_H */
|
||||
|
||||
@@ -3,45 +3,50 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double Epsilon{1e-9};
|
||||
|
||||
using uint = unsigned int;
|
||||
#if __cpp_lib_math_special_functions >= 201603L
|
||||
using std::cyl_bessel_i;
|
||||
|
||||
/* This is the normalized cardinal sine (sinc) function.
|
||||
*
|
||||
* sinc(x) = { 1, x = 0
|
||||
* { sin(pi x) / (pi x), otherwise.
|
||||
*/
|
||||
double Sinc(const double x)
|
||||
{
|
||||
if(unlikely(std::abs(x) < Epsilon))
|
||||
return 1.0;
|
||||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
#else
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
* Kaiser window.
|
||||
*
|
||||
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
|
||||
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
|
||||
*
|
||||
* This implementation only handles nu = 0, and isn't the most precise (it
|
||||
* starts with the largest value and accumulates successively smaller values,
|
||||
* compounding the rounding and precision error), but it's good enough.
|
||||
*/
|
||||
constexpr double BesselI_0(const double x)
|
||||
template<typename T, typename U>
|
||||
U cyl_bessel_i(T nu, U x)
|
||||
{
|
||||
// Start at k=1 since k=0 is trivial.
|
||||
if(nu != T{0})
|
||||
throw std::runtime_error{"cyl_bessel_i: nu != 0"};
|
||||
|
||||
/* Start at k=1 since k=0 is trivial. */
|
||||
const double x2{x/2.0};
|
||||
double term{1.0};
|
||||
double sum{1.0};
|
||||
int k{1};
|
||||
|
||||
// Let the integration converge until the term of the sum is no longer
|
||||
// significant.
|
||||
/* Let the integration converge until the term of the sum is no longer
|
||||
* significant.
|
||||
*/
|
||||
double last_sum{};
|
||||
do {
|
||||
const double y{x2 / k};
|
||||
@@ -50,7 +55,20 @@ constexpr double BesselI_0(const double x)
|
||||
term *= y * y;
|
||||
sum += term;
|
||||
} while(sum != last_sum);
|
||||
return sum;
|
||||
return static_cast<U>(sum);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This is the normalized cardinal sine (sinc) function.
|
||||
*
|
||||
* sinc(x) = { 1, x = 0
|
||||
* { sin(pi x) / (pi x), otherwise.
|
||||
*/
|
||||
double Sinc(const double x)
|
||||
{
|
||||
if(std::abs(x) < Epsilon) UNLIKELY
|
||||
return 1.0;
|
||||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
|
||||
/* Calculate a Kaiser window from the given beta value and a normalized k
|
||||
@@ -67,23 +85,11 @@ constexpr double BesselI_0(const double x)
|
||||
*
|
||||
* k = 2 i / M - 1, where 0 <= i <= M.
|
||||
*/
|
||||
double Kaiser(const double b, const double k)
|
||||
double Kaiser(const double beta, const double k, const double besseli_0_beta)
|
||||
{
|
||||
if(!(k >= -1.0 && k <= 1.0))
|
||||
return 0.0;
|
||||
return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b);
|
||||
}
|
||||
|
||||
// Calculates the greatest common divisor of a and b.
|
||||
constexpr uint Gcd(uint x, uint y)
|
||||
{
|
||||
while(y > 0)
|
||||
{
|
||||
const uint z{y};
|
||||
y = x % y;
|
||||
x = z;
|
||||
}
|
||||
return x;
|
||||
return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
|
||||
}
|
||||
|
||||
/* Calculates the size (order) of the Kaiser window. Rejection is in dB and
|
||||
@@ -96,7 +102,7 @@ constexpr uint Gcd(uint x, uint y)
|
||||
constexpr uint CalcKaiserOrder(const double rejection, const double transition)
|
||||
{
|
||||
const double w_t{2.0 * al::numbers::pi * transition};
|
||||
if LIKELY(rejection > 21.0)
|
||||
if(rejection > 21.0) LIKELY
|
||||
return static_cast<uint>(std::ceil((rejection - 7.95) / (2.285 * w_t)));
|
||||
return static_cast<uint>(std::ceil(5.79 / w_t));
|
||||
}
|
||||
@@ -104,7 +110,7 @@ constexpr uint CalcKaiserOrder(const double rejection, const double transition)
|
||||
// Calculates the beta value of the Kaiser window. Rejection is in dB.
|
||||
constexpr double CalcKaiserBeta(const double rejection)
|
||||
{
|
||||
if LIKELY(rejection > 50.0)
|
||||
if(rejection > 50.0) LIKELY
|
||||
return 0.1102 * (rejection - 8.7);
|
||||
if(rejection >= 21.0)
|
||||
return (0.5842 * std::pow(rejection - 21.0, 0.4)) +
|
||||
@@ -124,11 +130,11 @@ constexpr double CalcKaiserBeta(const double rejection)
|
||||
* p -- gain compensation factor when sampling
|
||||
* f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
|
||||
*/
|
||||
double SincFilter(const uint l, const double b, const double gain, const double cutoff,
|
||||
const uint i)
|
||||
double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
|
||||
const double cutoff, const uint i)
|
||||
{
|
||||
const double x{static_cast<double>(i) - l};
|
||||
return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
|
||||
return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -137,7 +143,7 @@ double SincFilter(const uint l, const double b, const double gain, const double
|
||||
// that's used to cut frequencies above the destination nyquist.
|
||||
void PPhaseResampler::init(const uint srcRate, const uint dstRate)
|
||||
{
|
||||
const uint gcd{Gcd(srcRate, dstRate)};
|
||||
const uint gcd{std::gcd(srcRate, dstRate)};
|
||||
mP = dstRate / gcd;
|
||||
mQ = srcRate / gcd;
|
||||
|
||||
@@ -145,78 +151,70 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate)
|
||||
* ends before the nyquist (0.5). Both are scaled by the downsampling
|
||||
* factor.
|
||||
*/
|
||||
double cutoff, width;
|
||||
if(mP > mQ)
|
||||
{
|
||||
cutoff = 0.475 / mP;
|
||||
width = 0.05 / mP;
|
||||
}
|
||||
else
|
||||
{
|
||||
cutoff = 0.475 / mQ;
|
||||
width = 0.05 / mQ;
|
||||
}
|
||||
const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP)
|
||||
: std::make_tuple(0.475 / mQ, 0.05 / mQ);
|
||||
|
||||
// A rejection of -180 dB is used for the stop band. Round up when
|
||||
// calculating the left offset to avoid increasing the transition width.
|
||||
const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
|
||||
const double beta{CalcKaiserBeta(180.0)};
|
||||
const double besseli_0_beta{cyl_bessel_i(0, beta)};
|
||||
mM = l*2 + 1;
|
||||
mL = l;
|
||||
mF.resize(mM);
|
||||
for(uint i{0};i < mM;i++)
|
||||
mF[i] = SincFilter(l, beta, mP, cutoff, i);
|
||||
mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i);
|
||||
}
|
||||
|
||||
// Perform the upsample-filter-downsample resampling operation using a
|
||||
// polyphase filter implementation.
|
||||
void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out)
|
||||
void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out)
|
||||
{
|
||||
if UNLIKELY(outN == 0)
|
||||
if(out.empty()) UNLIKELY
|
||||
return;
|
||||
|
||||
// Handle in-place operation.
|
||||
std::vector<double> workspace;
|
||||
double *work{out};
|
||||
if UNLIKELY(work == in)
|
||||
al::span work{out};
|
||||
if(work.data() == in.data()) UNLIKELY
|
||||
{
|
||||
workspace.resize(outN);
|
||||
work = workspace.data();
|
||||
workspace.resize(out.size());
|
||||
work = workspace;
|
||||
}
|
||||
|
||||
// Resample the input.
|
||||
const uint p{mP}, q{mQ}, m{mM}, l{mL};
|
||||
const double *f{mF.data()};
|
||||
for(uint i{0};i < outN;i++)
|
||||
const al::span<const double> f{mF};
|
||||
for(uint i{0};i < out.size();i++)
|
||||
{
|
||||
// Input starts at l to compensate for the filter delay. This will
|
||||
// drop any build-up from the first half of the filter.
|
||||
size_t j_f{(l + q*i) % p};
|
||||
size_t j_s{(l + q*i) / p};
|
||||
std::size_t j_f{(l + q*i) % p};
|
||||
std::size_t j_s{(l + q*i) / p};
|
||||
|
||||
// Only take input when 0 <= j_s < inN.
|
||||
// Only take input when 0 <= j_s < in.size().
|
||||
double r{0.0};
|
||||
if LIKELY(j_f < m)
|
||||
if(j_f < m) LIKELY
|
||||
{
|
||||
size_t filt_len{(m-j_f+p-1) / p};
|
||||
if LIKELY(j_s+1 > inN)
|
||||
std::size_t filt_len{(m-j_f+p-1) / p};
|
||||
if(j_s+1 > in.size()) LIKELY
|
||||
{
|
||||
size_t skip{std::min<size_t>(j_s+1 - inN, filt_len)};
|
||||
std::size_t skip{std::min(j_s+1 - in.size(), filt_len)};
|
||||
j_f += p*skip;
|
||||
j_s -= skip;
|
||||
filt_len -= skip;
|
||||
}
|
||||
if(size_t todo{std::min<size_t>(j_s+1, filt_len)})
|
||||
std::size_t todo{std::min(j_s+1, filt_len)};
|
||||
while(todo)
|
||||
{
|
||||
do {
|
||||
r += f[j_f] * in[j_s];
|
||||
j_f += p;
|
||||
--j_s;
|
||||
} while(--todo);
|
||||
r += f[j_f] * in[j_s];
|
||||
j_f += p; --j_s;
|
||||
--todo;
|
||||
}
|
||||
}
|
||||
work[i] = r;
|
||||
}
|
||||
// Clean up after in-place operation.
|
||||
if(work != out)
|
||||
std::copy_n(work, outN, out);
|
||||
if(work.data() != out.data())
|
||||
std::copy(work.cbegin(), work.cend(), out.begin());
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* This is a polyphase sinc-filtered resampler. It is built for very high
|
||||
* quality results, rather than real-time performance.
|
||||
@@ -32,13 +36,13 @@
|
||||
*/
|
||||
|
||||
struct PPhaseResampler {
|
||||
using uint = unsigned int;
|
||||
|
||||
void init(const uint srcRate, const uint dstRate);
|
||||
void process(const uint inN, const double *in, const uint outN, double *out);
|
||||
void process(const al::span<const double> in, const al::span<double> out);
|
||||
|
||||
explicit operator bool() const noexcept { return !mF.empty(); }
|
||||
|
||||
private:
|
||||
uint mP, mQ, mM, mL;
|
||||
uint mP{}, mQ{}, mM{}, mL{};
|
||||
std::vector<double> mF;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,202 +23,150 @@
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
|
||||
|
||||
RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes)
|
||||
auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr
|
||||
{
|
||||
size_t power_of_two{0u};
|
||||
std::size_t power_of_two{0u};
|
||||
if(sz > 0)
|
||||
{
|
||||
power_of_two = sz;
|
||||
power_of_two = sz - 1;
|
||||
power_of_two |= power_of_two>>1;
|
||||
power_of_two |= power_of_two>>2;
|
||||
power_of_two |= power_of_two>>4;
|
||||
power_of_two |= power_of_two>>8;
|
||||
power_of_two |= power_of_two>>16;
|
||||
#if SIZE_MAX > UINT_MAX
|
||||
power_of_two |= power_of_two>>32;
|
||||
#endif
|
||||
if constexpr(sizeof(size_t) > sizeof(uint32_t))
|
||||
power_of_two |= power_of_two>>32;
|
||||
}
|
||||
++power_of_two;
|
||||
if(power_of_two <= sz || power_of_two > std::numeric_limits<size_t>::max()/elem_sz)
|
||||
if(power_of_two < sz || power_of_two > std::numeric_limits<std::size_t>::max()>>1
|
||||
|| power_of_two > std::numeric_limits<std::size_t>::max()/elem_sz)
|
||||
throw std::overflow_error{"Ring buffer size overflow"};
|
||||
|
||||
const size_t bufbytes{power_of_two * elem_sz};
|
||||
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{bufbytes}};
|
||||
rb->mWriteSize = limit_writes ? sz : (power_of_two-1);
|
||||
rb->mSizeMask = power_of_two - 1;
|
||||
rb->mElemSize = elem_sz;
|
||||
const std::size_t bufbytes{power_of_two * elem_sz};
|
||||
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two,
|
||||
power_of_two-1, elem_sz, bufbytes}};
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
void RingBuffer::reset() noexcept
|
||||
{
|
||||
mWritePtr.store(0, std::memory_order_relaxed);
|
||||
mReadPtr.store(0, std::memory_order_relaxed);
|
||||
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{});
|
||||
mWriteCount.store(0, std::memory_order_relaxed);
|
||||
mReadCount.store(0, std::memory_order_relaxed);
|
||||
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{});
|
||||
}
|
||||
|
||||
|
||||
size_t RingBuffer::read(void *dest, size_t cnt) noexcept
|
||||
auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{readSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
if(readable == 0) return 0;
|
||||
|
||||
const size_t to_read{std::min(cnt, free_cnt)};
|
||||
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_read{std::min(count, readable)};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{read_ptr + to_read};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - read_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
|
||||
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
|
||||
static_cast<al::byte*>(dest));
|
||||
read_ptr += n1;
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
dstbytes.begin());
|
||||
if(n2 > 0)
|
||||
{
|
||||
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
|
||||
read_ptr += n2;
|
||||
}
|
||||
mReadPtr.store(read_ptr, std::memory_order_release);
|
||||
mReadCount.store(r+n1+n2, std::memory_order_release);
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept
|
||||
auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{readSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
if(readable == 0) return 0;
|
||||
|
||||
const size_t to_read{std::min(cnt, free_cnt)};
|
||||
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_read{std::min(count, readable)};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{read_ptr + to_read};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - read_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
|
||||
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
|
||||
static_cast<al::byte*>(dest));
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
dstbytes.begin());
|
||||
if(n2 > 0)
|
||||
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t RingBuffer::write(const void *src, size_t cnt) noexcept
|
||||
auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{writeSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
const std::size_t writable{mWriteSize - (w - r)};
|
||||
if(writable == 0) return 0;
|
||||
|
||||
const size_t to_write{std::min(cnt, free_cnt)};
|
||||
size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_write{std::min(count, writable)};
|
||||
const std::size_t write_idx{w & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{write_ptr + to_write};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - write_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_write;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t wrend{write_idx + to_write};
|
||||
const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask);
|
||||
|
||||
auto srcbytes = static_cast<const al::byte*>(src);
|
||||
std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize);
|
||||
write_ptr += n1;
|
||||
auto srcbytes = al::span{static_cast<const std::byte*>(src), count*mElemSize};
|
||||
std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize));
|
||||
if(n2 > 0)
|
||||
{
|
||||
std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin());
|
||||
write_ptr += n2;
|
||||
}
|
||||
mWritePtr.store(write_ptr, std::memory_order_release);
|
||||
std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin());
|
||||
mWriteCount.store(w+n1+n2, std::memory_order_release);
|
||||
return to_write;
|
||||
}
|
||||
|
||||
|
||||
auto RingBuffer::getReadVector() const noexcept -> DataPair
|
||||
auto RingBuffer::getReadVector() noexcept -> DataPair
|
||||
{
|
||||
DataPair ret;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
size_t r{mReadPtr.load(std::memory_order_acquire)};
|
||||
w &= mSizeMask;
|
||||
r &= mSizeMask;
|
||||
const size_t free_cnt{(w-r) & mSizeMask};
|
||||
|
||||
const size_t cnt2{r + free_cnt};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
const std::size_t rdend{read_idx + readable};
|
||||
if(rdend > mSizeMask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current read ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
|
||||
ret.first.len = mSizeMask+1 - r;
|
||||
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
|
||||
ret.second.len = cnt2 & mSizeMask;
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx},
|
||||
{mBuffer.data(), rdend&mSizeMask}};
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Single part vector: just the rest of the buffer */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
|
||||
ret.first.len = free_cnt;
|
||||
ret.second.buf = nullptr;
|
||||
ret.second.len = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}};
|
||||
}
|
||||
|
||||
auto RingBuffer::getWriteVector() const noexcept -> DataPair
|
||||
auto RingBuffer::getWriteVector() noexcept -> DataPair
|
||||
{
|
||||
DataPair ret;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
const std::size_t writable{mWriteSize - (w - r)};
|
||||
const std::size_t write_idx{w & mSizeMask};
|
||||
|
||||
size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
|
||||
w &= mSizeMask;
|
||||
r &= mSizeMask;
|
||||
const size_t free_cnt{(r-w-1) & mSizeMask};
|
||||
|
||||
const size_t cnt2{w + free_cnt};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
const std::size_t wrend{write_idx + writable};
|
||||
if(wrend > mSizeMask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current write ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
|
||||
ret.first.len = mSizeMask+1 - w;
|
||||
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
|
||||
ret.second.len = cnt2 & mSizeMask;
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx},
|
||||
{mBuffer.data(), wrend&mSizeMask}};
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
|
||||
ret.first.len = free_cnt;
|
||||
ret.second.buf = nullptr;
|
||||
ret.second.len = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}};
|
||||
}
|
||||
|
||||
@@ -2,111 +2,133 @@
|
||||
#define RINGBUFFER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "flexarray.h"
|
||||
|
||||
|
||||
/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
|
||||
* to include an element size. Consequently, parameters and return values for a
|
||||
* size or count is in 'elements', not bytes. Additionally, it only supports
|
||||
* size or count are in 'elements', not bytes. Additionally, it only supports
|
||||
* single-consumer/single-provider operation.
|
||||
*/
|
||||
|
||||
struct RingBuffer {
|
||||
private:
|
||||
std::atomic<size_t> mWritePtr{0u};
|
||||
std::atomic<size_t> mReadPtr{0u};
|
||||
size_t mWriteSize{0u};
|
||||
size_t mSizeMask{0u};
|
||||
size_t mElemSize{0u};
|
||||
#if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION)
|
||||
static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size};
|
||||
#else
|
||||
/* Assume a 64-byte cache line, the most common/likely value. */
|
||||
static constexpr std::size_t sCacheAlignment{64};
|
||||
#endif
|
||||
alignas(sCacheAlignment) std::atomic<std::size_t> mWriteCount{0u};
|
||||
alignas(sCacheAlignment) std::atomic<std::size_t> mReadCount{0u};
|
||||
|
||||
al::FlexArray<al::byte, 16> mBuffer;
|
||||
alignas(sCacheAlignment) const std::size_t mWriteSize;
|
||||
const std::size_t mSizeMask;
|
||||
const std::size_t mElemSize;
|
||||
|
||||
al::FlexArray<std::byte, 16> mBuffer;
|
||||
|
||||
public:
|
||||
struct Data {
|
||||
al::byte *buf;
|
||||
size_t len;
|
||||
std::byte *buf;
|
||||
std::size_t len;
|
||||
};
|
||||
using DataPair = std::pair<Data,Data>;
|
||||
|
||||
|
||||
RingBuffer(const size_t count) : mBuffer{count} { }
|
||||
RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize,
|
||||
const std::size_t numbytes)
|
||||
: mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes}
|
||||
{ }
|
||||
|
||||
/** Reset the read and write pointers to zero. This is not thread safe. */
|
||||
void reset() noexcept;
|
||||
auto reset() noexcept -> void;
|
||||
|
||||
/**
|
||||
* Return the number of elements available for reading. This is the number
|
||||
* of elements in front of the read pointer and behind the write pointer.
|
||||
*/
|
||||
[[nodiscard]] auto readSpace() const noexcept -> std::size_t
|
||||
{
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
/* mWriteCount is never more than mWriteSize greater than mReadCount. */
|
||||
return w - r;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data reader. Copy at most `count' elements into `dest'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t;
|
||||
/**
|
||||
* The copying data reader w/o read pointer advance. Copy at most `count'
|
||||
* elements into `dest'. Returns the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t;
|
||||
|
||||
/**
|
||||
* The non-copying data reader. Returns two ringbuffer data pointers that
|
||||
* hold the current readable data. If the readable data is in one segment
|
||||
* the second segment has zero length.
|
||||
*/
|
||||
DataPair getReadVector() const noexcept;
|
||||
[[nodiscard]] auto getReadVector() noexcept -> DataPair;
|
||||
/** Advance the read pointer `count' places. */
|
||||
auto readAdvance(std::size_t count) noexcept -> void
|
||||
{
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
[[maybe_unused]] const std::size_t readable{w - r};
|
||||
assert(readable >= count);
|
||||
mReadCount.store(r+count, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of elements available for writing. This is the total
|
||||
* number of writable elements excluding what's readable (already written).
|
||||
*/
|
||||
[[nodiscard]] auto writeSpace() const noexcept -> std::size_t
|
||||
{ return mWriteSize - readSpace(); }
|
||||
|
||||
/**
|
||||
* The copying data writer. Copy at most `count' elements from `src'. Returns
|
||||
* the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t;
|
||||
|
||||
/**
|
||||
* The non-copying data writer. Returns two ringbuffer data pointers that
|
||||
* hold the current writeable data. If the writeable data is in one segment
|
||||
* the second segment has zero length.
|
||||
*/
|
||||
DataPair getWriteVector() const noexcept;
|
||||
|
||||
/**
|
||||
* Return the number of elements available for reading. This is the number
|
||||
* of elements in front of the read pointer and behind the write pointer.
|
||||
*/
|
||||
size_t readSpace() const noexcept
|
||||
[[nodiscard]] auto getWriteVector() noexcept -> DataPair;
|
||||
/** Advance the write pointer `count' places. */
|
||||
auto writeAdvance(std::size_t count) noexcept -> void
|
||||
{
|
||||
const size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
const size_t r{mReadPtr.load(std::memory_order_acquire)};
|
||||
return (w-r) & mSizeMask;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
[[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)};
|
||||
assert(writable >= count);
|
||||
mWriteCount.store(w+count, std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data reader. Copy at most `cnt' elements into `dest'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t read(void *dest, size_t cnt) noexcept;
|
||||
/**
|
||||
* The copying data reader w/o read pointer advance. Copy at most `cnt'
|
||||
* elements into `dest'. Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t peek(void *dest, size_t cnt) const noexcept;
|
||||
/** Advance the read pointer `cnt' places. */
|
||||
void readAdvance(size_t cnt) noexcept
|
||||
{ mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); }
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of elements available for writing. This is the number
|
||||
* of elements in front of the write pointer and behind the read pointer.
|
||||
*/
|
||||
size_t writeSpace() const noexcept
|
||||
{
|
||||
const size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
|
||||
return (r-w-1) & mSizeMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data writer. Copy at most `cnt' elements from `src'. Returns
|
||||
* the actual number of elements copied.
|
||||
*/
|
||||
size_t write(const void *src, size_t cnt) noexcept;
|
||||
/** Advance the write pointer `cnt' places. */
|
||||
void writeAdvance(size_t cnt) noexcept
|
||||
{ mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); }
|
||||
|
||||
size_t getElemSize() const noexcept { return mElemSize; }
|
||||
[[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; }
|
||||
|
||||
/**
|
||||
* Create a new ringbuffer to hold at least `sz' elements of `elem_sz'
|
||||
* bytes. The number of elements is rounded up to the next power of two
|
||||
* (even if it is already a power of two, to ensure the requested amount
|
||||
* can be written).
|
||||
* bytes. The number of elements is rounded up to a power of two. If
|
||||
* `limit_writes' is true, the writable space will be limited to `sz'
|
||||
* elements regardless of the rounded size.
|
||||
*/
|
||||
static std::unique_ptr<RingBuffer> Create(size_t sz, size_t elem_sz, int limit_writes);
|
||||
[[nodiscard]] static
|
||||
auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr<RingBuffer>;
|
||||
|
||||
DEF_FAM_NEWDEL(RingBuffer, mBuffer)
|
||||
};
|
||||
|
||||
@@ -5,36 +5,37 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
std::string wstr_to_utf8(const WCHAR *wstr)
|
||||
#include "alstring.h"
|
||||
|
||||
std::string wstr_to_utf8(std::wstring_view wstr)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
|
||||
const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0,
|
||||
nullptr, nullptr)};
|
||||
if(len > 0)
|
||||
{
|
||||
ret.resize(len);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
|
||||
ret.pop_back();
|
||||
ret.resize(static_cast<size_t>(len));
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len,
|
||||
nullptr, nullptr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::wstring utf8_to_wstr(const char *str)
|
||||
std::wstring utf8_to_wstr(std::string_view str)
|
||||
{
|
||||
std::wstring ret;
|
||||
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
|
||||
const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)};
|
||||
if(len > 0)
|
||||
{
|
||||
ret.resize(len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
|
||||
ret.pop_back();
|
||||
ret.resize(static_cast<size_t>(len));
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -43,21 +44,25 @@ std::wstring utf8_to_wstr(const char *str)
|
||||
|
||||
namespace al {
|
||||
|
||||
al::optional<std::string> getenv(const char *envname)
|
||||
std::optional<std::string> getenv(const char *envname)
|
||||
{
|
||||
#ifdef _GAMING_XBOX
|
||||
const char *str{::getenv(envname)};
|
||||
#else
|
||||
const char *str{std::getenv(envname)};
|
||||
if(str && str[0] != '\0')
|
||||
return al::make_optional<std::string>(str);
|
||||
return al::nullopt;
|
||||
#endif
|
||||
if(str && *str != '\0')
|
||||
return str;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
al::optional<std::wstring> getenv(const WCHAR *envname)
|
||||
std::optional<std::wstring> getenv(const WCHAR *envname)
|
||||
{
|
||||
const WCHAR *str{_wgetenv(envname)};
|
||||
if(str && str[0] != L'\0')
|
||||
return al::make_optional<std::wstring>(str);
|
||||
return al::nullopt;
|
||||
if(str && *str != L'\0')
|
||||
return str;
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#ifndef AL_STRUTILS_H
|
||||
#define AL_STRUTILS_H
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <string_view>
|
||||
#include <wchar.h>
|
||||
|
||||
std::string wstr_to_utf8(const wchar_t *wstr);
|
||||
std::wstring utf8_to_wstr(const char *str);
|
||||
std::string wstr_to_utf8(std::wstring_view wstr);
|
||||
std::wstring utf8_to_wstr(std::string_view str);
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
al::optional<std::string> getenv(const char *envname);
|
||||
std::optional<std::string> getenv(const char *envname);
|
||||
#ifdef _WIN32
|
||||
al::optional<std::wstring> getenv(const wchar_t *envname);
|
||||
std::optional<std::wstring> getenv(const wchar_t *envname);
|
||||
#endif
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef AL_THREADS_H
|
||||
#define AL_THREADS_H
|
||||
|
||||
#if defined(__GNUC__) && defined(__i386__)
|
||||
/* force_align_arg_pointer is required for proper function arguments aligning
|
||||
* when SSE code is used. Some systems (Windows, QNX) do not guarantee our
|
||||
* thread functions will be properly aligned on the stack, even though GCC may
|
||||
* generate code with the assumption that it is. */
|
||||
#define FORCE_ALIGN __attribute__((force_align_arg_pointer))
|
||||
#else
|
||||
#define FORCE_ALIGN
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <dispatch/dispatch.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
void althrd_setname(const char *name);
|
||||
|
||||
namespace al {
|
||||
|
||||
class semaphore {
|
||||
#ifdef _WIN32
|
||||
using native_type = void*;
|
||||
#elif defined(__APPLE__)
|
||||
using native_type = dispatch_semaphore_t;
|
||||
#else
|
||||
using native_type = sem_t;
|
||||
#endif
|
||||
native_type mSem;
|
||||
|
||||
public:
|
||||
semaphore(unsigned int initial=0);
|
||||
semaphore(const semaphore&) = delete;
|
||||
~semaphore();
|
||||
|
||||
semaphore& operator=(const semaphore&) = delete;
|
||||
|
||||
void post();
|
||||
void wait() noexcept;
|
||||
bool try_wait() noexcept;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_THREADS_H */
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
@@ -19,14 +20,14 @@ class VectorR {
|
||||
public:
|
||||
constexpr VectorR() noexcept = default;
|
||||
constexpr VectorR(const VectorR&) noexcept = default;
|
||||
constexpr VectorR(T a, T b, T c, T d) noexcept : mVals{{a, b, c, d}} { }
|
||||
constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { }
|
||||
|
||||
constexpr VectorR& operator=(const VectorR&) noexcept = default;
|
||||
|
||||
T& operator[](size_t idx) noexcept { return mVals[idx]; }
|
||||
constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; }
|
||||
constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; }
|
||||
|
||||
VectorR& operator+=(const VectorR &rhs) noexcept
|
||||
constexpr VectorR& operator+=(const VectorR &rhs) noexcept
|
||||
{
|
||||
mVals[0] += rhs.mVals[0];
|
||||
mVals[1] += rhs.mVals[1];
|
||||
@@ -35,14 +36,13 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
VectorR operator-(const VectorR &rhs) const noexcept
|
||||
constexpr VectorR operator-(const VectorR &rhs) const noexcept
|
||||
{
|
||||
const VectorR ret{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]};
|
||||
return ret;
|
||||
}
|
||||
|
||||
T normalize(T limit = std::numeric_limits<T>::epsilon())
|
||||
constexpr T normalize(T limit = std::numeric_limits<T>::epsilon())
|
||||
{
|
||||
limit = std::max(limit, std::numeric_limits<T>::epsilon());
|
||||
const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
|
||||
@@ -59,17 +59,17 @@ public:
|
||||
return T{0};
|
||||
}
|
||||
|
||||
constexpr VectorR cross_product(const alu::VectorR<T> &rhs) const
|
||||
[[nodiscard]] constexpr auto cross_product(const alu::VectorR<T> &rhs) const noexcept -> VectorR
|
||||
{
|
||||
return VectorR{
|
||||
(*this)[1]*rhs[2] - (*this)[2]*rhs[1],
|
||||
(*this)[2]*rhs[0] - (*this)[0]*rhs[2],
|
||||
(*this)[0]*rhs[1] - (*this)[1]*rhs[0],
|
||||
mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1],
|
||||
mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2],
|
||||
mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0],
|
||||
T{0}};
|
||||
}
|
||||
|
||||
constexpr T dot_product(const alu::VectorR<T> &rhs) const
|
||||
{ return (*this)[0]*rhs[0] + (*this)[1]*rhs[1] + (*this)[2]*rhs[2]; }
|
||||
[[nodiscard]] constexpr auto dot_product(const alu::VectorR<T> &rhs) const noexcept -> T
|
||||
{ return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; }
|
||||
};
|
||||
using Vector = VectorR<float>;
|
||||
|
||||
@@ -81,16 +81,17 @@ class MatrixR {
|
||||
public:
|
||||
constexpr MatrixR() noexcept = default;
|
||||
constexpr MatrixR(const MatrixR&) noexcept = default;
|
||||
constexpr MatrixR(T aa, T ab, T ac, T ad,
|
||||
T ba, T bb, T bc, T bd,
|
||||
T ca, T cb, T cc, T cd,
|
||||
T da, T db, T dc, T dd) noexcept
|
||||
: mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}}
|
||||
constexpr explicit MatrixR(
|
||||
T aa, T ab, T ac, T ad,
|
||||
T ba, T bb, T bc, T bd,
|
||||
T ca, T cb, T cc, T cd,
|
||||
T da, T db, T dc, T dd) noexcept
|
||||
: mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}
|
||||
{ }
|
||||
|
||||
constexpr MatrixR& operator=(const MatrixR&) noexcept = default;
|
||||
|
||||
auto operator[](size_t idx) noexcept { return al::span<T,4>{&mVals[idx*4], 4}; }
|
||||
constexpr auto operator[](size_t idx) noexcept { return al::span<T,4>{&mVals[idx*4], 4}; }
|
||||
constexpr auto operator[](size_t idx) const noexcept
|
||||
{ return al::span<const T,4>{&mVals[idx*4], 4}; }
|
||||
|
||||
@@ -106,7 +107,7 @@ public:
|
||||
using Matrix = MatrixR<float>;
|
||||
|
||||
template<typename T>
|
||||
inline VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexcept
|
||||
constexpr VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexcept
|
||||
{
|
||||
return VectorR<T>{
|
||||
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
|
||||
@@ -115,13 +116,6 @@ inline VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexce
|
||||
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
|
||||
}
|
||||
|
||||
template<typename U, typename T>
|
||||
inline VectorR<U> cast_to(const VectorR<T> &vec) noexcept
|
||||
{
|
||||
return VectorR<U>{static_cast<U>(vec[0]), static_cast<U>(vec[1]),
|
||||
static_cast<U>(vec[2]), static_cast<U>(vec[3])};
|
||||
}
|
||||
|
||||
} // namespace alu
|
||||
|
||||
#endif /* COMMON_VECMAT_H */
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#ifndef AL_VECTOR_H
|
||||
#define AL_VECTOR_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, size_t alignment=alignof(T)>
|
||||
template<typename T, std::size_t alignment=alignof(T)>
|
||||
using vector = std::vector<T, al::allocator<T, alignment>>;
|
||||
|
||||
} // namespace al
|
||||
|
||||
@@ -20,14 +20,20 @@
|
||||
|
||||
#define STATIC_CAST(...) static_cast<__VA_ARGS__>
|
||||
#define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__>
|
||||
#define MAYBE_UNUSED [[maybe_unused]]
|
||||
|
||||
#else
|
||||
|
||||
#define STATIC_CAST(...) (__VA_ARGS__)
|
||||
#define REINTERPRET_CAST(...) (__VA_ARGS__)
|
||||
#ifdef __GNUC__
|
||||
#define MAYBE_UNUSED __attribute__((__unused__))
|
||||
#else
|
||||
#define MAYBE_UNUSED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static FILE *my_fopen(const char *fname, const char *mode)
|
||||
MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode)
|
||||
{
|
||||
wchar_t *wname=NULL, *wmode=NULL;
|
||||
int namelen, modelen;
|
||||
@@ -44,10 +50,11 @@ static FILE *my_fopen(const char *fname, const char *mode)
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen+modelen));
|
||||
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen) +
|
||||
static_cast<size_t>(modelen));
|
||||
wname = strbuf.get();
|
||||
#else
|
||||
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)(namelen+modelen));
|
||||
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen);
|
||||
#endif
|
||||
wmode = wname + namelen;
|
||||
MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen);
|
||||
|
||||
Reference in New Issue
Block a user