Merge remote-tracking branch 'upstream/master' into refactor-samplebuffer
This commit is contained in:
@@ -494,7 +494,11 @@ IF(WANT_SF2)
|
||||
find_package(FluidSynth 1.1.0)
|
||||
if(FluidSynth_FOUND)
|
||||
SET(LMMS_HAVE_FLUIDSYNTH TRUE)
|
||||
SET(STATUS_FLUIDSYNTH "OK")
|
||||
if(FluidSynth_VERSION_STRING VERSION_GREATER_EQUAL 2)
|
||||
set(STATUS_FLUIDSYNTH "OK")
|
||||
else()
|
||||
set(STATUS_FLUIDSYNTH "OK (FluidSynth version < 2: per-note panning unsupported)")
|
||||
endif()
|
||||
else()
|
||||
SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similar)"
|
||||
"is highly recommended")
|
||||
@@ -605,7 +609,9 @@ else()
|
||||
set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo")
|
||||
endif()
|
||||
if(STRIP)
|
||||
set(STRIP_COMMAND "$<IF:$<TARGET:Debug,RelWithDebInfo>,${NOOP_COMMAND},${STRIP}>")
|
||||
# TODO CMake 3.19: Now that CONFIG generator expressions support testing for
|
||||
# multiple configurations, combine the OR into a single CONFIG expression.
|
||||
set(STRIP_COMMAND "$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,${NOOP_COMMAND},${STRIP}>")
|
||||
else()
|
||||
set(STRIP_COMMAND "${NOOP_COMMAND}")
|
||||
endif()
|
||||
|
||||
388
include/ArrayVector.h
Normal file
388
include/ArrayVector.h
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* ArrayVector.h
|
||||
*
|
||||
* Copyright (c) 2023 Dominic Clark
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LMMS_ARRAY_VECTOR_H
|
||||
#define LMMS_ARRAY_VECTOR_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename T, typename = void>
|
||||
constexpr bool is_input_iterator_v = false;
|
||||
|
||||
template<typename T>
|
||||
constexpr bool is_input_iterator_v<T, std::void_t<typename std::iterator_traits<T>::iterator_category>> =
|
||||
std::is_convertible_v<typename std::iterator_traits<T>::iterator_category, std::input_iterator_tag>;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* A container that stores up to a maximum of `N` elements of type `T` directly
|
||||
* within itself, rather than separately on the heap. Useful when a dynamically
|
||||
* resizeable container is needed for use in real-time code. Can be thought of
|
||||
* as a hybrid between `std::array` and `std::vector`. The interface follows
|
||||
* that of `std::vector` - see standard C++ documentation.
|
||||
*/
|
||||
template<typename T, std::size_t N>
|
||||
class ArrayVector
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = 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>;
|
||||
|
||||
ArrayVector() = default;
|
||||
|
||||
ArrayVector(const ArrayVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{other.m_size}
|
||||
{
|
||||
std::uninitialized_copy(other.begin(), other.end(), begin());
|
||||
}
|
||||
|
||||
ArrayVector(ArrayVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>) :
|
||||
m_size{other.m_size}
|
||||
{
|
||||
std::uninitialized_move(other.begin(), other.end(), begin());
|
||||
other.clear();
|
||||
}
|
||||
|
||||
ArrayVector(size_type count, const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{count}
|
||||
{
|
||||
assert(count <= N);
|
||||
std::uninitialized_fill_n(begin(), count, value);
|
||||
}
|
||||
|
||||
explicit ArrayVector(size_type count) noexcept(std::is_nothrow_default_constructible_v<T>) :
|
||||
m_size{count}
|
||||
{
|
||||
assert(count <= N);
|
||||
std::uninitialized_value_construct_n(begin(), count);
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
ArrayVector(It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
const auto end = std::uninitialized_copy(first, last, begin());
|
||||
m_size = end - begin();
|
||||
assert(m_size <= N);
|
||||
}
|
||||
|
||||
ArrayVector(std::initializer_list<T> il) noexcept(std::is_nothrow_copy_constructible_v<T>) :
|
||||
m_size{il.size()}
|
||||
{
|
||||
assert(il.size() <= N);
|
||||
std::uninitialized_copy(il.begin(), il.end(), begin());
|
||||
}
|
||||
|
||||
~ArrayVector() { std::destroy(begin(), end()); }
|
||||
|
||||
ArrayVector& operator=(const ArrayVector& other)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
if (this != &other) {
|
||||
const auto toAssign = std::min(other.size(), size());
|
||||
const auto assignedFromEnd = other.begin() + toAssign;
|
||||
const auto assignedToEnd = std::copy(other.begin(), other.begin() + toAssign, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_copy(assignedFromEnd, other.end(), end());
|
||||
m_size = other.size();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ArrayVector& operator=(ArrayVector&& other)
|
||||
noexcept(std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
if (this != &other) {
|
||||
const auto toAssign = std::min(other.size(), size());
|
||||
const auto assignedFromEnd = other.begin() + toAssign;
|
||||
const auto assignedToEnd = std::move(other.begin(), other.begin() + toAssign, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_move(assignedFromEnd, other.end(), end());
|
||||
m_size = other.size();
|
||||
other.clear();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ArrayVector& operator=(std::initializer_list<T> il)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
assert(il.size() <= N);
|
||||
const auto toAssign = std::min(il.size(), size());
|
||||
const auto assignedFromEnd = il.begin() + toAssign;
|
||||
const auto assignedToEnd = std::copy(il.begin(), assignedFromEnd, begin());
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_copy(assignedFromEnd, il.end(), end());
|
||||
m_size = il.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void assign(size_type count, const T& value)
|
||||
noexcept(std::is_nothrow_copy_assignable_v<T> && std::is_nothrow_copy_constructible_v<T>)
|
||||
{
|
||||
assert(count <= N);
|
||||
const auto temp = value;
|
||||
const auto toAssign = std::min(count, size());
|
||||
const auto toConstruct = count - toAssign;
|
||||
const auto assignedToEnd = std::fill_n(begin(), toAssign, temp);
|
||||
std::destroy(assignedToEnd, end());
|
||||
std::uninitialized_fill_n(assignedToEnd, toConstruct, temp);
|
||||
m_size = count;
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
void assign(It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
auto pos = begin();
|
||||
for (; first != last && pos != end(); ++pos, ++first) {
|
||||
*pos = *first;
|
||||
}
|
||||
std::destroy(pos, end());
|
||||
pos = std::uninitialized_copy(first, last, pos);
|
||||
m_size = pos - begin();
|
||||
assert(m_size <= N);
|
||||
}
|
||||
|
||||
reference at(size_type index)
|
||||
{
|
||||
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
const_reference at(size_type index) const
|
||||
{
|
||||
if (index >= m_size) { throw std::out_of_range{"index out of range"}; }
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
reference operator[](size_type index) noexcept
|
||||
{
|
||||
assert(index < m_size);
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
const_reference operator[](size_type index) const noexcept
|
||||
{
|
||||
assert(index < m_size);
|
||||
return data()[index];
|
||||
}
|
||||
|
||||
reference front() noexcept { return operator[](0); }
|
||||
const_reference front() const noexcept { return operator[](0); }
|
||||
|
||||
reference back() noexcept { return operator[](m_size - 1); }
|
||||
const_reference back() const noexcept { return operator[](m_size - 1); }
|
||||
|
||||
pointer data() noexcept { return *std::launder(reinterpret_cast<T(*)[N]>(m_data)); }
|
||||
const_pointer data() const noexcept { return *std::launder(reinterpret_cast<const T(*)[N]>(m_data)); }
|
||||
|
||||
iterator begin() noexcept { return data(); }
|
||||
const_iterator begin() const noexcept { return data(); }
|
||||
const_iterator cbegin() const noexcept { return data(); }
|
||||
|
||||
iterator end() noexcept { return data() + m_size; }
|
||||
const_iterator end() const noexcept { return data() + m_size; }
|
||||
const_iterator cend() const noexcept { return data() + m_size; }
|
||||
|
||||
reverse_iterator rbegin() noexcept { return std::reverse_iterator{end()}; }
|
||||
const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator{end()}; }
|
||||
const_reverse_iterator crbegin() const noexcept { return std::reverse_iterator{cend()}; }
|
||||
|
||||
reverse_iterator rend() noexcept { return std::reverse_iterator{begin()}; }
|
||||
const_reverse_iterator rend() const noexcept { return std::reverse_iterator{begin()}; }
|
||||
const_reverse_iterator crend() const noexcept { return std::reverse_iterator{cbegin()}; }
|
||||
|
||||
bool empty() const noexcept { return m_size == 0; }
|
||||
bool full() const noexcept { return m_size == N; }
|
||||
size_type size() const noexcept { return m_size; }
|
||||
size_type max_size() const noexcept { return N; }
|
||||
size_type capacity() const noexcept { return N; }
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
std::destroy(begin(), end());
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); }
|
||||
iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); }
|
||||
|
||||
iterator insert(const_iterator pos, size_type count, const T& value)
|
||||
{
|
||||
assert(m_size + count <= N);
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
const auto newEnd = std::uninitialized_fill_n(end(), count, value);
|
||||
std::rotate(mutPos, end(), newEnd);
|
||||
m_size += count;
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
template<typename It, std::enable_if_t<detail::is_input_iterator_v<It>, int> = 0>
|
||||
iterator insert(const_iterator pos, It first, It last)
|
||||
{
|
||||
// Can't check the size first as the iterator may not be multipass
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
const auto newEnd = std::uninitialized_copy(first, last, end());
|
||||
std::rotate(mutPos, end(), newEnd);
|
||||
m_size = newEnd - begin();
|
||||
assert(m_size <= N);
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
iterator insert(const_iterator pos, std::initializer_list<T> il) { return insert(pos, il.begin(), il.end()); }
|
||||
|
||||
template<typename... Args>
|
||||
iterator emplace(const_iterator pos, Args&&... args)
|
||||
{
|
||||
assert(cbegin() <= pos && pos <= cend());
|
||||
const auto mutPos = begin() + (pos - cbegin());
|
||||
emplace_back(std::forward<Args>(args)...);
|
||||
std::rotate(mutPos, end() - 1, end());
|
||||
return mutPos;
|
||||
}
|
||||
|
||||
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
|
||||
iterator erase(const_iterator first, const_iterator last)
|
||||
{
|
||||
assert(cbegin() <= first && first <= last && last <= cend());
|
||||
const auto mutFirst = begin() + (first - cbegin());
|
||||
const auto mutLast = begin() + (last - cbegin());
|
||||
const auto newEnd = std::move(mutLast, end(), mutFirst);
|
||||
std::destroy(newEnd, end());
|
||||
m_size = newEnd - begin();
|
||||
return mutFirst;
|
||||
}
|
||||
|
||||
void push_back(const T& value) { emplace_back(value); }
|
||||
void push_back(T&& value) { emplace_back(std::move(value)); }
|
||||
|
||||
template<typename... Args>
|
||||
reference emplace_back(Args&&... args)
|
||||
{
|
||||
assert(!full());
|
||||
// TODO C++20: Use std::construct_at
|
||||
const auto result = new(static_cast<void*>(end())) T(std::forward<Args>(args)...);
|
||||
++m_size;
|
||||
return *result;
|
||||
}
|
||||
|
||||
void pop_back()
|
||||
{
|
||||
assert(!empty());
|
||||
--m_size;
|
||||
std::destroy_at(end());
|
||||
}
|
||||
|
||||
void resize(size_type size)
|
||||
{
|
||||
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
||||
if (size < m_size) {
|
||||
std::destroy(begin() + size, end());
|
||||
} else {
|
||||
std::uninitialized_value_construct(end(), begin() + size);
|
||||
}
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void resize(size_type size, const value_type& value)
|
||||
{
|
||||
if (size > N) { throw std::length_error{"size exceeds maximum size"}; }
|
||||
if (size < m_size) {
|
||||
std::destroy(begin() + size, end());
|
||||
} else {
|
||||
std::uninitialized_fill(end(), begin() + size, value);
|
||||
}
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void swap(ArrayVector& other)
|
||||
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
using std::swap;
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
friend void swap(ArrayVector& a, ArrayVector& b)
|
||||
noexcept(std::is_nothrow_swappable_v<T> && std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
const auto toSwap = std::min(a.size(), b.size());
|
||||
const auto aSwapEnd = a.begin() + toSwap;
|
||||
const auto bSwapEnd = b.begin() + toSwap;
|
||||
std::swap_ranges(a.begin(), aSwapEnd, b.begin());
|
||||
std::uninitialized_move(aSwapEnd, a.end(), bSwapEnd);
|
||||
std::uninitialized_move(bSwapEnd, b.end(), aSwapEnd);
|
||||
std::destroy(aSwapEnd, a.end());
|
||||
std::destroy(bSwapEnd, b.end());
|
||||
std::swap(a.m_size, b.m_size);
|
||||
}
|
||||
|
||||
// TODO C++20: Replace with operator<=>
|
||||
friend bool operator<(const ArrayVector& l, const ArrayVector& r)
|
||||
{
|
||||
return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end());
|
||||
}
|
||||
friend bool operator<=(const ArrayVector& l, const ArrayVector& r) { return !(r < l); }
|
||||
friend bool operator>(const ArrayVector& l, const ArrayVector& r) { return r < l; }
|
||||
friend bool operator>=(const ArrayVector& l, const ArrayVector& r) { return !(l < r); }
|
||||
|
||||
friend bool operator==(const ArrayVector& l, const ArrayVector& r)
|
||||
{
|
||||
return std::equal(l.begin(), l.end(), r.begin(), r.end());
|
||||
}
|
||||
// TODO C++20: Remove
|
||||
friend bool operator!=(const ArrayVector& l, const ArrayVector& r) { return !(l == r); }
|
||||
|
||||
private:
|
||||
alignas(T) std::byte m_data[std::max(N * sizeof(T), std::size_t{1})]; // Intentionally a raw array
|
||||
size_type m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_ARRAY_VECTOR_H
|
||||
@@ -25,6 +25,7 @@
|
||||
#ifndef LMMS_GUI_INSTRUMENT_TRACK_VIEW_H
|
||||
#define LMMS_GUI_INSTRUMENT_TRACK_VIEW_H
|
||||
|
||||
#include "MixerLineLcdSpinBox.h"
|
||||
#include "TrackView.h"
|
||||
|
||||
#include "InstrumentTrack.h"
|
||||
@@ -72,6 +73,7 @@ public:
|
||||
|
||||
|
||||
protected:
|
||||
void modelChanged() override;
|
||||
void dragEnterEvent( QDragEnterEvent * _dee ) override;
|
||||
void dropEvent( QDropEvent * _de ) override;
|
||||
|
||||
@@ -97,6 +99,7 @@ private:
|
||||
|
||||
// widgets in track-settings-widget
|
||||
TrackLabelButton * m_tlb;
|
||||
MixerLineLcdSpinBox* m_mixerChannelNumber;
|
||||
Knob * m_volumeKnob;
|
||||
Knob * m_panningKnob;
|
||||
FadeButton * m_activityIndicator;
|
||||
|
||||
@@ -144,6 +144,9 @@ protected:
|
||||
void wheelEvent( QWheelEvent * _me ) override;
|
||||
void changeEvent(QEvent * ev) override;
|
||||
|
||||
void enterEvent(QEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
virtual float getValue( const QPoint & _p );
|
||||
|
||||
private slots:
|
||||
@@ -160,6 +163,7 @@ private:
|
||||
float _innerRadius = 1) const;
|
||||
|
||||
void drawKnob( QPainter * _p );
|
||||
void showTextFloat(int msecBeforeDisplay, int msecDisplayTime);
|
||||
void setPosition( const QPoint & _p );
|
||||
bool updateAngle();
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ public:
|
||||
return m_unpitchedFrequency;
|
||||
}
|
||||
|
||||
//! Get the current per-note detuning for this note
|
||||
float currentDetuning() const { return m_baseDetuning->value(); }
|
||||
|
||||
/*! Renders one chunk using the attached instrument into the buffer */
|
||||
void play( sampleFrame* buffer ) override;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define LMMS_GUI_SAMPLE_TRACK_VIEW_H
|
||||
|
||||
|
||||
#include "MixerLineLcdSpinBox.h"
|
||||
#include "TrackView.h"
|
||||
|
||||
namespace lmms
|
||||
@@ -90,6 +91,7 @@ private slots:
|
||||
|
||||
private:
|
||||
SampleTrackWindow * m_window;
|
||||
MixerLineLcdSpinBox* m_mixerChannelNumber;
|
||||
Knob * m_volumeKnob;
|
||||
Knob * m_panningKnob;
|
||||
FadeButton * m_activityIndicator;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "lmms_export.h"
|
||||
|
||||
class QLabel;
|
||||
class QTimer;
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
@@ -44,6 +45,8 @@ public:
|
||||
|
||||
void setText(const QString & text);
|
||||
|
||||
void showWithDelay(int msecBeforeDisplay, int msecDisplayTime);
|
||||
|
||||
void setVisibilityTimeOut(int msecs);
|
||||
|
||||
void moveGlobal(QWidget * w, const QPoint & offset)
|
||||
@@ -51,11 +54,14 @@ public:
|
||||
move(w->mapToGlobal(QPoint(0, 0)) + offset);
|
||||
}
|
||||
|
||||
void hide();
|
||||
|
||||
private:
|
||||
QLabel * m_textLabel;
|
||||
QTimer * m_showTimer;
|
||||
QTimer * m_hideTimer;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lmms::gui
|
||||
|
||||
#endif
|
||||
|
||||
@@ -48,11 +48,11 @@ class FadeButton;
|
||||
class TrackContainerView;
|
||||
|
||||
|
||||
const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224;
|
||||
const int DEFAULT_SETTINGS_WIDGET_WIDTH = 256;
|
||||
const int TRACK_OP_WIDTH = 78;
|
||||
// This shaves 150-ish pixels off track buttons,
|
||||
// ruled from config: ui.compacttrackbuttons
|
||||
const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96;
|
||||
const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 128;
|
||||
const int TRACK_OP_WIDTH_COMPACT = 62;
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <QDomElement>
|
||||
#include <QLabel>
|
||||
|
||||
#include "ArrayVector.h"
|
||||
#include "AudioEngine.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "FileDialog.h"
|
||||
@@ -71,17 +72,47 @@ Plugin::Descriptor PLUGIN_EXPORT sf2player_plugin_descriptor =
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-owning reference to a single FluidSynth voice, for tracking whether the
|
||||
* referenced voice is still the same voice that was passed to the constructor.
|
||||
*/
|
||||
class FluidVoice
|
||||
{
|
||||
public:
|
||||
//! Create a reference to the voice currently pointed at by `voice`.
|
||||
explicit FluidVoice(fluid_voice_t* voice) :
|
||||
m_voice{voice},
|
||||
m_id{fluid_voice_get_id(voice)}
|
||||
{ }
|
||||
|
||||
//! Get a pointer to the referenced voice.
|
||||
fluid_voice_t* get() const noexcept { return m_voice; }
|
||||
|
||||
//! Test whether this object still refers to the original voice.
|
||||
bool isValid() const
|
||||
{
|
||||
return fluid_voice_get_id(m_voice) == m_id && fluid_voice_is_playing(m_voice);
|
||||
}
|
||||
|
||||
private:
|
||||
fluid_voice_t* m_voice;
|
||||
unsigned int m_id;
|
||||
};
|
||||
|
||||
struct Sf2PluginData
|
||||
{
|
||||
int midiNote;
|
||||
int lastPanning;
|
||||
float lastVelocity;
|
||||
fluid_voice_t * fluidVoice;
|
||||
// The soundfonts I checked used at most two voices per note, so space for
|
||||
// four should be safe. This may need to be increased if a soundfont with
|
||||
// more voices per note is found.
|
||||
ArrayVector<FluidVoice, 4> fluidVoices;
|
||||
bool isNew;
|
||||
f_cnt_t offset;
|
||||
bool noteOffSent;
|
||||
} ;
|
||||
panning_t panning;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -681,10 +712,10 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * )
|
||||
pluginData->midiNote = midiNote;
|
||||
pluginData->lastPanning = 0;
|
||||
pluginData->lastVelocity = _n->midiVelocity( baseVelocity );
|
||||
pluginData->fluidVoice = nullptr;
|
||||
pluginData->isNew = true;
|
||||
pluginData->offset = _n->offset();
|
||||
pluginData->noteOffSent = false;
|
||||
pluginData->panning = _n->getPanning();
|
||||
|
||||
_n->m_pluginData = pluginData;
|
||||
|
||||
@@ -703,6 +734,17 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * )
|
||||
m_playingNotes.append( _n );
|
||||
m_playingNotesMutex.unlock();
|
||||
}
|
||||
|
||||
// Update the pitch of all the voices
|
||||
if (const auto data = static_cast<Sf2PluginData*>(_n->m_pluginData)) {
|
||||
const auto detuning = _n->currentDetuning();
|
||||
for (const auto& voice : data->fluidVoices) {
|
||||
if (voice.isValid()) {
|
||||
fluid_voice_gen_set(voice.get(), GEN_COARSETUNE, detuning);
|
||||
fluid_voice_update_param(voice.get(), GEN_COARSETUNE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -715,35 +757,47 @@ void Sf2Instrument::noteOn( Sf2PluginData * n )
|
||||
const int poly = fluid_synth_get_polyphony( m_synth );
|
||||
#ifndef _MSC_VER
|
||||
fluid_voice_t* voices[poly];
|
||||
unsigned int id[poly];
|
||||
#else
|
||||
const auto voices = static_cast<fluid_voice_t**>(_alloca(poly * sizeof(fluid_voice_t*)));
|
||||
const auto id = static_cast<unsigned int*>(_alloca(poly * sizeof(unsigned int)));
|
||||
#endif
|
||||
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
|
||||
for( int i = 0; i < poly; ++i )
|
||||
{
|
||||
id[i] = 0;
|
||||
}
|
||||
for( int i = 0; i < poly && voices[i]; ++i )
|
||||
{
|
||||
id[i] = fluid_voice_get_id( voices[i] );
|
||||
}
|
||||
|
||||
fluid_synth_noteon( m_synth, m_channel, n->midiNote, n->lastVelocity );
|
||||
|
||||
// get new voice and save it
|
||||
fluid_synth_get_voicelist( m_synth, voices, poly, -1 );
|
||||
for( int i = 0; i < poly && voices[i]; ++i )
|
||||
// Get any new voices and store them in the plugin data
|
||||
fluid_synth_get_voicelist(m_synth, voices, poly, -1);
|
||||
for (int i = 0; i < poly && voices[i] && !n->fluidVoices.full(); ++i)
|
||||
{
|
||||
const unsigned int newID = fluid_voice_get_id( voices[i] );
|
||||
if( id[i] != newID || newID == 0 )
|
||||
{
|
||||
n->fluidVoice = voices[i];
|
||||
break;
|
||||
const auto voice = voices[i];
|
||||
// FluidSynth stops voices with the same channel and pitch upon note-on,
|
||||
// so voices with the current channel and pitch are playing this note.
|
||||
if (fluid_voice_get_channel(voice) == m_channel
|
||||
&& fluid_voice_get_key(voice) == n->midiNote
|
||||
&& fluid_voice_is_on(voice)
|
||||
) {
|
||||
n->fluidVoices.emplace_back(voices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#if FLUIDSYNTH_VERSION_MAJOR >= 2
|
||||
// Smallest balance value that results in full attenuation of one channel.
|
||||
// Corresponds to internal FluidSynth macro `FLUID_CB_AMP_SIZE`.
|
||||
constexpr static auto maxBalance = 1441.f;
|
||||
// Convert panning from linear to exponential for FluidSynth
|
||||
const auto panning = n->panning;
|
||||
const auto factor = 1.f - std::abs(panning) / static_cast<float>(PanningRight);
|
||||
const auto balance = std::copysign(
|
||||
factor <= 0 ? maxBalance : std::min(-200.f * std::log10(factor), maxBalance),
|
||||
panning
|
||||
);
|
||||
// Set note panning on all the voices
|
||||
for (const auto& voice : n->fluidVoices) {
|
||||
if (voice.isValid()) {
|
||||
fluid_voice_gen_set(voice.get(), GEN_CUSTOM_BALANCE, balance);
|
||||
fluid_voice_update_param(voice.get(), GEN_CUSTOM_BALANCE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_synthMutex.unlock();
|
||||
|
||||
m_notesRunningMutex.lock();
|
||||
@@ -859,6 +913,7 @@ void Sf2Instrument::play( sampleFrame * _working_buffer )
|
||||
void Sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf )
|
||||
{
|
||||
m_synthMutex.lock();
|
||||
fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect
|
||||
if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() &&
|
||||
m_srcState != nullptr )
|
||||
{
|
||||
|
||||
@@ -1671,6 +1671,8 @@ void DataFile::upgrade_extendedNoteRange()
|
||||
{
|
||||
auto root = documentElement();
|
||||
UpgradeExtendedNoteRange upgradeExtendedNoteRange(root);
|
||||
|
||||
upgradeExtendedNoteRange.upgrade();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ static void fixTrack(QDomElement & track, std::set<unsigned int> & automatedBase
|
||||
for (int i = 0; i < subTracks.size(); ++i)
|
||||
{
|
||||
QDomElement subTrack = subTracks.item(i).toElement();
|
||||
assert (static_cast<Track::Type>(subTrack.attribute("type").toInt()) != Track::Type::Pattern);
|
||||
fixTrack(subTrack, automatedBaseNoteIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +62,16 @@ void SideBarWidget::paintEvent( QPaintEvent * )
|
||||
|
||||
QFont f = p.font();
|
||||
f.setBold( true );
|
||||
f.setUnderline( true );
|
||||
f.setUnderline(false);
|
||||
f.setPointSize( f.pointSize() + 2 );
|
||||
p.setFont( f );
|
||||
|
||||
p.setPen( palette().highlightedText().color() );
|
||||
|
||||
const int tx = m_icon.width()+4;
|
||||
const int tx = m_icon.width() + 8;
|
||||
|
||||
QFontMetrics metrics( f );
|
||||
const int ty = metrics.ascent();
|
||||
const int ty = (metrics.ascent() + m_icon.height()) / 2;
|
||||
p.drawText( tx, ty, m_title );
|
||||
|
||||
p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) );
|
||||
|
||||
@@ -63,7 +63,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
|
||||
m_tlb = new TrackLabelButton( this, getTrackSettingsWidget() );
|
||||
m_tlb->setCheckable( true );
|
||||
m_tlb->setIcon( embed::getIconPixmap( "instrument_track" ) );
|
||||
m_tlb->move( 3, 1 );
|
||||
m_tlb->show();
|
||||
|
||||
connect( m_tlb, SIGNAL(toggled(bool)),
|
||||
@@ -75,24 +74,14 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
|
||||
connect(ConfigManager::inst(), SIGNAL(valueChanged(QString,QString,QString)),
|
||||
this, SLOT(handleConfigChange(QString,QString,QString)));
|
||||
|
||||
// creation of widgets for track-settings-widget
|
||||
int widgetWidth;
|
||||
if( ConfigManager::inst()->value( "ui",
|
||||
"compacttrackbuttons" ).toInt() )
|
||||
{
|
||||
widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT;
|
||||
}
|
||||
else
|
||||
{
|
||||
widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH;
|
||||
}
|
||||
m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this);
|
||||
m_mixerChannelNumber->show();
|
||||
|
||||
m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(),
|
||||
tr( "Volume" ) );
|
||||
m_volumeKnob->setVolumeKnob( true );
|
||||
m_volumeKnob->setModel( &_it->m_volumeModel );
|
||||
m_volumeKnob->setHintText( tr( "Volume:" ), "%" );
|
||||
m_volumeKnob->move( widgetWidth-2*24, 2 );
|
||||
m_volumeKnob->setLabel( tr( "VOL" ) );
|
||||
m_volumeKnob->show();
|
||||
|
||||
@@ -100,7 +89,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
|
||||
tr( "Panning" ) );
|
||||
m_panningKnob->setModel( &_it->m_panningModel );
|
||||
m_panningKnob->setHintText(tr("Panning:"), "%");
|
||||
m_panningKnob->move( widgetWidth-24, 2 );
|
||||
m_panningKnob->setLabel( tr( "PAN" ) );
|
||||
m_panningKnob->show();
|
||||
|
||||
@@ -151,9 +139,18 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV
|
||||
QApplication::palette().color( QPalette::Active,
|
||||
QPalette::BrightText).darker(),
|
||||
getTrackSettingsWidget() );
|
||||
m_activityIndicator->setGeometry(
|
||||
widgetWidth-2*24-11, 2, 8, 28 );
|
||||
m_activityIndicator->setFixedSize(8, 28);
|
||||
m_activityIndicator->show();
|
||||
|
||||
auto layout = new QHBoxLayout(getTrackSettingsWidget());
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(m_tlb);
|
||||
layout->addWidget(m_mixerChannelNumber);
|
||||
layout->addWidget(m_activityIndicator);
|
||||
layout->addWidget(m_volumeKnob);
|
||||
layout->addWidget(m_panningKnob);
|
||||
|
||||
connect( m_activityIndicator, SIGNAL(pressed()),
|
||||
this, SLOT(activityIndicatorPressed()));
|
||||
connect( m_activityIndicator, SIGNAL(released()),
|
||||
@@ -268,6 +265,13 @@ void InstrumentTrackView::handleConfigChange(QString cls, QString attr, QString
|
||||
}
|
||||
}
|
||||
|
||||
void InstrumentTrackView::modelChanged()
|
||||
{
|
||||
TrackView::modelChanged();
|
||||
auto st = castModel<InstrumentTrack>();
|
||||
m_mixerChannelNumber->setModel(&st->m_mixerChannelModel);
|
||||
}
|
||||
|
||||
void InstrumentTrackView::dragEnterEvent( QDragEnterEvent * _dee )
|
||||
{
|
||||
InstrumentTrackWindow::dragEnterEventGeneric( _dee );
|
||||
|
||||
@@ -56,20 +56,17 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
|
||||
connect(m_tlb, SIGNAL(clicked(bool)),
|
||||
this, SLOT(showEffects()));
|
||||
m_tlb->setIcon(embed::getIconPixmap("sample_track"));
|
||||
m_tlb->move(3, 1);
|
||||
m_tlb->show();
|
||||
|
||||
m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this);
|
||||
m_mixerChannelNumber->show();
|
||||
|
||||
m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(),
|
||||
tr( "Track volume" ) );
|
||||
m_volumeKnob->setVolumeKnob( true );
|
||||
m_volumeKnob->setModel( &_t->m_volumeModel );
|
||||
m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" );
|
||||
|
||||
int settingsWidgetWidth = ConfigManager::inst()->
|
||||
value( "ui", "compacttrackbuttons" ).toInt()
|
||||
? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT
|
||||
: DEFAULT_SETTINGS_WIDGET_WIDTH;
|
||||
m_volumeKnob->move( settingsWidgetWidth - 2 * 24, 2 );
|
||||
m_volumeKnob->setLabel( tr( "VOL" ) );
|
||||
m_volumeKnob->show();
|
||||
|
||||
@@ -77,7 +74,6 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
|
||||
tr( "Panning" ) );
|
||||
m_panningKnob->setModel( &_t->m_panningModel );
|
||||
m_panningKnob->setHintText( tr( "Panning:" ), "%" );
|
||||
m_panningKnob->move( settingsWidgetWidth - 24, 2 );
|
||||
m_panningKnob->setLabel( tr( "PAN" ) );
|
||||
m_panningKnob->show();
|
||||
|
||||
@@ -87,8 +83,18 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
|
||||
QApplication::palette().color(QPalette::Active, QPalette::BrightText).darker(),
|
||||
getTrackSettingsWidget()
|
||||
);
|
||||
m_activityIndicator->setGeometry(settingsWidgetWidth - 2 * 24 - 11, 2, 8, 28);
|
||||
m_activityIndicator->setFixedSize(8, 28);
|
||||
m_activityIndicator->show();
|
||||
|
||||
auto layout = new QHBoxLayout(getTrackSettingsWidget());
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(m_tlb);
|
||||
layout->addWidget(m_mixerChannelNumber);
|
||||
layout->addWidget(m_activityIndicator);
|
||||
layout->addWidget(m_volumeKnob);
|
||||
layout->addWidget(m_panningKnob);
|
||||
|
||||
connect(_t, SIGNAL(playingChanged()), this, SLOT(updateIndicator()));
|
||||
|
||||
setModel( _t );
|
||||
@@ -170,6 +176,7 @@ void SampleTrackView::modelChanged()
|
||||
{
|
||||
auto st = castModel<SampleTrack>();
|
||||
m_volumeKnob->setModel(&st->m_volumeModel);
|
||||
m_mixerChannelNumber->setModel(&st->m_mixerChannelModel);
|
||||
|
||||
TrackView::modelChanged();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Knob.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QApplication>
|
||||
#include <QFontMetrics>
|
||||
@@ -34,7 +36,6 @@
|
||||
#endif
|
||||
|
||||
#include "lmms_math.h"
|
||||
#include "Knob.h"
|
||||
#include "CaptionMenu.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "ControllerConnection.h"
|
||||
@@ -48,7 +49,6 @@
|
||||
#include "SimpleTextFloat.h"
|
||||
#include "StringPairDrag.h"
|
||||
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
|
||||
@@ -484,6 +484,13 @@ void Knob::drawKnob( QPainter * _p )
|
||||
_p->drawImage( 0, 0, m_cache );
|
||||
}
|
||||
|
||||
void Knob::showTextFloat(int msecBeforeDisplay, int msecDisplayTime)
|
||||
{
|
||||
s_textFloat->setText(displayValue());
|
||||
s_textFloat->moveGlobal(this, QPoint(width() + 2, 0));
|
||||
s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime);
|
||||
}
|
||||
|
||||
float Knob::getValue( const QPoint & _p )
|
||||
{
|
||||
float value;
|
||||
@@ -580,10 +587,8 @@ void Knob::mousePressEvent( QMouseEvent * _me )
|
||||
|
||||
emit sliderPressed();
|
||||
|
||||
s_textFloat->setText( displayValue() );
|
||||
s_textFloat->moveGlobal( this,
|
||||
QPoint( width() + 2, 0 ) );
|
||||
s_textFloat->show();
|
||||
showTextFloat(0, 0);
|
||||
|
||||
m_buttonPressed = true;
|
||||
}
|
||||
else if( _me->button() == Qt::LeftButton &&
|
||||
@@ -613,6 +618,7 @@ void Knob::mouseMoveEvent( QMouseEvent * _me )
|
||||
m_lastMousePos = _me->pos();
|
||||
}
|
||||
s_textFloat->setText( displayValue() );
|
||||
s_textFloat->show();
|
||||
}
|
||||
|
||||
|
||||
@@ -638,7 +644,15 @@ void Knob::mouseReleaseEvent( QMouseEvent* event )
|
||||
s_textFloat->hide();
|
||||
}
|
||||
|
||||
void Knob::enterEvent(QEvent *event)
|
||||
{
|
||||
showTextFloat(700, 2000);
|
||||
}
|
||||
|
||||
void Knob::leaveEvent(QEvent *event)
|
||||
{
|
||||
s_textFloat->hide();
|
||||
}
|
||||
|
||||
|
||||
void Knob::focusOutEvent( QFocusEvent * _fe )
|
||||
|
||||
@@ -45,6 +45,14 @@ SimpleTextFloat::SimpleTextFloat() :
|
||||
|
||||
m_textLabel = new QLabel(this);
|
||||
layout->addWidget(m_textLabel);
|
||||
|
||||
m_showTimer = new QTimer();
|
||||
m_showTimer->setSingleShot(true);
|
||||
QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show);
|
||||
|
||||
m_hideTimer = new QTimer();
|
||||
m_hideTimer->setSingleShot(true);
|
||||
QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide);
|
||||
}
|
||||
|
||||
void SimpleTextFloat::setText(const QString & text)
|
||||
@@ -52,6 +60,29 @@ void SimpleTextFloat::setText(const QString & text)
|
||||
m_textLabel->setText(text);
|
||||
}
|
||||
|
||||
void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime)
|
||||
{
|
||||
if (msecBeforeDisplay != 0)
|
||||
{
|
||||
m_showTimer->start(msecBeforeDisplay);
|
||||
}
|
||||
else
|
||||
{
|
||||
show();
|
||||
}
|
||||
|
||||
if (msecDisplayTime != 0)
|
||||
{
|
||||
m_hideTimer->start(msecBeforeDisplay + msecDisplayTime);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleTextFloat::hide()
|
||||
{
|
||||
m_showTimer->stop();
|
||||
m_hideTimer->stop();
|
||||
QWidget::hide();
|
||||
}
|
||||
|
||||
void SimpleTextFloat::setVisibilityTimeOut(int msecs)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ ADD_EXECUTABLE(tests
|
||||
QTestSuite.cpp
|
||||
$<TARGET_OBJECTS:lmmsobjs>
|
||||
|
||||
src/core/ArrayVectorTest.cpp
|
||||
src/core/AutomatableModelTest.cpp
|
||||
src/core/ProjectVersionTest.cpp
|
||||
src/core/RelativePathsTest.cpp
|
||||
|
||||
@@ -16,7 +16,7 @@ int main(int argc, char* argv[])
|
||||
int failed = 0;
|
||||
for (QTestSuite*& suite : QTestSuite::suites())
|
||||
{
|
||||
failed += QTest::qExec(suite, argc, argv);
|
||||
if (QTest::qExec(suite, argc, argv) != 0) { ++failed; }
|
||||
}
|
||||
qDebug() << "<<" << failed << "out of"<<numsuites<<"test suites failed.";
|
||||
return failed;
|
||||
|
||||
831
tests/src/core/ArrayVectorTest.cpp
Normal file
831
tests/src/core/ArrayVectorTest.cpp
Normal file
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
* ArrayVectorTest.cpp
|
||||
*
|
||||
* Copyright (c) 2023 Dominic Clark
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ArrayVector.h"
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
|
||||
#include "QTestSuite.h"
|
||||
|
||||
using lmms::ArrayVector;
|
||||
|
||||
struct ShouldNotConstruct
|
||||
{
|
||||
ShouldNotConstruct() { QFAIL("should not construct"); }
|
||||
};
|
||||
|
||||
struct ShouldNotDestruct
|
||||
{
|
||||
~ShouldNotDestruct() { QFAIL("should not destruct"); }
|
||||
};
|
||||
|
||||
enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign };
|
||||
|
||||
struct Constructible
|
||||
{
|
||||
Constructible() : construction{Construction::Default} {}
|
||||
Constructible(const Constructible&) : construction{Construction::Copy} {}
|
||||
Constructible(Constructible&&) : construction{Construction::Move} {}
|
||||
Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; }
|
||||
Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; }
|
||||
Construction construction;
|
||||
};
|
||||
|
||||
struct DestructorCheck
|
||||
{
|
||||
~DestructorCheck() { *destructed = true; }
|
||||
bool* destructed;
|
||||
};
|
||||
|
||||
class ArrayVectorTest : QTestSuite
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void defaultConstructorTest()
|
||||
{
|
||||
// Ensure no elements are constructed
|
||||
const auto v = ArrayVector<ShouldNotConstruct, 1>();
|
||||
// Ensure the container is empty
|
||||
QVERIFY(v.empty());
|
||||
}
|
||||
|
||||
void copyConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 1>{{}};
|
||||
const auto copy = v;
|
||||
for (const auto& element : copy) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto copy = v;
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void moveConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure all elements are move constructed
|
||||
auto v = ArrayVector<Constructible, 1>{{}};
|
||||
const auto moved = std::move(v);
|
||||
for (const auto& element : moved) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto moved = std::move(v);
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end()));
|
||||
// Move construction should leave the source empty
|
||||
QVERIFY(v.empty());
|
||||
}
|
||||
}
|
||||
|
||||
void fillValueConstructorTest()
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 2>(1, {});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
// Ensure the container has the correct size
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
}
|
||||
|
||||
void fillDefaultConstructorTest()
|
||||
{
|
||||
// Ensure all elements are copy constructed
|
||||
const auto v = ArrayVector<Constructible, 2>(1);
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Default);
|
||||
}
|
||||
// Ensure the container has the correct size
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
}
|
||||
|
||||
void rangeConstructorTest()
|
||||
{
|
||||
{
|
||||
// Ensure the elements are copy constructed from normal iterators
|
||||
const auto data = std::array{Constructible{}};
|
||||
const auto v = ArrayVector<Constructible, 1>(data.begin(), data.end());
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure the elements are move constructed from move iterators
|
||||
auto data = std::array{Constructible{}};
|
||||
const auto v = ArrayVector<Constructible, 1>(
|
||||
std::move_iterator{data.begin()}, std::move_iterator{data.end()});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto data = std::array{1, 2, 3};
|
||||
const auto v = ArrayVector<int, 5>(data.begin(), data.end());
|
||||
QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void initializerListConstructorTest()
|
||||
{
|
||||
// Ensure the container is constructed with the correct data
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
void destructorTest()
|
||||
{
|
||||
{
|
||||
// Should not call destructors for space without elements
|
||||
const auto v = ArrayVector<ShouldNotDestruct, 1>{};
|
||||
}
|
||||
{
|
||||
// Should call destructors for all elements
|
||||
auto destructed = false;
|
||||
{
|
||||
const auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
}
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
}
|
||||
|
||||
void copyAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Self-assignment should not change the contents
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto oldValue = v;
|
||||
v = v;
|
||||
QCOMPARE(v, oldValue);
|
||||
}
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
const auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>(5);
|
||||
dst = src;
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
const auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>{};
|
||||
dst = src;
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
const auto src = ArrayVector<int, 5>{1, 2, 3};
|
||||
auto dst = ArrayVector<int, 5>{};
|
||||
dst = src;
|
||||
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void moveAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Self-assignment should not change the contents
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto oldValue = v;
|
||||
v = std::move(v);
|
||||
QCOMPARE(v, oldValue);
|
||||
}
|
||||
{
|
||||
// Assignment to a larger container should move assign
|
||||
auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>(5);
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::MoveAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should move construct
|
||||
auto src = ArrayVector<Constructible, 5>(3);
|
||||
auto dst = ArrayVector<Constructible, 5>{};
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst.size(), std::size_t{3});
|
||||
for (const auto& element : dst) {
|
||||
QCOMPARE(element.construction, Construction::Move);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto src = ArrayVector<int, 5>{1, 2, 3};
|
||||
auto dst = ArrayVector<int, 5>{};
|
||||
dst = std::move(src);
|
||||
QCOMPARE(dst, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void initializerListAssignmentTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
auto v = ArrayVector<Constructible, 2>(2);
|
||||
v = {Constructible{}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v = {Constructible{}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v = {1, 2, 3};
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void fillValueAssignTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
auto v = ArrayVector<Constructible, 5>(5);
|
||||
v.assign(3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
auto v = ArrayVector<Constructible, 5>{};
|
||||
v.assign(3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v.assign(3, 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 1, 1}));
|
||||
}
|
||||
}
|
||||
|
||||
void rangeAssignTest()
|
||||
{
|
||||
{
|
||||
// Assignment to a larger container should copy assign
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>(2);
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::CopyAssign);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Assignment to a smaller container should copy construct
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
const auto data = std::array{1, 2, 3};
|
||||
auto v = ArrayVector<int, 5>{};
|
||||
v.assign(data.begin(), data.end());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void atTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.at(1), 2);
|
||||
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.at(1), 2);
|
||||
QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range);
|
||||
}
|
||||
}
|
||||
|
||||
void subscriptTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v[1], 2);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v[1], 2);
|
||||
}
|
||||
}
|
||||
|
||||
void frontTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.front(), 1);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.front(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void backTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.back(), 3);
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.back(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
void dataTest()
|
||||
{
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.data(), &v.front());
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QCOMPARE(v.data(), &v.front());
|
||||
}
|
||||
}
|
||||
|
||||
void beginEndTest()
|
||||
{
|
||||
const auto expected = std::array{1, 2, 3};
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end()));
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void rbeginRendTest()
|
||||
{
|
||||
const auto expected = std::array{3, 2, 1};
|
||||
{
|
||||
// Non-const version
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
|
||||
QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end()));
|
||||
}
|
||||
{
|
||||
// Const version
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end()));
|
||||
}
|
||||
}
|
||||
|
||||
void emptyFullSizeMaxCapacityTest()
|
||||
{
|
||||
auto v = ArrayVector<int, 2>{};
|
||||
QVERIFY(v.empty());
|
||||
QVERIFY(!v.full());
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
v.push_back(1);
|
||||
QVERIFY(!v.empty());
|
||||
QVERIFY(!v.full());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
v.push_back(2);
|
||||
QVERIFY(!v.empty());
|
||||
QVERIFY(v.full());
|
||||
QCOMPARE(v.size(), std::size_t{2});
|
||||
QCOMPARE(v.max_size(), std::size_t{2});
|
||||
QCOMPARE(v.capacity(), std::size_t{2});
|
||||
|
||||
auto empty = ArrayVector<int, 0>{};
|
||||
QVERIFY(empty.empty());
|
||||
QVERIFY(empty.full());
|
||||
QCOMPARE(empty.size(), std::size_t{0});
|
||||
QCOMPARE(empty.max_size(), std::size_t{0});
|
||||
QCOMPARE(empty.capacity(), std::size_t{0});
|
||||
}
|
||||
|
||||
void insertValueTest()
|
||||
{
|
||||
{
|
||||
// Copy
|
||||
const auto data = Constructible{};
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.insert(v.cbegin(), data);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Move
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.insert(v.cbegin(), Constructible{});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Move);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (copy)
|
||||
const auto data = 1;
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.insert(v.cbegin(), data);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.insert(v.cbegin(), 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertFillValueTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
auto v = ArrayVector<Constructible, 5>{};
|
||||
v.insert(v.cbegin(), 3, {});
|
||||
QCOMPARE(v.size(), std::size_t{3});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
auto v = ArrayVector<int, 5>{1, 3};
|
||||
v.insert(v.cbegin() + 1, 3, 2);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 2, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertRangeTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
const auto data = std::array{Constructible{}};
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.insert(v.cbegin(), data.begin(), data.end());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure correct value is filled
|
||||
const auto data = std::array{2, 3};
|
||||
auto v = ArrayVector<int, 5>{1, 4};
|
||||
v.insert(v.cbegin() + 1, data.begin(), data.end());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
|
||||
}
|
||||
}
|
||||
|
||||
void insertInitializerListTest()
|
||||
{
|
||||
{
|
||||
// Insertion should copy construct
|
||||
auto v = ArrayVector<Constructible, 2>{};
|
||||
v.insert(v.cbegin(), {Constructible{}});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
for (const auto& element : v) {
|
||||
QCOMPARE(element.construction, Construction::Copy);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Ensure corresponding elements are used
|
||||
auto v = ArrayVector<int, 5>{1, 4};
|
||||
v.insert(v.cbegin() + 1, {2, 3});
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3, 4}));
|
||||
}
|
||||
}
|
||||
|
||||
void emplaceTest()
|
||||
{
|
||||
{
|
||||
// Ensure the value is constructed in-place
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.emplace(v.cbegin());
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{2, 3};
|
||||
v.emplace(v.cbegin(), 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void eraseTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.erase(v.cbegin());
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{10, 1, 2, 3};
|
||||
v.erase(v.cbegin());
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void eraseRangeTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.erase(v.cbegin(), v.cend());
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 20, 21, 2, 3};
|
||||
v.erase(v.cbegin() + 1, v.cbegin() + 3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void pushBackTest()
|
||||
{
|
||||
{
|
||||
// Copy
|
||||
const auto data = Constructible{};
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.push_back(data);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Move
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.push_back({});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Move);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (copy)
|
||||
const auto data = 3;
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.push_back(data);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.push_back(3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void emplaceBackTest()
|
||||
{
|
||||
{
|
||||
// Ensure the value is constructed in-place
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
v.emplace_back();
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used (move)
|
||||
auto v = ArrayVector<int, 5>{1, 2};
|
||||
v.emplace_back(3);
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2, 3}));
|
||||
}
|
||||
}
|
||||
|
||||
void popBackTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.pop_back();
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
v.pop_back();
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{1, 2}));
|
||||
}
|
||||
}
|
||||
|
||||
void resizeDefaultTest()
|
||||
{
|
||||
{
|
||||
// Smaller
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
v.resize(0);
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Bigger
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
v.resize(1);
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Default);
|
||||
}
|
||||
{
|
||||
// Too big
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
|
||||
}
|
||||
}
|
||||
|
||||
void resizeValueTest()
|
||||
{
|
||||
{
|
||||
// Smaller
|
||||
auto dummy = false;
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
v.resize(0, {&dummy});
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Bigger
|
||||
auto v = ArrayVector<Constructible, 1>{};
|
||||
QCOMPARE(v.size(), std::size_t{0});
|
||||
v.resize(1, {});
|
||||
QCOMPARE(v.size(), std::size_t{1});
|
||||
QCOMPARE(v[0].construction, Construction::Copy);
|
||||
}
|
||||
{
|
||||
// Too big
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error);
|
||||
}
|
||||
{
|
||||
// Ensure the correct value is used
|
||||
auto v = ArrayVector<int, 1>{};
|
||||
v.resize(1, 1);
|
||||
QCOMPARE(v, (ArrayVector<int, 1>{1}));
|
||||
}
|
||||
}
|
||||
|
||||
void clearTest()
|
||||
{
|
||||
{
|
||||
// Ensure destructors are run
|
||||
auto destructed = false;
|
||||
auto v = ArrayVector<DestructorCheck, 1>{{&destructed}};
|
||||
v.clear();
|
||||
QVERIFY(destructed);
|
||||
}
|
||||
{
|
||||
// Ensure the result is correct
|
||||
auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
v.clear();
|
||||
QCOMPARE(v, (ArrayVector<int, 5>{}));
|
||||
}
|
||||
}
|
||||
|
||||
void memberSwapTest()
|
||||
{
|
||||
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
|
||||
auto b = ArrayVector<int, 5>{2, 4, 6};
|
||||
|
||||
const auto aOriginal = a;
|
||||
const auto bOriginal = b;
|
||||
|
||||
a.swap(b);
|
||||
|
||||
QCOMPARE(a, bOriginal);
|
||||
QCOMPARE(b, aOriginal);
|
||||
}
|
||||
|
||||
void freeSwapTest()
|
||||
{
|
||||
auto a = ArrayVector<int, 5>{1, 2, 3, 4};
|
||||
auto b = ArrayVector<int, 5>{2, 4, 6};
|
||||
|
||||
const auto aOriginal = a;
|
||||
const auto bOriginal = b;
|
||||
|
||||
swap(a, b);
|
||||
|
||||
QCOMPARE(a, bOriginal);
|
||||
QCOMPARE(b, aOriginal);
|
||||
}
|
||||
|
||||
void comparisonTest()
|
||||
{
|
||||
const auto v = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto l = ArrayVector<int, 5>{1, 2, 2};
|
||||
const auto e = ArrayVector<int, 5>{1, 2, 3};
|
||||
const auto g = ArrayVector<int, 5>{1, 3, 3};
|
||||
|
||||
QVERIFY(l < v);
|
||||
QVERIFY(!(e < v));
|
||||
QVERIFY(!(g < v));
|
||||
|
||||
QVERIFY(l <= v);
|
||||
QVERIFY(e <= v);
|
||||
QVERIFY(!(g <= v));
|
||||
|
||||
QVERIFY(!(l > v));
|
||||
QVERIFY(!(e > v));
|
||||
QVERIFY(g > v);
|
||||
|
||||
QVERIFY(!(l >= v));
|
||||
QVERIFY(e >= v);
|
||||
QVERIFY(g >= v);
|
||||
|
||||
QVERIFY(!(l == v));
|
||||
QVERIFY(e == v);
|
||||
QVERIFY(!(g == v));
|
||||
|
||||
QVERIFY(l != v);
|
||||
QVERIFY(!(e != v));
|
||||
QVERIFY(g != v);
|
||||
}
|
||||
} ArrayVectorTests;
|
||||
|
||||
#include "ArrayVectorTest.moc"
|
||||
Reference in New Issue
Block a user