Skip to content

Commit

Permalink
Merge pull request #32017 from jbytheway/point_type_safety
Browse files Browse the repository at this point in the history
Provide point types with coordinate system type-safety
  • Loading branch information
kevingranade authored Jul 7, 2020
2 parents ab2e206 + 9a0fdae commit 4db4b25
Show file tree
Hide file tree
Showing 7 changed files with 828 additions and 0 deletions.
369 changes: 369 additions & 0 deletions src/coordinates.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,375 @@
#include "enums.h"
#include "game_constants.h"
#include "point.h"
#include "debug.h"

namespace coords
{

enum class scale {
map_square,
submap,
overmap_terrain,
segment,
overmap,
vehicle
};

constexpr scale ms = scale::map_square;
constexpr scale sm = scale::submap;
constexpr scale omt = scale::overmap_terrain;
constexpr scale seg = scale::segment;
constexpr scale om = scale::overmap;

constexpr int map_squares_per( scale s )
{
static_assert( SEEX == SEEY, "we assume submaps are square" );
static_assert( OMAPX == OMAPY, "we assume overmaps are square" );

switch( s ) {
case scale::map_square:
return 1;
case scale::submap:
return SEEX;
case scale::overmap_terrain:
return SEEX * 2;
case scale::segment:
return SEG_SIZE * map_squares_per( scale::overmap_terrain );
case scale::overmap:
return OMAPX * map_squares_per( scale::overmap_terrain );
default:
constexpr_fatal( 0, "Requested scale of %d", s );
}
}

enum class origin {
relative, // this is a special origin that can be added to any other
abs, // the global absolute origin for the entire game
submap, // from corner of submap
overmap_terrain, // from corner of overmap_terrain
overmap, // from corner of overmap
};

constexpr origin origin_from_scale( scale s )
{
switch( s ) {
case scale::submap:
return origin::submap;
case scale::overmap_terrain:
return origin::overmap_terrain;
case scale::overmap:
return origin::overmap;
default:
constexpr_fatal( origin::abs, "Requested origin for scale %d", s );
}
}

// A generic coordinate-type-safe point.
// Point should be the underlying representation type (either point or
// tripoint).
// Scale and Origin define the coordinate system for the point.
template<typename Point, origin Origin, scale Scale>
class coord_point
{
public:
static constexpr int dimension = Point::dimension;

constexpr coord_point() = default;
explicit constexpr coord_point( const Point &p ) :
raw_( p )
{}
template<typename T>
constexpr coord_point( T x, T y ) : raw_( x, y ) {}
template<typename T>
constexpr coord_point( T x, T y, T z ) : raw_( x, y, z ) {}
template<typename T>
constexpr coord_point( const coord_point<point, Origin, Scale> &xy, T z ) :
raw_( xy.raw(), z )
{}

constexpr Point &raw() {
return raw_;
}
constexpr const Point &raw() const {
return raw_;
}

constexpr auto &x() {
return raw_.x;
}
constexpr auto x() const {
return raw_.x;
}
constexpr auto &y() {
return raw_.y;
}
constexpr auto y() const {
return raw_.y;
}
constexpr auto xy() const {
return coord_point<point, Origin, Scale>( raw_.xy() );
}
constexpr auto &z() {
return raw_.z;
}
constexpr auto z() const {
return raw_.z;
}

std::string to_string() const {
return raw_.to_string();
}

void serialize( JsonOut &jsout ) const {
raw().serialize( jsout );
}
void deserialize( JsonIn &jsin ) {
raw().deserialize( jsin );
}

coord_point &operator+=( const coord_point<Point, origin::relative, Scale> &r ) {
raw_ += r.raw();
return *this;
}

coord_point &operator-=( const coord_point<Point, origin::relative, Scale> &r ) {
raw_ -= r.raw();
return *this;
}

coord_point &operator+=( const point &r ) {
raw_ += r;
return *this;
}

coord_point &operator-=( const point &r ) {
raw_ -= r;
return *this;
}

coord_point &operator+=( const tripoint &r ) {
raw_ += r;
return *this;
}

coord_point &operator-=( const tripoint &r ) {
raw_ -= r;
return *this;
}

friend inline coord_point operator+( const coord_point &l, const point &r ) {
return coord_point( l.raw() + r );
}

friend inline coord_point operator+( const coord_point &l, const tripoint &r ) {
return coord_point( l.raw() + r );
}

friend inline coord_point operator-( const coord_point &l, const point &r ) {
return coord_point( l.raw() - r );
}

friend inline coord_point operator-( const coord_point &l, const tripoint &r ) {
return coord_point( l.raw() - r );
}
private:
Point raw_;
};

template<typename Point, origin Origin, scale Scale>
constexpr inline bool operator==( const coord_point<Point, Origin, Scale> &l,
const coord_point<Point, Origin, Scale> &r )
{
return l.raw() == r.raw();
}

template<typename Point, origin Origin, scale Scale>
constexpr inline bool operator!=( const coord_point<Point, Origin, Scale> &l,
const coord_point<Point, Origin, Scale> &r )
{
return l.raw() != r.raw();
}

template<typename Point, origin Origin, scale Scale>
constexpr inline bool operator<( const coord_point<Point, Origin, Scale> &l,
const coord_point<Point, Origin, Scale> &r )
{
return l.raw() < r.raw();
}

template<typename PointL, typename PointR, origin OriginL, scale Scale>
constexpr inline auto operator+(
const coord_point<PointL, OriginL, Scale> &l,
const coord_point<PointR, origin::relative, Scale> &r )
{
using PointResult = decltype( PointL() + PointR() );
return coord_point<PointResult, OriginL, Scale>( l.raw() + r.raw() );
}

template < typename PointL, typename PointR, origin OriginR, scale Scale,
// enable_if to prevent ambiguity with above when both args are
// relative
typename = std::enable_if_t < OriginR != origin::relative >>
constexpr inline auto operator+(
const coord_point<PointL, origin::relative, Scale> &l,
const coord_point<PointR, OriginR, Scale> &r )
{
using PointResult = decltype( PointL() + PointR() );
return coord_point<PointResult, OriginR, Scale>( l.raw() + r.raw() );
}

template<typename PointL, typename PointR, origin OriginL, scale Scale>
constexpr inline auto operator-(
const coord_point<PointL, OriginL, Scale> &l,
const coord_point<PointR, origin::relative, Scale> &r )
{
using PointResult = decltype( PointL() + PointR() );
return coord_point<PointResult, OriginL, Scale>( l.raw() - r.raw() );
}

template < typename PointL, typename PointR, origin Origin, scale Scale,
// enable_if to prevent ambiguity with above when both args are
// relative
typename = std::enable_if_t < Origin != origin::relative >>
constexpr inline auto operator-(
const coord_point<PointL, Origin, Scale> &l,
const coord_point<PointR, Origin, Scale> &r )
{
using PointResult = decltype( PointL() + PointR() );
return coord_point<PointResult, origin::relative, Scale>( l.raw() - r.raw() );
}

// Only relative points can be multiplied by a constant
template<typename Point, scale Scale>
constexpr inline coord_point<Point, origin::relative, Scale> operator*(
int l, const coord_point<Point, origin::relative, Scale> &r )
{
return coord_point<Point, origin::relative, Scale>( l * r.raw() );
}

template<typename Point, scale Scale>
constexpr inline coord_point<Point, origin::relative, Scale> operator*(
const coord_point<Point, origin::relative, Scale> &r, int l )
{
return coord_point<Point, origin::relative, Scale>( r.raw() * l );
}

template<typename Point, origin Origin, scale Scale>
inline std::ostream &operator<<( std::ostream &os, const coord_point<Point, Origin, Scale> &p )
{
return os << p.raw();
}

template<int ScaleUp, int ScaleDown, scale ResultScale>
struct project_to_impl;

template<int ScaleUp, scale ResultScale>
struct project_to_impl<ScaleUp, 0, ResultScale> {
template<typename Point, origin Origin, scale SourceScale>
coord_point<Point, Origin, ResultScale> operator()(
const coord_point<Point, Origin, SourceScale> &src ) {
return coord_point<Point, Origin, ResultScale>( multiply_xy( src.raw(), ScaleUp ) );
}
};

template<int ScaleDown, scale ResultScale>
struct project_to_impl<0, ScaleDown, ResultScale> {
template<typename Point, origin Origin, scale SourceScale>
coord_point<Point, Origin, ResultScale> operator()(
const coord_point<Point, Origin, SourceScale> &src ) {
return coord_point<Point, Origin, ResultScale>(
divide_xy_round_to_minus_infinity( src.raw(), ScaleDown ) );
}
};

template<scale ResultScale, typename Point, origin Origin, scale SourceScale>
inline coord_point<Point, Origin, ResultScale> project_to(
const coord_point<Point, Origin, SourceScale> &src )
{
constexpr int scale_down = map_squares_per( ResultScale ) / map_squares_per( SourceScale );
constexpr int scale_up = map_squares_per( SourceScale ) / map_squares_per( ResultScale );
return project_to_impl<scale_up, scale_down, ResultScale>()( src );
}

template<typename Point, origin Origin, scale CoarseScale, scale FineScale>
struct quotient_remainder {
constexpr static origin RemainderOrigin = origin_from_scale( CoarseScale );
using quotient_type = coord_point<Point, Origin, CoarseScale>;
quotient_type quotient;
using remainder_type = coord_point<Point, RemainderOrigin, FineScale>;
remainder_type remainder;

// For assigning to std::tie( q, r );
operator std::tuple<quotient_type &, remainder_type &>() {
return std::tie( quotient, remainder );
}
};

template<scale ResultScale, typename Point, origin Origin, scale SourceScale>
inline quotient_remainder<Point, Origin, ResultScale, SourceScale> project_remain(
const coord_point<Point, Origin, SourceScale> &src )
{
constexpr int ScaleDown = map_squares_per( ResultScale ) / map_squares_per( SourceScale );
static_assert( ScaleDown > 0, "You can only project to coarser coordinate systems" );
constexpr static origin RemainderOrigin = origin_from_scale( ResultScale );
coord_point<Point, Origin, ResultScale> quotient(
divide_xy_round_to_minus_infinity( src.raw(), ScaleDown ) );
coord_point<Point, RemainderOrigin, SourceScale> remainder(
src.raw() - quotient.raw() * ScaleDown );

return { quotient, remainder };
}

} // namespace coords

namespace std
{

template<typename Point, coords::origin Origin, coords::scale Scale>
struct hash<coords::coord_point<Point, Origin, Scale>> {
std::size_t operator()( const coords::coord_point<Point, Origin, Scale> &p ) const {
const hash<Point> h{};
return h( p.raw() );
}
};

} // namespace std

/** Typedefs for point types with coordinate mnemonics.
*
* Each name is of the form (tri)point_<origin>_<scale> where <origin> tells you the
* context in which the point has meaning, and <scale> tells you what one unit
* of the point means.
*
* For example:
* point_omt_ms is the position of a map square within an overmap terrain.
* tripoint_rel_sm is a relative tripoint submap offset.
*/
/*@{*/
using point_rel_ms = coords::coord_point<point, coords::origin::relative, coords::ms>;
using point_abs_ms = coords::coord_point<point, coords::origin::abs, coords::ms>;
using point_sm_ms = coords::coord_point<point, coords::origin::submap, coords::ms>;
using point_omt_ms = coords::coord_point<point, coords::origin::overmap_terrain, coords::ms>;
using point_abs_sm = coords::coord_point<point, coords::origin::abs, coords::sm>;
using point_omt_sm = coords::coord_point<point, coords::origin::overmap_terrain, coords::sm>;
using point_om_sm = coords::coord_point<point, coords::origin::overmap, coords::sm>;
using point_abs_omt = coords::coord_point<point, coords::origin::abs, coords::omt>;
using point_om_omt = coords::coord_point<point, coords::origin::overmap, coords::omt>;
using point_abs_seg = coords::coord_point<point, coords::origin::abs, coords::seg>;
using point_abs_om = coords::coord_point<point, coords::origin::abs, coords::om>;

using tripoint_rel_ms = coords::coord_point<tripoint, coords::origin::relative, coords::ms>;
using tripoint_abs_ms = coords::coord_point<tripoint, coords::origin::abs, coords::ms>;
using tripoint_sm_ms = coords::coord_point<tripoint, coords::origin::submap, coords::ms>;
using tripoint_omt_ms = coords::coord_point<tripoint, coords::origin::overmap_terrain, coords::ms>;
using tripoint_abs_sm = coords::coord_point<tripoint, coords::origin::abs, coords::sm>;
using tripoint_abs_omt = coords::coord_point<tripoint, coords::origin::abs, coords::omt>;
using tripoint_abs_seg = coords::coord_point<tripoint, coords::origin::abs, coords::seg>;
using tripoint_abs_om = coords::coord_point<tripoint, coords::origin::abs, coords::om>;
/*@}*/

using coords::project_to;
using coords::project_remain;

/* find appropriate subdivided coordinates for absolute tile coordinate.
* This is less obvious than one might think, for negative coordinates, so this
Expand Down
15 changes: 15 additions & 0 deletions src/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ inline void realDebugmsg( const char *const filename, const char *const line,
std::forward<Args>( args )... ) );
}

// A fatal error for use in constexpr functions
// This exists for compatibility reasons. On gcc 5.3 we need a
// different implementation that is messier.
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371
// Pass a placeholder return value to be used on gcc 5.3 (it won't
// actually be returned, it's just needed for the type), and then
// args as if to debugmsg for the remaining args.
#if defined(__GNUC__) && __GNUC__ < 6
#define constexpr_fatal(ret, ...) \
do { return false ? ( ret ) : ( abort(), ( ret ) ); } while(false)
#else
#define constexpr_fatal(ret, ...) \
do { debugmsg(__VA_ARGS__); abort(); } while(false)
#endif

/**
* Used to generate game report information.
*/
Expand Down
Loading

0 comments on commit 4db4b25

Please sign in to comment.