Compare commits

..

9 commits

12 changed files with 380 additions and 16 deletions

View file

@ -3,7 +3,7 @@ project(psemek)
cmake_policy(SET CMP0077 NEW)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
string(TOUPPER "${CMAKE_BUILD_TYPE}" PSEMEK_BUILD_TYPE)

View file

@ -0,0 +1,98 @@
#pragma once
#include <psemek/ecs/declare_uuid.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/tuple/size.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/variadic_seq_to_seq.hpp>
#include <format>
namespace psemek::ecs::detail
{
template <typename T>
struct define_component_type_helper;
template <typename T>
struct define_component_type_helper<void(T)>
{
using type = T;
};
}
#define psemek_ecs_define_component_detail_field_impl_2(value) \
typename ::psemek::ecs::detail::define_component_type_helper<void(BOOST_PP_TUPLE_ELEM(1, value))>::type BOOST_PP_TUPLE_ELEM(0, value);
#define psemek_ecs_define_component_detail_field_impl_3(value) \
typename ::psemek::ecs::detail::define_component_type_helper<void(BOOST_PP_TUPLE_ELEM(1, value))>::type BOOST_PP_TUPLE_ELEM(0, value) = BOOST_PP_TUPLE_ELEM(2, value);
#define psemek_ecs_define_component_detail_field_impl(value, size) \
BOOST_PP_CAT(psemek_ecs_define_component_detail_field_impl_, size)(value)
#define psemek_ecs_define_component_detail_field(r, data, value) \
psemek_ecs_define_component_detail_field_impl(value, BOOST_PP_TUPLE_SIZE(value))
#define psemek_ecs_define_component_detail_to_string_impl(fname) \
result += std::format(BOOST_PP_STRINGIZE(fname) ": {}\n", fname);
#define psemek_ecs_define_component_detail_to_string(r, data, value) \
psemek_ecs_define_component_detail_to_string_impl(BOOST_PP_TUPLE_ELEM(0, value))
#define psemek_ecs_define_component_fields_impl(fields) \
BOOST_PP_SEQ_FOR_EACH(psemek_ecs_define_component_detail_field, _, fields) \
std::string to_string() const { \
std::string result; \
BOOST_PP_SEQ_FOR_EACH(psemek_ecs_define_component_detail_to_string, _, fields) \
return result; \
}
/* Given a sequence of pairs or triples of the form
*
* (name, type)
* (name, type, default_value)
*
* emit a field with the specified type, name, and default_value (if present),
* and generate a to_string() const method that outputs each field on its own line
* using std::format.
*
* For example,
*
* psemek_ecs_define_component_fields(
* (position, float)
* (count, int, 0)
* )
*
* emits
*
* float position;
* int count = 0;
*
* and the corresponding to_string() implementation.
*
* If the type contains commas (e.g. if it is a template specialization with several
* parameters, like math::vector<float, 2>), the whole type must be put in parentheses, like
*
* psemek_ecs_define_component_fields(
* (position, (math::vector<float, 2>))
* )
*
* If the default value contains commas, the same applies. If the default value is an initializer
* list, it (quite regrettably) must be converted to an explicit value within parentheses, e.g.
*
* psemek_ecs_define_component_fields(
* (position, (math::vector<float, 2>), (math::vector{100.f, 100.f}))
* )
*/
#define psemek_ecs_define_component_fields(fields) \
psemek_ecs_define_component_fields_impl(BOOST_PP_VARIADIC_SEQ_TO_SEQ(fields))
#define psemek_ecs_define_component(cname, fields) \
struct cname { \
psemek_ecs_define_component_fields_impl(BOOST_PP_VARIADIC_SEQ_TO_SEQ(fields)) \
psemek_ecs_declare_uuid(BOOST_PP_STRINGIZE(cname)) \
};

View file

@ -29,7 +29,10 @@ namespace psemek::ecs
inline std::ostream & operator << (std::ostream & out, handle const & handle)
{
out << '(' << handle.id << ',' << handle.epoch << ')';
if (!handle)
out << "(none)";
else
out << '(' << handle.id << ',' << handle.epoch << ')';
return out;
}
@ -55,9 +58,13 @@ namespace std
return ctx.begin();
}
auto format(::psemek::ecs::handle const & handle, std::format_context & ctx) const
template <typename FormatContext>
auto format(::psemek::ecs::handle const & handle, FormatContext & ctx) const
{
return std::format_to(ctx.out(), "({},{})", handle.id, handle.epoch);
if (!handle)
return std::format_to(ctx.out(), "(none)");
else
return std::format_to(ctx.out(), "({},{})", handle.id, handle.epoch);
}
};

View file

@ -0,0 +1,36 @@
#pragma once
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/variadic/to_seq.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <format>
#define psemek_ecs_to_string_detail_apply_one(r, data, i, elem) \
BOOST_PP_IF(i, "\n", "") BOOST_PP_STRINGIZE(elem) ": {}"
/* Format a sequence of values into lines of the form "name: value".
* For example, having
*
* int a = 10;
* float x = 3.5f;
*
* The call
*
* psemek_ecs_to_string(a, b)
*
* returns a string
*
* "a = 10\nx = 3.5\n"
*/
#define psemek_ecs_to_string(...) \
std::format( \
BOOST_PP_SEQ_FOR_EACH_I( \
psemek_ecs_to_string_detail_apply_one, _, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__) \
), \
__VA_ARGS__ \
)
#define psemek_ecs_declare_to_string(...) \
std::string to_string() const { return psemek_ecs_to_string(__VA_ARGS__); }

View file

@ -4,6 +4,8 @@
#include <psemek/math/rotation.hpp>
#include <psemek/math/translation.hpp>
#include <format>
namespace psemek::math
{
@ -86,3 +88,24 @@ namespace psemek::math
};
}
namespace std
{
template <typename T, typename Char>
struct formatter<::psemek::math::trs<T, 3>, Char>
{
template <typename FormatContext>
constexpr auto parse(FormatContext & ctx) const
{
return ctx.begin();
}
template <typename FormatContext>
auto format(::psemek::math::trs<T, 3> const & trs, FormatContext & ctx) const
{
return std::format_to(ctx.out(), "translation: {}\nrotation: {}\nscale: {}", trs.translation, trs.rotation, trs.scale);
}
};
}

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include <limits>
#include <format>
#include <psemek/random/device.hpp>
@ -66,6 +67,9 @@ namespace psemek::random
read(stream, gen.inc_);
}
template <typename T, typename Char>
friend struct std::formatter;
private:
std::uint64_t state_ = 0;
std::uint64_t inc_ = 0;
@ -78,3 +82,21 @@ namespace psemek::random
};
}
namespace std
{
template <typename Char>
struct formatter<::psemek::random::generator, Char>
: formatter<std::uint64_t>
{
using base = formatter<std::uint64_t>;
template <typename FormatContext>
auto format(::psemek::random::generator const & rng, FormatContext & ctx) const
{
return std::format_to(ctx.out(), "({}, {})", rng.state_, rng.inc_);
}
};
}

View file

@ -4,6 +4,7 @@
#include <cstdint>
#include <concepts>
#include <limits>
#include <format>
namespace psemek::util
{
@ -363,6 +364,25 @@ namespace psemek::util
return FP::from_rep(((x.rep() - static_cast<rep_type>(1)) & ~fractional_mask) + fixed_point_one);
}
template <unsigned int I, unsigned int F, bool S>
constexpr fixed_point<I, F, S> trunc(fixed_point<I, F, S> x)
{
if (S && x < fixed_point<I, F, S>{0})
{
return ceil(x);
}
else
{
return floor(x);
}
}
template <unsigned int I, unsigned int F, bool S>
constexpr fixed_point<I, F, S> fract(fixed_point<I, F, S> x)
{
return x - trunc(x);
}
template <unsigned int I, unsigned int F, bool S>
constexpr fixed_point<I, F, S> round(fixed_point<I, F, S> x)
{
@ -436,3 +456,21 @@ namespace psemek::util
}
}
namespace std
{
template <unsigned int I, unsigned int F, bool S, typename Char>
struct formatter<::psemek::util::fixed_point<I, F, S>, Char>
: formatter<double>
{
using base = formatter<double>;
template <typename FormatContext>
auto format(::psemek::util::fixed_point<I, F, S> const & fp, FormatContext & ctx) const
{
return base::format(static_cast<double>(fp), ctx);
}
};
}

View file

@ -0,0 +1,118 @@
#pragma once
#include <format>
#include <memory>
#include <optional>
#include <variant>
namespace psemek::util
{
template <typename T>
concept has_format_impl = requires (T x, std::format_context & format_context)
{
format_impl(x, format_context);
};
}
namespace std
{
template <typename T, typename Char>
requires ::psemek::util::has_format_impl<T>
struct formatter<T, Char>
{
constexpr auto parse(std::format_parse_context & ctx) const
{
return ctx.begin();
}
template <typename FormatContext>
auto format(T const & x, FormatContext & ctx) const
{
return format_impl(x, ctx);
}
};
template <typename T, typename Char>
requires (!std::is_same_v<T, void> && !std::is_same_v<T, Char>)
struct formatter<T *, Char>
: formatter<void *>
{
using base = formatter<void *, Char>;
template <typename FormatContext>
auto format(T const * ptr, FormatContext & ctx) const
{
if (ptr)
return base::format((void *)ptr, ctx);
else
return std::format_to(ctx.out(), "(null)");
}
};
template <typename T, typename Char>
struct formatter<unique_ptr<T>, Char>
: formatter<void *>
{
using base = formatter<void *, Char>;
template <typename FormatContext>
auto format(unique_ptr<T> const & ptr, FormatContext & ctx) const
{
if (ptr)
return base::format((void *)ptr.get(), ctx);
else
return std::format_to(ctx.out(), "(null)");
}
};
template <typename T, typename Char>
struct formatter<shared_ptr<T>, Char>
: formatter<void *>
{
using base = formatter<void *, Char>;
template <typename FormatContext>
auto format(shared_ptr<T> const & ptr, FormatContext & ctx) const
{
if (ptr)
return base::format((void *)ptr.get(), ctx);
else
return std::format_to(ctx.out(), "(null)");
}
};
template <typename T, typename Char>
struct formatter<optional<T>, Char>
: formatter<T>
{
using base = formatter<T, Char>;
template <typename FormatContext>
auto format(optional<T> const & opt, FormatContext & ctx) const
{
if (opt)
return base::format(*opt, ctx);
else
return std::format_to(ctx.out(), "(none)");
}
};
template <typename ... Args, typename Char>
struct formatter<variant<Args...>, Char>
{
constexpr auto parse(std::format_parse_context & ctx) const
{
return ctx.begin();
}
template <typename FormatContext>
auto format(variant<Args...> const & v, FormatContext & ctx) const
{
return std::visit([&ctx](auto const & v){ return std::format_to(ctx.out(), "{}", v); }, v);
}
};
}

View file

@ -23,8 +23,8 @@ namespace psemek::util
struct function<R(Args...)>
{
private:
static constexpr std::size_t storage_size = sizeof(void*) * 3;
static constexpr std::size_t storage_align = alignof(void*);
static constexpr std::size_t storage_size = sizeof(void *) * 3;
static constexpr std::size_t storage_align = alignof(void *);
template <typename T>
struct uses_static_storage
@ -81,7 +81,7 @@ namespace psemek::util
void swap(function & other) noexcept;
private:
std::aligned_storage_t<storage_size, storage_align> storage_;
alignas(storage_align) std::byte storage_[storage_size];
struct vtable
{

View file

@ -1,6 +1,5 @@
#pragma once
#include <type_traits>
#include <memory>
#define psemek_declare_pimpl \
@ -23,7 +22,7 @@ namespace psemek::util::pimpl
template <typename Parent>
struct impl;
template <typename Parent, std::size_t Size>
template <typename Parent, std::size_t Size, std::size_t Align = alignof(std::max_align_t)>
struct in_place
{
protected:
@ -49,8 +48,7 @@ namespace psemek::util::pimpl
}
private:
typename std::aligned_storage<Size>::type storage_;
alignas(Align) std::byte storage_[Size];
};
template <typename Parent>

View file

@ -6,6 +6,8 @@
#include <cstdint>
#include <string_view>
#include <iostream>
#include <bit>
#include <format>
namespace psemek::util
{
@ -44,6 +46,8 @@ namespace psemek::util
return uuid;
}
// Make a version 3 UUID (name-based, using MD5)
// NB: the namespace UUID is omitted (empty)
// TODO: use SHA-1 or something like that?
constexpr uuid make_uuid(std::string_view str)
{
@ -76,4 +80,21 @@ namespace std
}
};
template <typename Char>
struct formatter<::psemek::util::uuid, Char>
{
constexpr auto parse(std::format_parse_context & ctx)
{
return ctx.begin();
}
template <typename FormatContext>
auto format(::psemek::util::uuid const & uuid, FormatContext & ctx) const
{
auto x0 = std::byteswap(uuid[0]);
auto x1 = std::byteswap(uuid[1]);
return std::format_to(ctx.out(), "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}", x0 & 0xffffffffull, (x0 >> 4) & 0xfffful, (x0 >> 6) & 0xfffful, x1 & 0xfffful, (x1 >> 2) & 0xffffffffffffull);
}
};
}

View file

@ -7,16 +7,19 @@ namespace psemek::util
std::ostream & operator << (std::ostream & os, uuid const & uuid)
{
auto x0 = std::byteswap(uuid[0]);
auto x1 = std::byteswap(uuid[1]);
os << std::hex << std::setfill('0') << std::left;
os << std::setw(8) << (uuid[0] & 0x00000000ffffffffull);
os << std::setw(8) << (x0 & 0xffffffffull);
os << std::setw(1) << '-';
os << std::setw(4) << ((uuid[0] & 0x0000ffff00000000ull) >> 32);
os << std::setw(4) << ((x0 >> 4) & 0xfffful);
os << std::setw(1) << '-';
os << std::setw(4) << ((uuid[0] & 0xffff000000000000ull) >> 48);
os << std::setw(4) << ((x0 >> 6) & 0xfffful);
os << std::setw(1) << '-';
os << std::setw(4) << (uuid[1] & 0x000000000000ffffull);
os << std::setw(4) << (x1 & 0xfffful);
os << std::setw(1) << '-';
os << std::setw(12) << ((uuid[1] & 0xffffffffffff0000ull) >> 16);
os << std::setw(12) << ((x1 >> 2) & 0xffffffffffffull);
return os;
}