summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2024-02-03 14:17:59 +0000
committerDan Goodliffe <dan@randomdan.homeip.net>2024-02-03 14:17:59 +0000
commit7bb4bfc23f3764770c7728d00dbe272a035db146 (patch)
tree679b48ea9e3cddeeed48e5668e609f026e2287c4
parentMerge branch 'geo-shadows' (diff)
parentAllow specifying a target other than array buffer (diff)
downloadilt-7bb4bfc23f3764770c7728d00dbe272a035db146.tar.bz2
ilt-7bb4bfc23f3764770c7728d00dbe272a035db146.tar.xz
ilt-7bb4bfc23f3764770c7728d00dbe272a035db146.zip
Merge branch 'glcontainer-fix'
-rw-r--r--lib/glContainer.h213
-rw-r--r--test/test-glContainer.cpp115
-rw-r--r--test/test-instancing.cpp6
3 files changed, 217 insertions, 117 deletions
diff --git a/lib/glContainer.h b/lib/glContainer.h
index 2ccc1c2..5cbb038 100644
--- a/lib/glContainer.h
+++ b/lib/glContainer.h
@@ -7,6 +7,9 @@
#include <utility>
#include <vector>
+static_assert(GL_READ_ONLY < GL_READ_WRITE);
+static_assert(GL_WRITE_ONLY < GL_READ_WRITE);
+
template<typename T> class glContainer {
public:
using span = std::span<T>;
@@ -23,7 +26,7 @@ public:
using const_reverse_iterator = const_span::reverse_iterator;
static constexpr bool is_trivial_dest = std::is_trivially_destructible_v<T>;
- glContainer()
+ explicit glContainer(GLenum target = GL_ARRAY_BUFFER) : target_ {target}
{
allocBuffer(1);
}
@@ -38,7 +41,8 @@ public:
clear();
}
- template<template<typename, typename...> typename C> explicit glContainer(const C<T> & src)
+ template<template<typename, typename...> typename C>
+ explicit glContainer(const C<T> & src, GLenum target = GL_ARRAY_BUFFER) : target_ {target}
{
reserve(src.size());
std::copy(src.begin(), src.end(), std::back_inserter(*this));
@@ -49,85 +53,85 @@ public:
[[nodiscard]] iterator
begin()
{
- map();
- return data_.begin();
+ map(GL_READ_WRITE);
+ return mkspan().begin();
}
[[nodiscard]] iterator
end()
{
- map();
- return data_.end();
+ map(GL_READ_WRITE);
+ return mkspan().end();
}
[[nodiscard]] const_iterator
begin() const
{
- map();
- return const_span {data_}.begin();
+ map(GL_READ_ONLY);
+ return mkcspan().begin();
}
[[nodiscard]] const_iterator
end() const
{
- map();
- return const_span {data_}.end();
+ map(GL_READ_ONLY);
+ return mkcspan().end();
}
[[nodiscard]] const_iterator
cbegin() const
{
- map();
- return const_span {data_}.begin();
+ map(GL_READ_ONLY);
+ return mkcspan().begin();
}
[[nodiscard]] const_iterator
cend() const
{
- map();
- return const_span {data_}.end();
+ map(GL_READ_ONLY);
+ return mkcspan().end();
}
[[nodiscard]] reverse_iterator
rbegin()
{
- map();
- return data_.rbegin();
+ map(GL_READ_WRITE);
+ return mkspan().rbegin();
}
[[nodiscard]] reverse_iterator
rend()
{
- map();
- return data_.rend();
+ map(GL_READ_WRITE);
+ return mkspan().rend();
}
[[nodiscard]] const_reverse_iterator
rbegin() const
{
- map();
- return const_span {data_}.rbegin();
+ map(GL_READ_ONLY);
+ return mkcspan().rbegin();
}
[[nodiscard]] const_reverse_iterator
rend() const
{
- map();
- return const_span {data_}.rend();
+ map(GL_READ_ONLY);
+ return mkcspan().rend();
}
[[nodiscard]] const_reverse_iterator
crbegin() const
{
- map();
- return const_span {data_}.rbegin();
+ map(GL_READ_ONLY);
+ return mkcspan().rbegin();
}
[[nodiscard]] const_reverse_iterator
crend() const
{
- map();
- return const_span {data_}.rend();
+ map(GL_READ_ONLY);
+ return mkcspan().rend();
}
[[nodiscard]] const auto &
@@ -148,13 +152,13 @@ public:
if (pos >= size()) {
throw std::out_of_range {__FUNCTION__};
}
- if (data_.data()) {
- data_[pos] = value;
+ if (data_) {
+ mkspan()[pos] = value;
}
else {
- glBindBuffer(GL_ARRAY_BUFFER, buffer_);
- glBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(pos * sizeof(T)), sizeof(value), &value);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(target_, buffer_);
+ glBufferSubData(target_, static_cast<GLintptr>(pos * sizeof(T)), sizeof(value), &value);
+ glBindBuffer(target_, 0);
}
}
@@ -164,8 +168,8 @@ public:
if (pos >= size()) {
throw std::out_of_range {__FUNCTION__};
}
- map();
- return data_[pos];
+ map(GL_READ_WRITE);
+ return mkspan()[pos];
}
[[nodiscard]] const_reference_type
@@ -174,64 +178,64 @@ public:
if (pos >= size()) {
throw std::out_of_range {__FUNCTION__};
}
- map();
- return data_[pos];
+ map(GL_READ_ONLY);
+ return mkcspan()[pos];
}
[[nodiscard]] reference_type
operator[](size_type pos)
{
- map();
- return data_[pos];
+ map(GL_READ_WRITE);
+ return mkspan()[pos];
}
[[nodiscard]] const_reference_type
operator[](size_type pos) const
{
- map();
- return data_[pos];
+ map(GL_READ_ONLY);
+ return mkcspan()[pos];
}
[[nodiscard]] pointer_type
data()
{
- map();
- return data_.data();
+ map(GL_READ_WRITE);
+ return data_;
}
[[nodiscard]] const_pointer_type
data() const
{
- map();
- return data_.data();
+ map(GL_READ_ONLY);
+ return data_;
}
[[nodiscard]] reference_type
front()
{
- map();
- return data_.front();
+ map(GL_READ_WRITE);
+ return mkspan().front();
}
[[nodiscard]] reference_type
back()
{
- map();
- return data_.back();
+ map(GL_READ_WRITE);
+ return mkspan().back();
}
[[nodiscard]] const_reference_type
front() const
{
- map();
- return data_.front();
+ map(GL_READ_ONLY);
+ return mkcspan().front();
}
[[nodiscard]] const_reference_type
back() const
{
- map();
- return data_.back();
+ map(GL_READ_ONLY);
+ return mkcspan().back();
}
[[nodiscard]] bool
@@ -249,9 +253,12 @@ public:
void
unmap() const
{
- if (data_.data()) {
- glUnmapNamedBuffer(buffer_);
+ if (data_) {
+ glBindBuffer(target_, buffer_);
+ glUnmapBuffer(target_);
+ glBindBuffer(target_, 0);
data_ = {};
+ access_ = {};
}
}
@@ -272,6 +279,7 @@ public:
void
resize(size_type newSize)
+ requires std::is_default_constructible_v<T>
{
if (newSize == size_) {
return;
@@ -290,8 +298,10 @@ public:
allocBuffer(newSize);
mapForAdd();
}
- for (auto uninitialised = data_.data() + size_; uninitialised < data_.data() + newSize; ++uninitialised) {
- new (uninitialised) T {};
+ if (newSize > size_) {
+ for (auto & uninitialised : mkspan().subspan(size_, newSize - size_)) {
+ new (&uninitialised) T {};
+ }
}
setSize(newSize);
}
@@ -305,10 +315,10 @@ public:
std::vector<T> existing;
existing.reserve(size_);
- map();
+ map(is_trivial_dest ? GL_READ_ONLY : GL_READ_WRITE);
std::move(begin(), end(), std::back_inserter(existing));
allocBuffer(size_);
- map();
+ map(GL_READ_WRITE);
std::move(existing.begin(), existing.end(), begin());
}
@@ -316,7 +326,7 @@ public:
clear() noexcept(is_trivial_dest)
{
if constexpr (!is_trivial_dest) {
- map();
+ map(GL_READ_WRITE);
std::for_each(begin(), end(), [](auto && v) {
v.~T();
});
@@ -327,11 +337,12 @@ public:
template<typename... P>
reference_type
emplace_back(P &&... ps)
+ requires std::is_constructible_v<T, P...>
{
auto newSize = size_ + 1;
reserve(newSize);
mapForAdd();
- new (&data_[size_]) T {std::forward<P>(ps)...};
+ new (&*end()) T {std::forward<P>(ps)...};
setSize(newSize);
return back();
}
@@ -339,43 +350,44 @@ public:
template<typename... P>
iterator
emplace(iterator pos, P &&... ps)
+ requires std::is_nothrow_constructible_v<T, P...>
{
- static_assert(std::is_nothrow_constructible_v<T, P...>);
auto newSize = size_ + 1;
const auto idx = pos - begin();
reserve(newSize);
mapForAdd();
- auto newT = begin() + idx;
- std::move_backward(newT, end(), end() + 1);
- newT->~T();
- new (&*newT) T {std::forward<P>(ps)...};
+ pos = begin() + idx;
+ std::move_backward(pos, end(), end() + 1);
+ pos->~T();
+ new (&*pos) T {std::forward<P>(ps)...};
setSize(newSize);
return pos;
}
reference_type
push_back(T p)
+ requires std::is_move_constructible_v<T>
{
auto newSize = size_ + 1;
reserve(newSize);
mapForAdd();
- new (&data_[size_]) T {std::move(p)};
+ new (&*end()) T {std::move(p)};
setSize(newSize);
return back();
}
iterator
insert(iterator pos, T p)
+ requires std::is_nothrow_move_constructible_v<T>
{
- static_assert(std::is_nothrow_move_constructible_v<T>);
auto newSize = size_ + 1;
const auto idx = pos - begin();
reserve(newSize);
mapForAdd();
- auto newT = begin() + idx;
- std::move_backward(newT, end(), end() + 1);
- newT->~T();
- new (&*newT) T {std::move(p)};
+ pos = begin() + idx;
+ std::move_backward(pos, end(), end() + 1);
+ pos->~T();
+ new (&*pos) T {std::move(p)};
setSize(newSize);
return pos;
}
@@ -384,12 +396,10 @@ public:
pop_back()
{
if constexpr (!is_trivial_dest) {
- map();
- data_[--size_].~T();
- }
- else {
- --size_;
+ map(GL_READ_WRITE);
+ back().~T();
}
+ --size_;
}
void
@@ -402,7 +412,7 @@ public:
erase(iterator pos, iterator to)
{
const auto eraseSize = to - pos;
- map();
+ map(GL_READ_WRITE);
std::move(to, end(), pos);
if constexpr (!is_trivial_dest) {
std::for_each(end() - eraseSize, end(), [](auto && v) {
@@ -416,7 +426,7 @@ protected:
void
setSize(size_type s)
{
- data_ = {data_.data(), size_ = s};
+ size_ = s;
}
void
@@ -425,32 +435,63 @@ protected:
if (newCapacity == 0) {
return allocBuffer(1);
}
- glBindBuffer(GL_ARRAY_BUFFER, buffer_);
- glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(target_, buffer_);
+ glBufferData(target_, static_cast<GLsizeiptr>(sizeof(T) * newCapacity), nullptr, GL_DYNAMIC_DRAW);
+ glBindBuffer(target_, 0);
capacity_ = newCapacity;
data_ = {};
+ access_ = {};
}
void
- map() const
+ map(GLenum access) const
{
if (size_ > 0) {
- mapForAdd();
+ mapMode(access);
}
}
void
mapForAdd() const
{
- if (!data_.data()) {
- data_ = {static_cast<T *>(glMapNamedBuffer(buffer_, GL_READ_WRITE)), size_};
- assert(data_.data());
+ if (!data_) {
+ mapMode(GL_READ_WRITE);
+ }
+ }
+
+ void
+ mapMode(GLenum access) const
+ {
+ if (data_ && access_ < access) {
+ unmap();
+ }
+ if (!data_) {
+ glBindBuffer(target_, buffer_);
+ data_ = static_cast<T *>(glMapBuffer(target_, access));
+ glBindBuffer(target_, 0);
+ assert(data_);
+ access_ = access;
}
}
+ span
+ mkspan() const
+ {
+ assert(!size_ || data_);
+ return span {data_, size_};
+ }
+
+ const_span
+ mkcspan() const
+ {
+ assert(!size_ || data_);
+ return const_span {data_, size_};
+ }
+
glBuffer buffer_;
+ GLenum target_;
std::size_t capacity_ {};
std::size_t size_ {};
- mutable span data_;
+ mutable T * data_;
+ mutable GLenum access_ {};
};
diff --git a/test/test-glContainer.cpp b/test/test-glContainer.cpp
index ccf3b90..ec1c0d1 100644
--- a/test/test-glContainer.cpp
+++ b/test/test-glContainer.cpp
@@ -1,6 +1,5 @@
#define BOOST_TEST_MODULE glContainer
-#include "testHelpers.h"
#include "testMainWindow.h"
#include "ui/applicationBase.h"
#include <boost/test/data/test_case.hpp>
@@ -23,14 +22,69 @@ BOOST_FIXTURE_TEST_SUITE(i, glContainer<int>)
BOOST_AUTO_TEST_CASE(createDestroy, *boost::unit_test::timeout(1))
{
- BOOST_CHECK(!data_.data());
- BOOST_CHECK_NO_THROW(map());
- BOOST_REQUIRE(!data_.data());
+ // Unmapped
+ BOOST_CHECK(!data_);
+ BOOST_CHECK(!access_);
+ // Request map, but empty
+ BOOST_CHECK_NO_THROW(map(GL_READ_ONLY));
+ BOOST_REQUIRE(!data_);
+ BOOST_REQUIRE(!access_);
+ BOOST_CHECK_NO_THROW(map(GL_READ_WRITE));
+ BOOST_REQUIRE(!data_);
+ BOOST_REQUIRE(!access_);
+ // Add something
BOOST_CHECK_NO_THROW(emplace_back(0));
- BOOST_CHECK_NO_THROW(map());
- BOOST_REQUIRE(data_.data());
+ BOOST_REQUIRE(data_);
+ BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE);
+ // Unmap
+ BOOST_CHECK_NO_THROW(unmap());
+ BOOST_REQUIRE(!data_);
+ BOOST_REQUIRE(!access_);
+ // Map RO
+ BOOST_CHECK_NO_THROW(map(GL_READ_ONLY));
+ BOOST_REQUIRE(data_);
+ BOOST_REQUIRE_EQUAL(access_, GL_READ_ONLY);
+ // Map RW upgradde
+ BOOST_CHECK_NO_THROW(map(GL_READ_WRITE));
+ BOOST_REQUIRE(data_);
+ BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE);
+ // Map RO downgradde, no change
+ BOOST_CHECK_NO_THROW(map(GL_READ_ONLY));
+ BOOST_REQUIRE(data_);
+ BOOST_REQUIRE_EQUAL(access_, GL_READ_WRITE);
+ // Unmap
+ BOOST_CHECK_NO_THROW(unmap());
+ BOOST_CHECK(!data_);
+ BOOST_CHECK(!access_);
+}
+
+BOOST_AUTO_TEST_CASE(mapModes)
+{
+ BOOST_CHECK_EQUAL(std::accumulate(begin(), end(), 0), 0);
+ BOOST_CHECK(!data_);
+ BOOST_CHECK(!access_);
+
+ BOOST_CHECK_NO_THROW(push_back(1));
+ BOOST_CHECK_NO_THROW(push_back(2));
+ BOOST_CHECK_NO_THROW(push_back(3));
+ BOOST_CHECK_NO_THROW(push_back(4));
+ BOOST_CHECK_NO_THROW(unmap());
+
+ BOOST_CHECK_EQUAL(std::accumulate(cbegin(), cend(), 0), 10);
+ BOOST_CHECK(data_);
+ BOOST_CHECK_EQUAL(access_, GL_READ_ONLY);
+ BOOST_CHECK_NO_THROW(unmap());
+
+ BOOST_CHECK_EQUAL(std::accumulate(begin(), end(), 0), 10);
+ BOOST_CHECK(data_);
+ BOOST_CHECK_EQUAL(access_, GL_READ_WRITE);
+ BOOST_CHECK_NO_THROW(unmap());
+
+ const auto & c = *this;
+ BOOST_CHECK_EQUAL(std::accumulate(c.begin(), c.end(), 0), 10);
+ BOOST_CHECK(data_);
+ BOOST_CHECK_EQUAL(access_, GL_READ_ONLY);
BOOST_CHECK_NO_THROW(unmap());
- BOOST_CHECK(!data_.data());
}
BOOST_AUTO_TEST_CASE(push_back_test, *boost::unit_test::timeout(1))
@@ -44,7 +98,6 @@ BOOST_AUTO_TEST_CASE(push_back_test, *boost::unit_test::timeout(1))
BOOST_CHECK_NO_THROW(push_back(5));
BOOST_CHECK_EQUAL(capacity_, 8);
BOOST_CHECK_EQUAL(size_, 5);
- BOOST_CHECK_EQUAL(data_.size(), 5);
{
std::array expected1 {1, 2, 3, 4, 5};
BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end());
@@ -166,7 +219,7 @@ BOOST_AUTO_TEST_CASE(getters)
BOOST_CHECK_EQUAL(2, at(1));
BOOST_CHECK_EQUAL(1, (*this)[0]);
BOOST_CHECK_EQUAL(2, (*this)[1]);
- BOOST_CHECK_EQUAL(data_.data(), data());
+ BOOST_CHECK_EQUAL(data_, data());
BOOST_CHECK_THROW(std::ignore = at(2), std::out_of_range);
const auto & constCont {*this};
@@ -183,7 +236,7 @@ BOOST_AUTO_TEST_CASE(getters)
BOOST_CHECK_EQUAL(2, constCont.at(1));
BOOST_CHECK_EQUAL(1, constCont[0]);
BOOST_CHECK_EQUAL(2, constCont[1]);
- BOOST_CHECK_EQUAL(data_.data(), constCont.data());
+ BOOST_CHECK_EQUAL(data_, constCont.data());
BOOST_CHECK_THROW(std::ignore = constCont.at(2), std::out_of_range);
}
@@ -209,16 +262,16 @@ BOOST_AUTO_TEST_CASE(random_write)
BOOST_CHECK_NO_THROW(resize(3));
BOOST_CHECK_EQUAL(size(), 3);
BOOST_CHECK_NO_THROW(unmap());
- BOOST_REQUIRE(!data_.data());
+ BOOST_REQUIRE(!data_);
BOOST_CHECK_NO_THROW(at(0, 10));
BOOST_CHECK_NO_THROW(at(1, 20));
BOOST_CHECK_NO_THROW(at(2, 30));
- BOOST_CHECK(!data_.data());
+ BOOST_CHECK(!data_);
{
std::array expected1 {10, 20, 30};
BOOST_CHECK_EQUAL_COLLECTIONS(begin(), end(), expected1.begin(), expected1.end());
}
- BOOST_CHECK(data_.data());
+ BOOST_CHECK(data_);
BOOST_CHECK_NO_THROW(at(1, 40));
{
std::array expected1 {10, 40, 30};
@@ -325,27 +378,33 @@ BOOST_FIXTURE_TEST_SUITE(c, glContainer<C>)
BOOST_AUTO_TEST_CASE(basic)
{
- BOOST_CHECK_NO_THROW(emplace_back(1, 2.f));
+ BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
BOOST_CHECK_EQUAL(1, begin()->x);
- BOOST_CHECK_EQUAL(2.f, begin()->y);
+ BOOST_CHECK_EQUAL(2.F, begin()->y);
BOOST_CHECK_NO_THROW(begin()->x = 3);
BOOST_CHECK_EQUAL(3, begin()->x);
- BOOST_CHECK_NO_THROW(push_back(C {4, 5.f}));
+ BOOST_CHECK_NO_THROW(push_back(C {4, 5.F}));
BOOST_CHECK_EQUAL(3, begin()->x);
- BOOST_CHECK_EQUAL(2.f, begin()->y);
+ BOOST_CHECK_EQUAL(2.F, begin()->y);
BOOST_CHECK_EQUAL(4, rbegin()->x);
- BOOST_CHECK_EQUAL(5.f, rbegin()->y);
+ BOOST_CHECK_EQUAL(5.F, rbegin()->y);
}
BOOST_AUTO_TEST_SUITE_END();
struct CC {
+ CC() = default;
+
+ CC(int a, float b) noexcept : x {a}, y {b} { }
+
~CC()
{
++x;
}
+ DEFAULT_MOVE_COPY(CC);
+
int x;
float y;
};
@@ -356,17 +415,17 @@ BOOST_FIXTURE_TEST_SUITE(cc, glContainer<CC>)
BOOST_AUTO_TEST_CASE(basic)
{
- BOOST_CHECK_NO_THROW(emplace_back(1, 2.f));
+ BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
BOOST_CHECK_EQUAL(1, begin()->x);
- BOOST_CHECK_EQUAL(2.f, begin()->y);
+ BOOST_CHECK_EQUAL(2.F, begin()->y);
BOOST_CHECK_NO_THROW(begin()->x = 3);
BOOST_CHECK_EQUAL(3, begin()->x);
- BOOST_CHECK_NO_THROW(push_back(CC {4, 5.f}));
+ BOOST_CHECK_NO_THROW(push_back(CC {4, 5.F}));
BOOST_CHECK_EQUAL(3, begin()->x);
- BOOST_CHECK_EQUAL(2.f, begin()->y);
+ BOOST_CHECK_EQUAL(2.F, begin()->y);
BOOST_CHECK_EQUAL(4, rbegin()->x);
- BOOST_CHECK_EQUAL(5.f, rbegin()->y);
+ BOOST_CHECK_EQUAL(5.F, rbegin()->y);
BOOST_CHECK_NO_THROW(pop_back());
BOOST_CHECK_EQUAL(size(), 1);
BOOST_CHECK_EQUAL(capacity(), 2);
@@ -380,11 +439,11 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_AUTO_TEST_CASE(insert_remove_test)
{
- BOOST_CHECK_NO_THROW(emplace_back(1, 2.f));
- BOOST_CHECK_NO_THROW(emplace_back(3, 4.f));
- BOOST_CHECK_NO_THROW(emplace(begin(), 5, 6.f));
- BOOST_CHECK_NO_THROW(emplace(begin() + 1, 7, 8.f));
- BOOST_CHECK_NO_THROW(emplace(begin() + 2, 9, 10.f));
+ BOOST_CHECK_NO_THROW(emplace_back(1, 2.F));
+ BOOST_CHECK_NO_THROW(emplace_back(3, 4.F));
+ BOOST_CHECK_NO_THROW(emplace(begin(), 5, 6.F));
+ BOOST_CHECK_NO_THROW(emplace(begin() + 1, 7, 8.F));
+ BOOST_CHECK_NO_THROW(emplace(begin() + 2, 9, 10.F));
BOOST_CHECK_EQUAL(capacity(), 8);
BOOST_CHECK_EQUAL(size(), 5);
BOOST_CHECK_NO_THROW(shrink_to_fit());
diff --git a/test/test-instancing.cpp b/test/test-instancing.cpp
index 98133f3..c1860a4 100644
--- a/test/test-instancing.cpp
+++ b/test/test-instancing.cpp
@@ -56,11 +56,11 @@ BOOST_AUTO_TEST_CASE(autoMapUnmap)
{
{
auto proxy = acquire();
- BOOST_CHECK(data_.data());
+ BOOST_CHECK(data_);
std::ignore = bufferName();
- BOOST_CHECK(data_.data());
+ BOOST_CHECK(data_);
BOOST_CHECK_EQUAL(1, size());
- BOOST_CHECK(!data_.data());
+ BOOST_CHECK(!data_);
}
BOOST_CHECK_EQUAL(0, size());
}