mirror of
https://github.com/BlackMATov/ecs.hpp.git
synced 2025-12-15 11:53:51 +07:00
move to C++17
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
version: "{build}"
|
version: "{build}"
|
||||||
shallow_clone: true
|
shallow_clone: true
|
||||||
image:
|
image:
|
||||||
- Visual Studio 2015
|
|
||||||
- Visual Studio 2017
|
- Visual Studio 2017
|
||||||
- Visual Studio 2019 Preview
|
- Visual Studio 2019
|
||||||
platform:
|
platform:
|
||||||
- Win32
|
- Win32
|
||||||
- x64
|
- x64
|
||||||
|
|||||||
51
.travis.yml
51
.travis.yml
@@ -1,18 +1,6 @@
|
|||||||
language: cpp
|
language: cpp
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ubuntu-toolchain-r-test, packages: ["xorg-dev", "g++-4.9"] } }
|
|
||||||
env: MATRIX_EVAL="CC=gcc-4.9 && CXX=g++-4.9"
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ubuntu-toolchain-r-test, packages: ["xorg-dev", "g++-5"] } }
|
|
||||||
env: MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ubuntu-toolchain-r-test, packages: ["xorg-dev", "g++-6"] } }
|
|
||||||
env: MATRIX_EVAL="CC=gcc-6 && CXX=g++-6"
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons: { apt: { sources: ubuntu-toolchain-r-test, packages: ["xorg-dev", "g++-7"] } }
|
addons: { apt: { sources: ubuntu-toolchain-r-test, packages: ["xorg-dev", "g++-7"] } }
|
||||||
@@ -23,42 +11,12 @@ matrix:
|
|||||||
env: MATRIX_EVAL="CC=gcc-8 && CXX=g++-8"
|
env: MATRIX_EVAL="CC=gcc-8 && CXX=g++-8"
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-precise-3.8"], packages: ["xorg-dev", "clang-3.8", "g++-5"] } }
|
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-7"], packages: ["xorg-dev", "clang-7", "g++-7"] } }
|
||||||
env: MATRIX_EVAL="CC=clang-3.8 && CXX=clang++-3.8"
|
env: MATRIX_EVAL="CC=clang-7 && CXX=clang++-7"
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-precise-3.9"], packages: ["xorg-dev", "clang-3.9", "g++-5"] } }
|
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-8"], packages: ["xorg-dev", "clang-8", "g++-7"] } }
|
||||||
env: MATRIX_EVAL="CC=clang-3.9 && CXX=clang++-3.9"
|
env: MATRIX_EVAL="CC=clang-8 && CXX=clang++-8"
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-4.0"], packages: ["xorg-dev", "clang-4.0", "g++-5"] } }
|
|
||||||
env: MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0"
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-5.0"], packages: ["xorg-dev", "clang-5.0", "g++-7"] } }
|
|
||||||
env: MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0"
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
addons: { apt: { sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-6.0"], packages: ["xorg-dev", "clang-6.0", "g++-7"] } }
|
|
||||||
env: MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0"
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode8.3
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9.1
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9.2
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9.3
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode9.4
|
|
||||||
compiler: clang
|
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode10
|
osx_image: xcode10
|
||||||
compiler: clang
|
compiler: clang
|
||||||
@@ -69,7 +27,6 @@ before_install:
|
|||||||
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then
|
- if [ "$TRAVIS_OS_NAME" == 'osx' ]; then
|
||||||
brew update;
|
brew update;
|
||||||
brew upgrade cmake;
|
brew upgrade cmake;
|
||||||
brew install git-lfs;
|
|
||||||
fi
|
fi
|
||||||
- if [ "$TRAVIS_OS_NAME" == 'linux' ]; then
|
- if [ "$TRAVIS_OS_NAME" == 'linux' ]; then
|
||||||
mkdir $HOME/cmake;
|
mkdir $HOME/cmake;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 3.8 version is required for `cxx_std_14`
|
# 3.8 version is required for `cxx_std_17`
|
||||||
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
|
||||||
|
|
||||||
if(NOT DEFINED PROJECT_NAME)
|
if(NOT DEFINED PROJECT_NAME)
|
||||||
@@ -9,7 +9,7 @@ project(ecs.hpp)
|
|||||||
|
|
||||||
add_library(${PROJECT_NAME} INTERFACE)
|
add_library(${PROJECT_NAME} INTERFACE)
|
||||||
target_include_directories(${PROJECT_NAME} INTERFACE headers)
|
target_include_directories(${PROJECT_NAME} INTERFACE headers)
|
||||||
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_14)
|
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17)
|
||||||
|
|
||||||
if(BUILD_AS_STANDALONE)
|
if(BUILD_AS_STANDALONE)
|
||||||
option(BUILD_WITH_UNBENCH "Build with benchmarks" OFF)
|
option(BUILD_WITH_UNBENCH "Build with benchmarks" OFF)
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
[badge.travis]: https://img.shields.io/travis/BlackMATov/ecs.hpp/master.svg?logo=travis
|
[badge.travis]: https://img.shields.io/travis/BlackMATov/ecs.hpp/master.svg?logo=travis
|
||||||
[badge.appveyor]: https://img.shields.io/appveyor/ci/BlackMATov/ecs-hpp/master.svg?logo=appveyor
|
[badge.appveyor]: https://img.shields.io/appveyor/ci/BlackMATov/ecs-hpp/master.svg?logo=appveyor
|
||||||
[badge.codecov]: https://img.shields.io/codecov/c/github/BlackMATov/ecs.hpp/master.svg?logo=codecov
|
[badge.codecov]: https://img.shields.io/codecov/c/github/BlackMATov/ecs.hpp/master.svg?logo=codecov
|
||||||
[badge.language]: https://img.shields.io/badge/language-C%2B%2B14-red.svg
|
[badge.language]: https://img.shields.io/badge/language-C%2B%2B17-yellow.svg
|
||||||
[badge.license]: https://img.shields.io/badge/license-MIT-blue.svg
|
[badge.license]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
[badge.paypal]: https://img.shields.io/badge/donate-PayPal-orange.svg?logo=paypal&colorA=00457C
|
[badge.paypal]: https://img.shields.io/badge/donate-PayPal-orange.svg?logo=paypal&colorA=00457C
|
||||||
|
|
||||||
[travis]: https://travis-ci.org/BlackMATov/ecs.hpp
|
[travis]: https://travis-ci.org/BlackMATov/ecs.hpp
|
||||||
[appveyor]: https://ci.appveyor.com/project/BlackMATov/ecs-hpp
|
[appveyor]: https://ci.appveyor.com/project/BlackMATov/ecs-hpp
|
||||||
[codecov]: https://codecov.io/gh/BlackMATov/ecs.hpp
|
[codecov]: https://codecov.io/gh/BlackMATov/ecs.hpp
|
||||||
[language]: https://en.wikipedia.org/wiki/C%2B%2B14
|
[language]: https://en.wikipedia.org/wiki/C%2B%2B17
|
||||||
[license]: https://en.wikipedia.org/wiki/MIT_License
|
[license]: https://en.wikipedia.org/wiki/MIT_License
|
||||||
[paypal]: https://www.paypal.me/matov
|
[paypal]: https://www.paypal.me/matov
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ namespace ecs_hpp
|
|||||||
constexpr std::size_t entity_id_version_bits = 10u;
|
constexpr std::size_t entity_id_version_bits = 10u;
|
||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_unsigned<family_id>::value,
|
std::is_unsigned_v<family_id>,
|
||||||
"ecs_hpp (family_id must be an unsigned integer)");
|
"ecs_hpp (family_id must be an unsigned integer)");
|
||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_unsigned<entity_id>::value,
|
std::is_unsigned_v<entity_id>,
|
||||||
"ecs_hpp (entity_id must be an unsigned integer)");
|
"ecs_hpp (entity_id must be an unsigned integer)");
|
||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
@@ -77,15 +77,6 @@ namespace ecs_hpp
|
|||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
//
|
|
||||||
// as_const
|
|
||||||
//
|
|
||||||
|
|
||||||
template < typename T >
|
|
||||||
constexpr std::add_const_t<T>& as_const(T& t) noexcept {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// hash_combine
|
// hash_combine
|
||||||
//
|
//
|
||||||
@@ -102,21 +93,19 @@ namespace ecs_hpp
|
|||||||
{
|
{
|
||||||
template < typename T, typename... Ts, std::size_t... Is >
|
template < typename T, typename... Ts, std::size_t... Is >
|
||||||
std::tuple<Ts...> tuple_tail_impl(
|
std::tuple<Ts...> tuple_tail_impl(
|
||||||
std::tuple<T, Ts...>&& t,
|
std::index_sequence<Is...>,
|
||||||
std::index_sequence<Is...> iseq)
|
std::tuple<T, Ts...>&& t)
|
||||||
{
|
{
|
||||||
(void)t;
|
(void)t;
|
||||||
(void)iseq;
|
|
||||||
return std::make_tuple(std::move(std::get<Is + 1u>(t))...);
|
return std::make_tuple(std::move(std::get<Is + 1u>(t))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T, typename... Ts, std::size_t... Is >
|
template < typename T, typename... Ts, std::size_t... Is >
|
||||||
std::tuple<Ts...> tuple_tail_impl(
|
std::tuple<Ts...> tuple_tail_impl(
|
||||||
const std::tuple<T, Ts...>& t,
|
std::index_sequence<Is...>,
|
||||||
std::index_sequence<Is...> iseq)
|
const std::tuple<T, Ts...>& t)
|
||||||
{
|
{
|
||||||
(void)t;
|
(void)t;
|
||||||
(void)iseq;
|
|
||||||
return std::make_tuple(std::get<Is + 1u>(t)...);
|
return std::make_tuple(std::get<Is + 1u>(t)...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,15 +113,15 @@ namespace ecs_hpp
|
|||||||
template < typename T, typename... Ts >
|
template < typename T, typename... Ts >
|
||||||
std::tuple<Ts...> tuple_tail(std::tuple<T, Ts...>&& t) {
|
std::tuple<Ts...> tuple_tail(std::tuple<T, Ts...>&& t) {
|
||||||
return impl::tuple_tail_impl(
|
return impl::tuple_tail_impl(
|
||||||
std::move(t),
|
std::make_index_sequence<sizeof...(Ts)>(),
|
||||||
std::make_index_sequence<sizeof...(Ts)>());
|
std::move(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T, typename... Ts >
|
template < typename T, typename... Ts >
|
||||||
std::tuple<Ts...> tuple_tail(const std::tuple<T, Ts...>& t) {
|
std::tuple<Ts...> tuple_tail(const std::tuple<T, Ts...>& t) {
|
||||||
return impl::tuple_tail_impl(
|
return impl::tuple_tail_impl(
|
||||||
t,
|
std::make_index_sequence<sizeof...(Ts)>(),
|
||||||
std::make_index_sequence<sizeof...(Ts)>());
|
t);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -141,47 +130,23 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
namespace impl
|
namespace impl
|
||||||
{
|
{
|
||||||
template < size_t I, typename V, typename... Ts >
|
template < typename V, typename... Ts, std::size_t... Is >
|
||||||
std::enable_if_t<I == sizeof...(Ts), bool>
|
bool tuple_contains_impl(
|
||||||
tuple_contains_impl(const std::tuple<Ts...>& t, const V& v) {
|
std::index_sequence<Is...>,
|
||||||
(void)t;
|
const std::tuple<Ts...>& t,
|
||||||
(void)v;
|
const V& v)
|
||||||
return false;
|
{
|
||||||
}
|
(void)t; (void)v;
|
||||||
|
return (... || (std::get<Is>(t) == v));
|
||||||
template < size_t I, typename V, typename... Ts >
|
|
||||||
std::enable_if_t<I != sizeof...(Ts), bool>
|
|
||||||
tuple_contains_impl(const std::tuple<Ts...>& t, const V& v) {
|
|
||||||
if ( std::get<I>(t) == v ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return tuple_contains_impl<I + 1>(t, v);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename V, typename... Ts >
|
template < typename V, typename... Ts >
|
||||||
bool tuple_contains(const std::tuple<Ts...>& t, const V& v) {
|
bool tuple_contains(const std::tuple<Ts...>& t, const V& v) {
|
||||||
return impl::tuple_contains_impl<0>(t, v);
|
return impl::tuple_contains_impl(
|
||||||
}
|
std::make_index_sequence<sizeof...(Ts)>(),
|
||||||
|
t,
|
||||||
//
|
v);
|
||||||
// tiny_tuple_apply
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace impl
|
|
||||||
{
|
|
||||||
template < typename F, typename Tuple, std::size_t... I >
|
|
||||||
void tiny_tuple_apply_impl(F&& f, Tuple&& args, std::index_sequence<I...>) {
|
|
||||||
std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename F, typename Tuple >
|
|
||||||
void tiny_tuple_apply(F&& f, Tuple&& args) {
|
|
||||||
impl::tiny_tuple_apply_impl(
|
|
||||||
std::forward<F>(f),
|
|
||||||
std::forward<Tuple>(args),
|
|
||||||
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -242,7 +207,7 @@ namespace ecs_hpp
|
|||||||
template < typename Void = void >
|
template < typename Void = void >
|
||||||
class type_family_base {
|
class type_family_base {
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_void<Void>::value,
|
std::is_void_v<Void>,
|
||||||
"unexpected internal error");
|
"unexpected internal error");
|
||||||
protected:
|
protected:
|
||||||
static family_id last_id_;
|
static family_id last_id_;
|
||||||
@@ -273,24 +238,23 @@ namespace ecs_hpp
|
|||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template < typename T
|
template < typename T >
|
||||||
, bool = std::is_unsigned<T>::value
|
struct sparse_indexer final {
|
||||||
&& sizeof(T) <= sizeof(std::size_t) >
|
static_assert(std::is_unsigned_v<T>);
|
||||||
struct sparse_unsigned_indexer {
|
static_assert(sizeof(T) <= sizeof(std::size_t));
|
||||||
std::size_t operator()(const T v) const noexcept {
|
std::size_t operator()(const T v) const noexcept {
|
||||||
return static_cast<std::size_t>(v);
|
return static_cast<std::size_t>(v);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template < typename T >
|
|
||||||
struct sparse_unsigned_indexer<T, false> {};
|
|
||||||
|
|
||||||
template < typename T >
|
|
||||||
struct sparse_indexer final
|
|
||||||
: public sparse_unsigned_indexer<T> {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// detail::incremental_locker
|
||||||
|
//
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace ecs_hpp
|
namespace ecs_hpp
|
||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -354,14 +318,6 @@ namespace ecs_hpp
|
|||||||
public:
|
public:
|
||||||
using iterator = typename std::vector<T>::iterator;
|
using iterator = typename std::vector<T>::iterator;
|
||||||
using const_iterator = typename std::vector<T>::const_iterator;
|
using const_iterator = typename std::vector<T>::const_iterator;
|
||||||
public:
|
|
||||||
static_assert(
|
|
||||||
noexcept(std::declval<Indexer>()(std::declval<T>())),
|
|
||||||
"unsupported sparse_set indexer");
|
|
||||||
static_assert(
|
|
||||||
std::is_nothrow_move_assignable<T>::value &&
|
|
||||||
noexcept(std::declval<T>() == std::declval<T>()),
|
|
||||||
"unsupported sparse_set value type");
|
|
||||||
public:
|
public:
|
||||||
iterator begin() noexcept {
|
iterator begin() noexcept {
|
||||||
return dense_.begin();
|
return dense_.begin();
|
||||||
@@ -424,7 +380,8 @@ namespace ecs_hpp
|
|||||||
const std::size_t vi = indexer_(v);
|
const std::size_t vi = indexer_(v);
|
||||||
const std::size_t dense_index = sparse_[vi];
|
const std::size_t dense_index = sparse_[vi];
|
||||||
if ( dense_index != dense_.size() - 1 ) {
|
if ( dense_index != dense_.size() - 1 ) {
|
||||||
dense_[dense_index] = std::move(dense_.back());
|
using std::swap;
|
||||||
|
swap(dense_[dense_index], dense_.back());
|
||||||
sparse_[indexer_(dense_[dense_index])] = dense_index;
|
sparse_[indexer_(dense_[dense_index])] = dense_index;
|
||||||
}
|
}
|
||||||
dense_.pop_back();
|
dense_.pop_back();
|
||||||
@@ -508,10 +465,6 @@ namespace ecs_hpp
|
|||||||
public:
|
public:
|
||||||
using iterator = typename std::vector<K>::iterator;
|
using iterator = typename std::vector<K>::iterator;
|
||||||
using const_iterator = typename std::vector<K>::const_iterator;
|
using const_iterator = typename std::vector<K>::const_iterator;
|
||||||
public:
|
|
||||||
static_assert(
|
|
||||||
std::is_nothrow_move_assignable<T>::value,
|
|
||||||
"unsupported sparse_map value type");
|
|
||||||
public:
|
public:
|
||||||
iterator begin() noexcept {
|
iterator begin() noexcept {
|
||||||
return keys_.begin();
|
return keys_.begin();
|
||||||
@@ -589,7 +542,8 @@ namespace ecs_hpp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ( value_index_p.first != values_.size() - 1 ) {
|
if ( value_index_p.first != values_.size() - 1 ) {
|
||||||
values_[value_index_p.first] = std::move(values_.back());
|
using std::swap;
|
||||||
|
swap(values_[value_index_p.first], values_.back());
|
||||||
}
|
}
|
||||||
values_.pop_back();
|
values_.pop_back();
|
||||||
keys_.unordered_erase(k);
|
keys_.unordered_erase(k);
|
||||||
@@ -693,7 +647,7 @@ namespace ecs_hpp
|
|||||||
virtual std::size_t memory_usage() const noexcept = 0;
|
virtual std::size_t memory_usage() const noexcept = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template < typename T, bool E = std::is_empty<T>::value >
|
template < typename T, bool E = std::is_empty_v<T> >
|
||||||
class component_storage final : public component_storage_base {
|
class component_storage final : public component_storage_base {
|
||||||
public:
|
public:
|
||||||
component_storage(registry& owner)
|
component_storage(registry& owner)
|
||||||
@@ -751,8 +705,7 @@ namespace ecs_hpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
void clone(entity_id from, entity_id to) override {
|
void clone(entity_id from, entity_id to) override {
|
||||||
const T* c = find(from);
|
if ( const T* c = find(from) ) {
|
||||||
if ( c ) {
|
|
||||||
assign(to, *c);
|
assign(to, *c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -845,8 +798,7 @@ namespace ecs_hpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
void clone(entity_id from, entity_id to) override {
|
void clone(entity_id from, entity_id to) override {
|
||||||
const T* c = find(from);
|
if ( const T* c = find(from) ) {
|
||||||
if ( c ) {
|
|
||||||
assign(to, *c);
|
assign(to, *c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1425,8 +1377,8 @@ namespace ecs_hpp
|
|||||||
, std::size_t I
|
, std::size_t I
|
||||||
, std::size_t... Is >
|
, std::size_t... Is >
|
||||||
void for_joined_components_impl_(
|
void for_joined_components_impl_(
|
||||||
F&& f,
|
std::index_sequence<I, Is...>,
|
||||||
std::index_sequence<I, Is...> iseq);
|
F&& f);
|
||||||
|
|
||||||
template < typename T
|
template < typename T
|
||||||
, typename... Ts
|
, typename... Ts
|
||||||
@@ -1434,8 +1386,8 @@ namespace ecs_hpp
|
|||||||
, std::size_t I
|
, std::size_t I
|
||||||
, std::size_t... Is >
|
, std::size_t... Is >
|
||||||
void for_joined_components_impl_(
|
void for_joined_components_impl_(
|
||||||
F&& f,
|
std::index_sequence<I, Is...>,
|
||||||
std::index_sequence<I, Is...> iseq) const;
|
F&& f) const;
|
||||||
|
|
||||||
template < typename T
|
template < typename T
|
||||||
, typename... Ts
|
, typename... Ts
|
||||||
@@ -1561,7 +1513,7 @@ namespace ecs_hpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool entity::valid() const noexcept {
|
inline bool entity::valid() const noexcept {
|
||||||
return detail::as_const(*owner_).valid_entity(id_);
|
return std::as_const(*owner_).valid_entity(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T, typename... Args >
|
template < typename T, typename... Args >
|
||||||
@@ -1585,7 +1537,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
bool entity::exists_component() const noexcept {
|
bool entity::exists_component() const noexcept {
|
||||||
return detail::as_const(*owner_).exists_component<T>(id_);
|
return std::as_const(*owner_).exists_component<T>(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::size_t entity::remove_all_components() noexcept {
|
inline std::size_t entity::remove_all_components() noexcept {
|
||||||
@@ -1599,7 +1551,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T& entity::get_component() const {
|
const T& entity::get_component() const {
|
||||||
return detail::as_const(*owner_).get_component<T>(id_);
|
return std::as_const(*owner_).get_component<T>(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
@@ -1609,7 +1561,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T* entity::find_component() const noexcept {
|
const T* entity::find_component() const noexcept {
|
||||||
return detail::as_const(*owner_).find_component<T>(id_);
|
return std::as_const(*owner_).find_component<T>(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename... Ts >
|
template < typename... Ts >
|
||||||
@@ -1619,7 +1571,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename... Ts >
|
template < typename... Ts >
|
||||||
std::tuple<const Ts&...> entity::get_components() const {
|
std::tuple<const Ts&...> entity::get_components() const {
|
||||||
return detail::as_const(*owner_).get_components<Ts...>(id_);
|
return std::as_const(*owner_).get_components<Ts...>(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename... Ts >
|
template < typename... Ts >
|
||||||
@@ -1629,11 +1581,11 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename... Ts >
|
template < typename... Ts >
|
||||||
std::tuple<const Ts*...> entity::find_components() const noexcept {
|
std::tuple<const Ts*...> entity::find_components() const noexcept {
|
||||||
return detail::as_const(*owner_).find_components<Ts...>(id_);
|
return std::as_const(*owner_).find_components<Ts...>(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::size_t entity::component_count() const noexcept {
|
inline std::size_t entity::component_count() const noexcept {
|
||||||
return detail::as_const(*owner_).entity_component_count(id_);
|
return std::as_const(*owner_).entity_component_count(id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator<(const entity& l, const entity& r) noexcept {
|
inline bool operator<(const entity& l, const entity& r) noexcept {
|
||||||
@@ -1797,7 +1749,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T& component<T>::get() const {
|
const T& component<T>::get() const {
|
||||||
return detail::as_const(owner_).template get_component<T>();
|
return std::as_const(owner_).template get_component<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
@@ -1807,7 +1759,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T* component<T>::find() const noexcept {
|
const T* component<T>::find() const noexcept {
|
||||||
return detail::as_const(owner_).template find_component<T>();
|
return std::as_const(owner_).template find_component<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
@@ -1884,17 +1836,17 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
bool const_component<T>::exists() const noexcept {
|
bool const_component<T>::exists() const noexcept {
|
||||||
return detail::as_const(owner_).template exists_component<T>();
|
return std::as_const(owner_).template exists_component<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T& const_component<T>::get() const {
|
const T& const_component<T>::get() const {
|
||||||
return detail::as_const(owner_).template get_component<T>();
|
return std::as_const(owner_).template get_component<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
const T* const_component<T>::find() const noexcept {
|
const T* const_component<T>::find() const noexcept {
|
||||||
return detail::as_const(owner_).template find_component<T>();
|
return std::as_const(owner_).template find_component<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
@@ -1963,7 +1915,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T, typename... Args >
|
template < typename T, typename... Args >
|
||||||
void typed_applier_with_args<T, Args...>::apply_to_entity(entity& ent, bool override) const {
|
void typed_applier_with_args<T, Args...>::apply_to_entity(entity& ent, bool override) const {
|
||||||
detail::tiny_tuple_apply([&ent, override](const Args&... args){
|
std::apply([&ent, override](const Args&... args){
|
||||||
if ( override || !ent.exists_component<T>() ) {
|
if ( override || !ent.exists_component<T>() ) {
|
||||||
ent.assign_component<T>(args...);
|
ent.assign_component<T>(args...);
|
||||||
}
|
}
|
||||||
@@ -1972,7 +1924,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T, typename... Args >
|
template < typename T, typename... Args >
|
||||||
void typed_applier_with_args<T, Args...>::apply_to_component(T& component) const {
|
void typed_applier_with_args<T, Args...>::apply_to_component(T& component) const {
|
||||||
detail::tiny_tuple_apply([&component](const Args&... args){
|
std::apply([&component](const Args&... args){
|
||||||
component = T(args...);
|
component = T(args...);
|
||||||
}, args_);
|
}, args_);
|
||||||
}
|
}
|
||||||
@@ -2319,8 +2271,7 @@ namespace ecs_hpp
|
|||||||
template < typename T >
|
template < typename T >
|
||||||
T& registry::get_component(const uentity& ent) {
|
T& registry::get_component(const uentity& ent) {
|
||||||
assert(valid_entity(ent));
|
assert(valid_entity(ent));
|
||||||
T* component = find_component<T>(ent);
|
if ( T* component = find_component<T>(ent) ) {
|
||||||
if ( component ) {
|
|
||||||
return *component;
|
return *component;
|
||||||
}
|
}
|
||||||
throw std::logic_error("ecs_hpp::registry (component not found)");
|
throw std::logic_error("ecs_hpp::registry (component not found)");
|
||||||
@@ -2329,8 +2280,7 @@ namespace ecs_hpp
|
|||||||
template < typename T >
|
template < typename T >
|
||||||
const T& registry::get_component(const const_uentity& ent) const {
|
const T& registry::get_component(const const_uentity& ent) const {
|
||||||
assert(valid_entity(ent));
|
assert(valid_entity(ent));
|
||||||
const T* component = find_component<T>(ent);
|
if ( const T* component = find_component<T>(ent) ) {
|
||||||
if ( component ) {
|
|
||||||
return *component;
|
return *component;
|
||||||
}
|
}
|
||||||
throw std::logic_error("ecs_hpp::registry (component not found)");
|
throw std::logic_error("ecs_hpp::registry (component not found)");
|
||||||
@@ -2423,8 +2373,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T, typename F >
|
template < typename T, typename F >
|
||||||
void registry::for_each_component(F&& f) {
|
void registry::for_each_component(F&& f) {
|
||||||
detail::component_storage<T>* storage = find_storage_<T>();
|
if ( detail::component_storage<T>* storage = find_storage_<T>() ) {
|
||||||
if ( storage ) {
|
|
||||||
storage->for_each_component([this, &f](const entity_id e, T& t){
|
storage->for_each_component([this, &f](const entity_id e, T& t){
|
||||||
f(uentity{*this, e}, t);
|
f(uentity{*this, e}, t);
|
||||||
});
|
});
|
||||||
@@ -2433,8 +2382,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T, typename F >
|
template < typename T, typename F >
|
||||||
void registry::for_each_component(F&& f) const {
|
void registry::for_each_component(F&& f) const {
|
||||||
const detail::component_storage<T>* storage = find_storage_<T>();
|
if ( const detail::component_storage<T>* storage = find_storage_<T>() ) {
|
||||||
if ( storage ) {
|
|
||||||
storage->for_each_component([this, &f](const entity_id e, const T& t){
|
storage->for_each_component([this, &f](const entity_id e, const T& t){
|
||||||
f(const_uentity{*this, e}, t);
|
f(const_uentity{*this, e}, t);
|
||||||
});
|
});
|
||||||
@@ -2444,15 +2392,15 @@ namespace ecs_hpp
|
|||||||
template < typename... Ts, typename F >
|
template < typename... Ts, typename F >
|
||||||
void registry::for_joined_components(F&& f) {
|
void registry::for_joined_components(F&& f) {
|
||||||
for_joined_components_impl_<Ts...>(
|
for_joined_components_impl_<Ts...>(
|
||||||
std::forward<F>(f),
|
std::make_index_sequence<sizeof...(Ts)>(),
|
||||||
std::make_index_sequence<sizeof...(Ts)>());
|
std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename... Ts, typename F >
|
template < typename... Ts, typename F >
|
||||||
void registry::for_joined_components(F&& f) const {
|
void registry::for_joined_components(F&& f) const {
|
||||||
for_joined_components_impl_<Ts...>(
|
for_joined_components_impl_<Ts...>(
|
||||||
std::forward<F>(f),
|
std::make_index_sequence<sizeof...(Ts)>(),
|
||||||
std::make_index_sequence<sizeof...(Ts)>());
|
std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T, typename... Args >
|
template < typename T, typename... Args >
|
||||||
@@ -2536,8 +2484,7 @@ namespace ecs_hpp
|
|||||||
|
|
||||||
template < typename T >
|
template < typename T >
|
||||||
detail::component_storage<T>& registry::get_or_create_storage_() {
|
detail::component_storage<T>& registry::get_or_create_storage_() {
|
||||||
detail::component_storage<T>* storage = find_storage_<T>();
|
if ( detail::component_storage<T>* storage = find_storage_<T>() ) {
|
||||||
if ( storage ) {
|
|
||||||
return *storage;
|
return *storage;
|
||||||
}
|
}
|
||||||
const auto family = detail::type_family<T>::id();
|
const auto family = detail::type_family<T>::id();
|
||||||
@@ -2554,16 +2501,16 @@ namespace ecs_hpp
|
|||||||
, std::size_t I
|
, std::size_t I
|
||||||
, std::size_t... Is >
|
, std::size_t... Is >
|
||||||
void registry::for_joined_components_impl_(
|
void registry::for_joined_components_impl_(
|
||||||
F&& f,
|
std::index_sequence<I, Is...>,
|
||||||
std::index_sequence<I, Is...> iseq)
|
F&& f)
|
||||||
{
|
{
|
||||||
(void)iseq;
|
|
||||||
const auto ss = std::make_tuple(find_storage_<Ts>()...);
|
const auto ss = std::make_tuple(find_storage_<Ts>()...);
|
||||||
if ( !detail::tuple_contains(ss, nullptr) ) {
|
if ( detail::tuple_contains(ss, nullptr) ) {
|
||||||
for_each_component<T>([this, &f, &ss](const uentity& e, T& t) {
|
return;
|
||||||
for_joined_components_impl_<Ts...>(e, f, ss, t);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
for_each_component<T>([this, &f, &ss](const uentity& e, T& t) {
|
||||||
|
for_joined_components_impl_<Ts...>(e, f, ss, t);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T
|
template < typename T
|
||||||
@@ -2572,16 +2519,16 @@ namespace ecs_hpp
|
|||||||
, std::size_t I
|
, std::size_t I
|
||||||
, std::size_t... Is >
|
, std::size_t... Is >
|
||||||
void registry::for_joined_components_impl_(
|
void registry::for_joined_components_impl_(
|
||||||
F&& f,
|
std::index_sequence<I, Is...>,
|
||||||
std::index_sequence<I, Is...> iseq) const
|
F&& f) const
|
||||||
{
|
{
|
||||||
(void)iseq;
|
|
||||||
const auto ss = std::make_tuple(find_storage_<Ts>()...);
|
const auto ss = std::make_tuple(find_storage_<Ts>()...);
|
||||||
if ( !detail::tuple_contains(ss, nullptr) ) {
|
if ( detail::tuple_contains(ss, nullptr) ) {
|
||||||
for_each_component<T>([this, &f, &ss](const const_uentity& e, const T& t) {
|
return;
|
||||||
detail::as_const(*this).for_joined_components_impl_<Ts...>(e, f, ss, t);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
for_each_component<T>([this, &f, &ss](const const_uentity& e, const T& t) {
|
||||||
|
std::as_const(*this).for_joined_components_impl_<Ts...>(e, f, ss, t);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename T
|
template < typename T
|
||||||
@@ -2595,8 +2542,7 @@ namespace ecs_hpp
|
|||||||
const Ss& ss,
|
const Ss& ss,
|
||||||
Cs&... cs)
|
Cs&... cs)
|
||||||
{
|
{
|
||||||
T* c = std::get<0>(ss)->find(e);
|
if ( T* c = std::get<0>(ss)->find(e) ) {
|
||||||
if ( c ) {
|
|
||||||
for_joined_components_impl_<Ts...>(
|
for_joined_components_impl_<Ts...>(
|
||||||
e,
|
e,
|
||||||
f,
|
f,
|
||||||
@@ -2617,8 +2563,7 @@ namespace ecs_hpp
|
|||||||
const Ss& ss,
|
const Ss& ss,
|
||||||
const Cs&... cs) const
|
const Cs&... cs) const
|
||||||
{
|
{
|
||||||
const T* c = std::get<0>(ss)->find(e);
|
if ( const T* c = std::get<0>(ss)->find(e) ) {
|
||||||
if ( c ) {
|
|
||||||
for_joined_components_impl_<Ts...>(
|
for_joined_components_impl_<Ts...>(
|
||||||
e,
|
e,
|
||||||
f,
|
f,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace
|
|||||||
|
|
||||||
struct movable_c {
|
struct movable_c {
|
||||||
};
|
};
|
||||||
static_assert(std::is_empty<movable_c>::value, "!!!");
|
static_assert(std::is_empty_v<movable_c>, "!!!");
|
||||||
|
|
||||||
bool operator==(const position_c& l, const position_c& r) noexcept {
|
bool operator==(const position_c& l, const position_c& r) noexcept {
|
||||||
return l.x == r.x
|
return l.x == r.x
|
||||||
@@ -203,9 +203,9 @@ TEST_CASE("detail") {
|
|||||||
REQUIRE_FALSE(m.size());
|
REQUIRE_FALSE(m.size());
|
||||||
REQUIRE_FALSE(m.has(42u));
|
REQUIRE_FALSE(m.has(42u));
|
||||||
REQUIRE_THROWS(m.get(42u));
|
REQUIRE_THROWS(m.get(42u));
|
||||||
REQUIRE_THROWS(as_const(m).get(42u));
|
REQUIRE_THROWS(std::as_const(m).get(42u));
|
||||||
REQUIRE_FALSE(m.find(42u));
|
REQUIRE_FALSE(m.find(42u));
|
||||||
REQUIRE_FALSE(as_const(m).find(42u));
|
REQUIRE_FALSE(std::as_const(m).find(42u));
|
||||||
|
|
||||||
{
|
{
|
||||||
obj_t o{21u};
|
obj_t o{21u};
|
||||||
@@ -233,20 +233,20 @@ TEST_CASE("detail") {
|
|||||||
REQUIRE(m.get(21u).x == 21u);
|
REQUIRE(m.get(21u).x == 21u);
|
||||||
REQUIRE(m.get(42u).x == 42u);
|
REQUIRE(m.get(42u).x == 42u);
|
||||||
REQUIRE(m.get(84u).x == 84u);
|
REQUIRE(m.get(84u).x == 84u);
|
||||||
REQUIRE(as_const(m).get(84u).x == 84u);
|
REQUIRE(std::as_const(m).get(84u).x == 84u);
|
||||||
REQUIRE_THROWS(m.get(11u));
|
REQUIRE_THROWS(m.get(11u));
|
||||||
REQUIRE_THROWS(m.get(25u));
|
REQUIRE_THROWS(m.get(25u));
|
||||||
REQUIRE_THROWS(m.get(99u));
|
REQUIRE_THROWS(m.get(99u));
|
||||||
REQUIRE_THROWS(as_const(m).get(99u));
|
REQUIRE_THROWS(std::as_const(m).get(99u));
|
||||||
|
|
||||||
REQUIRE(m.find(21u)->x == 21u);
|
REQUIRE(m.find(21u)->x == 21u);
|
||||||
REQUIRE(m.find(42u)->x == 42u);
|
REQUIRE(m.find(42u)->x == 42u);
|
||||||
REQUIRE(m.find(84u)->x == 84u);
|
REQUIRE(m.find(84u)->x == 84u);
|
||||||
REQUIRE(as_const(m).find(84u)->x == 84u);
|
REQUIRE(std::as_const(m).find(84u)->x == 84u);
|
||||||
REQUIRE_FALSE(m.find(11u));
|
REQUIRE_FALSE(m.find(11u));
|
||||||
REQUIRE_FALSE(m.find(25u));
|
REQUIRE_FALSE(m.find(25u));
|
||||||
REQUIRE_FALSE(m.find(99u));
|
REQUIRE_FALSE(m.find(99u));
|
||||||
REQUIRE_FALSE(as_const(m).find(99u));
|
REQUIRE_FALSE(std::as_const(m).find(99u));
|
||||||
|
|
||||||
REQUIRE(m.unordered_erase(42u));
|
REQUIRE(m.unordered_erase(42u));
|
||||||
REQUIRE_FALSE(m.unordered_erase(42u));
|
REQUIRE_FALSE(m.unordered_erase(42u));
|
||||||
@@ -527,43 +527,43 @@ TEST_CASE("registry") {
|
|||||||
ecs::const_component<position_c> c2 = w.wrap_component<position_c>(e1);
|
ecs::const_component<position_c> c2 = w.wrap_component<position_c>(e1);
|
||||||
|
|
||||||
REQUIRE_FALSE(c1);
|
REQUIRE_FALSE(c1);
|
||||||
REQUIRE_FALSE(as_const(c1));
|
REQUIRE_FALSE(std::as_const(c1));
|
||||||
REQUIRE_FALSE(c2);
|
REQUIRE_FALSE(c2);
|
||||||
REQUIRE_FALSE(as_const(c2));
|
REQUIRE_FALSE(std::as_const(c2));
|
||||||
|
|
||||||
REQUIRE_THROWS_AS(*c1, std::logic_error);
|
REQUIRE_THROWS_AS(*c1, std::logic_error);
|
||||||
REQUIRE_THROWS_AS(*as_const(c1), std::logic_error);
|
REQUIRE_THROWS_AS(*std::as_const(c1), std::logic_error);
|
||||||
REQUIRE_THROWS_AS(*c2, std::logic_error);
|
REQUIRE_THROWS_AS(*c2, std::logic_error);
|
||||||
REQUIRE_THROWS_AS(*as_const(c2), std::logic_error);
|
REQUIRE_THROWS_AS(*std::as_const(c2), std::logic_error);
|
||||||
|
|
||||||
c1.assign(1,2);
|
c1.assign(1,2);
|
||||||
|
|
||||||
REQUIRE(c1);
|
REQUIRE(c1);
|
||||||
REQUIRE(as_const(c1));
|
REQUIRE(std::as_const(c1));
|
||||||
REQUIRE(c2);
|
REQUIRE(c2);
|
||||||
REQUIRE(as_const(c2));
|
REQUIRE(std::as_const(c2));
|
||||||
|
|
||||||
REQUIRE(*c1 == position_c(1,2));
|
REQUIRE(*c1 == position_c(1,2));
|
||||||
REQUIRE(*as_const(c1) == position_c(1,2));
|
REQUIRE(*std::as_const(c1) == position_c(1,2));
|
||||||
REQUIRE(*c2 == position_c(1,2));
|
REQUIRE(*c2 == position_c(1,2));
|
||||||
REQUIRE(*as_const(c2) == position_c(1,2));
|
REQUIRE(*std::as_const(c2) == position_c(1,2));
|
||||||
|
|
||||||
REQUIRE(c1->x == 1);
|
REQUIRE(c1->x == 1);
|
||||||
REQUIRE(c1->y == 2);
|
REQUIRE(c1->y == 2);
|
||||||
REQUIRE(as_const(c1)->x == 1);
|
REQUIRE(std::as_const(c1)->x == 1);
|
||||||
REQUIRE(as_const(c1)->y == 2);
|
REQUIRE(std::as_const(c1)->y == 2);
|
||||||
|
|
||||||
REQUIRE(c2->x == 1);
|
REQUIRE(c2->x == 1);
|
||||||
REQUIRE(c2->y == 2);
|
REQUIRE(c2->y == 2);
|
||||||
REQUIRE(as_const(c2)->x == 1);
|
REQUIRE(std::as_const(c2)->x == 1);
|
||||||
REQUIRE(as_const(c2)->y == 2);
|
REQUIRE(std::as_const(c2)->y == 2);
|
||||||
|
|
||||||
c1.remove();
|
c1.remove();
|
||||||
|
|
||||||
REQUIRE_FALSE(c1);
|
REQUIRE_FALSE(c1);
|
||||||
REQUIRE_FALSE(as_const(c1));
|
REQUIRE_FALSE(std::as_const(c1));
|
||||||
REQUIRE_FALSE(c2);
|
REQUIRE_FALSE(c2);
|
||||||
REQUIRE_FALSE(as_const(c2));
|
REQUIRE_FALSE(std::as_const(c2));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ecs::registry w;
|
ecs::registry w;
|
||||||
|
|||||||
Reference in New Issue
Block a user