Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,96 @@
// The cubic bezier easing implementation is a C++ version of the following javascript code:
/**
* https://github.com/gre/bezier-easing
* BezierEasing - use bezier curve for transition easing function
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
*/
#include "BezierEasing.hpp"
#include <cmath>
BezierEasing BezierEasing::linear{0.0, 1.0, 0.0, 1.0};
BezierEasing::BezierEasing(Eigen::Vector4f c):
BezierEasing(c.x(), c.y(), c.z(), c.w()) {}
BezierEasing::BezierEasing(float x1, float y1, float x2, float y2):
mX1(x1),
mY1(y1),
mX2(x2),
mY2(y2)
{
for(size_t i = 0; i < KSPLINE_TABLE_SIZE; ++i)
sampleValues[i] = calc_bezier(i * KSAMPLE_STEP_SIZE, mX1, mX2);
}
float BezierEasing::A(float aA1, float aA2) const {
return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}
float BezierEasing::B(float aA1, float aA2) const {
return 3.0 * aA2 - 6.0 * aA1;
}
float BezierEasing::C(float aA1) const {
return 3.0 * aA1;
}
float BezierEasing::calc_bezier(float aT, float aA1, float aA2) const {
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
}
float BezierEasing::get_slope(float aT, float aA1, float aA2) const {
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
float BezierEasing::binary_subdivide(float aX, float aA, float aB) const {
float currentX = 0;
float currentT = 0;
unsigned i = 0;
do {
currentT = aA + (aB - aA) / 2;
currentX = calc_bezier(currentT, mX1, mX2) - aX;
if(currentX > 0)
aB = currentT;
else
aA = currentT;
} while(std::abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
return currentT;
}
float BezierEasing::newton_raphson_iterate(float aX, float aGuessT) const {
for(unsigned i = 0; i < NEWTON_ITERATIONS; i++) {
float currentSlope = get_slope(aGuessT, mX1, mX2);
if(currentSlope == 0)
return aGuessT;
float currentX = calc_bezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
float BezierEasing::get_t_for_x(float aX) const {
float intervalStart = 0.0;
unsigned currentSample = 1;
for(; currentSample != KSPLINE_TABLE_LAST && sampleValues[currentSample] <= aX; ++currentSample)
intervalStart += KSAMPLE_STEP_SIZE;
--currentSample;
float dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
float guessForT = intervalStart + dist * KSAMPLE_STEP_SIZE;
float initialSlope = get_slope(guessForT, mX1, mX2);
if(initialSlope >= NEWTON_MIN_SLOPE)
return newton_raphson_iterate(aX, guessForT);
else if(initialSlope == 0.0)
return guessForT;
else
return binary_subdivide(aX, intervalStart, intervalStart + KSAMPLE_STEP_SIZE);
}
float BezierEasing::operator() (float x) const {
if(x == 0.0 || x == 1.0 || (mX1 == mY1 && mX2 == mY2))
return x;
return calc_bezier(get_t_for_x(x), mY1, mY2);
}

View file

@ -0,0 +1,42 @@
// The cubic bezier easing implementation is a C++ version of the following javascript code:
/**
* https://github.com/gre/bezier-easing
* BezierEasing - use bezier curve for transition easing function
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
*/
#pragma once
#include <array>
#include <Eigen/Dense>
class BezierEasing {
public:
BezierEasing(Eigen::Vector4f c);
BezierEasing(float x1, float y1, float x2, float y2);
float operator() (float x) const;
static BezierEasing linear;
private:
static constexpr unsigned NEWTON_ITERATIONS = 4;
static constexpr float NEWTON_MIN_SLOPE = 0.001;
static constexpr float SUBDIVISION_PRECISION = 0.0000001;
static constexpr unsigned SUBDIVISION_MAX_ITERATIONS = 10;
static constexpr unsigned KSPLINE_TABLE_SIZE = 11;
static constexpr unsigned KSPLINE_TABLE_LAST = KSPLINE_TABLE_SIZE - 1;
static constexpr float KSAMPLE_STEP_SIZE = 1.0 / (KSPLINE_TABLE_SIZE - 1.0);
float A(float aA1, float aA2) const;
float B(float aA1, float aA2) const;
float C(float aA1) const;
float calc_bezier(float aT, float aA1, float aA2) const;
float get_slope(float aT, float aA1, float aA2) const;
float binary_subdivide(float aX, float aA, float aB) const;
float newton_raphson_iterate(float aX, float aGuessT) const;
float get_t_for_x(float aX) const;
float mX1;
float mY1;
float mX2;
float mY2;
std::array<float, KSPLINE_TABLE_SIZE> sampleValues;
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <clay.h>
#ifdef IS_CLIENT
#include <include/core/SkPoint.h>
template <typename S> S convert_vec2(const SkPoint& t) {
return S{t.fX, t.fY};
}
#endif
template <typename S, typename T> S convert_vec2(const T& t) {
return S{t[0], t[1]};
}
template <typename S, typename T> S convert_vec3(const T& t) {
return S{t[0], t[1], t[2]};
}
template <typename S, typename T> S convert_vec4(const T& t) {
return S{t[0], t[1], t[2], t[3]};
}
template <typename S> S convert_vec4(const Clay_Color& t) {
return S{t.r, t.g, t.b, t.a};
}
template <typename S> S convert_vec2(const Clay_Dimensions& t) {
return S{t.width, t.height};
}

View file

@ -0,0 +1,346 @@
#pragma once
#include "Helpers/StringHelpers.hpp"
#include <cmath>
#include <cstdint>
#include <string>
#include <Eigen/Core>
#include <boost/multiprecision/cpp_int.hpp>
#include <cereal/types/vector.hpp>
namespace FixedPoint {
template <typename T> T abs(const T& a) {
if(a < T(0))
return -a;
return a;
}
template <typename T> T sqrt(const T& a) {
return T(boost::multiprecision::sqrt(a.get_underlying_val() << a.bit_fraction_count()), true);
}
template <typename T> T lerp(const T& a, const T& b, const T& t) {
return a + t * (b - a);
}
template <typename T> T lerp_double(const T& a, const T& b, double t) {
if(t == 0.0)
return a;
else
return a + (b - a) / T(1.0 / t);
}
// Gets highest bit in UNDERLYING VALUE, not the integer portion of the number
template <typename T> int to_highest_bit(const T& a) {
typename T::UnderlyingType underNum = a.get_underlying_val();
if(underNum == 0)
return 0;
int b = 0;
while(underNum >>= 1)
b++;
return b;
}
// https://github.com/Koishi-Satori/EirinFixed/blob/main/include/eirin/math.hpp
template <typename T> T log2(const T& a) {
typename T::UnderlyingType b(typename T::UnderlyingType(1u) << (a.bit_fraction_count() - 1));
typename T::UnderlyingType y(0);
typename T::UnderlyingType x = a.get_underlying_val();
if(a < T(0))
throw std::runtime_error("[FixedPoint::log2] Number outside domain");
while(x < (typename T::UnderlyingType(1u) << a.bit_fraction_count()))
{
x <<= 1;
y -= (typename T::UnderlyingType(1u)) << a.bit_fraction_count();
}
while(x >= (typename T::UnderlyingType(2u) << a.bit_fraction_count()))
{
x >>= 1;
y += (typename T::UnderlyingType(1u)) << a.bit_fraction_count();
}
typename T::UnderlyingType z = x;
for(int i = 0; i < a.bit_fraction_count(); ++i)
{
z = (z * z) >> a.bit_fraction_count();
if(z >= (typename T::UnderlyingType(2u) << a.bit_fraction_count()))
{
z >>= 1;
y += b;
}
b = b >> 1;
}
return T(y, true);
}
// https://github.com/MikeLankamp/fpm/blob/master/include/fpm/math.hpp
template <typename T> T exp2(T x) {
if(x < T(0))
return T(1) / exp2(-x);
typename T::UnderlyingType x_int = x.get_underlying_val() >> x.bit_fraction_count();
x -= T(x_int, false);
assert(x >= T(0) && x < T(1));
T fA(1.8964611454333148e-3);
T fB(8.9428289841091295e-3);
T fC(5.5866246304520701e-2);
T fD(2.4013971109076949e-1);
T fE(6.9315475247516736e-1);
T fF(9.9999989311082668e-1);
return T(typename T::UnderlyingType(1) << static_cast<int>(x_int), false) * (((((fA * x + fB) * x + fC) * x + fD) * x + fE) * x + fF);
}
template <typename T, int bitFractionCount> class Number {
public:
typedef T UnderlyingType;
static int bit_fraction_count() {
return bitFractionCount;
}
Number():
val(0) {}
explicit Number(double initVal) {
val = T(initVal * std::pow(2, bitFractionCount));
}
explicit Number(float initVal) {
val = T(initVal * std::pow(2, bitFractionCount));
}
explicit Number(int64_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(int32_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(int16_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(uint64_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(uint32_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(uint16_t initVal) {
val = T(initVal) << bitFractionCount;
}
explicit Number(const std::string& initVal) {
val = T(initVal) << bitFractionCount;
}
template <typename Archive> void load(Archive& a) {
bool isNegative;
std::vector<uint8_t> v;
a(isNegative, v);
boost::multiprecision::import_bits(val, v.begin(), v.end(), 8, true);
if(isNegative)
val = -val;
}
template <typename Archive> void save(Archive& a) const {
bool isNegative = val < 0;
std::vector<uint8_t> v;
T absVal = boost::multiprecision::abs(val);
boost::multiprecision::export_bits(absVal, std::back_inserter(v), 8, true);
a(isNegative, v);
}
Number operator-(const Number& other) const {
return Number(val - other.val, true);
}
Number operator-() const {
return Number(-val, true);
}
Number operator/(const Number& other) const {
return Number((val << bitFractionCount) / other.val, true);
}
Number operator*(const Number& other) const {
return Number((val * other.val) >> bitFractionCount, true);
}
template <int otherDecimalCount> Number multiply_different_precision(const Number<T, otherDecimalCount>& other) const {
return Number(((val * other.get_underlying_val()) >> other.bit_fraction_count()), true);
}
Number multiply_double(double a) const {
if(std::fabs(a) < 1.0)
a = 1.0 / a;
return (*this) * Number(a);
}
Number divide_double(double a) const {
if(std::fabs(a) < 1.0)
a = 1.0 / a;
return (*this) / Number(a);
}
const T& get_underlying_val() const {
return val;
}
Number operator+(const Number& other) const {
return Number(val + other.val, true);
}
Number operator%(const Number& other) const {
return Number(val % other.val, true);
}
Number& operator+=(const Number& other) {
val += other.val;
return *this;
}
Number& operator-=(const Number& other) {
val -= other.val;
return *this;
}
Number& operator*=(const Number& other) {
val *= other.val;
val >>= bitFractionCount;
return *this;
}
Number& operator/=(const Number& other) {
val <<= bitFractionCount;
val /= other.val;
return *this;
}
Number& operator%=(const Number& other) {
val %= other.val;
return *this;
}
explicit operator float() const {
return static_cast<float>(val) / std::pow(2, bitFractionCount);
}
explicit operator double() const {
return static_cast<double>(val) / std::pow(2, bitFractionCount);
}
explicit operator int64_t() const {
return static_cast<int64_t>(val >> bitFractionCount);
}
explicit operator uint64_t() const {
return static_cast<uint64_t>(val >> bitFractionCount);
}
std::string to_underlying_str() const {
return val.str();
}
void from_underlying_str(const std::string& str) {
val = T(str);
}
std::string display_int_str(size_t scientificNotationDigits) const {
if(scientificNotationDigits <= 0) {
T shiftedVal = val >> bitFractionCount;
return shiftedVal.str();
}
else {
T shiftedVal = val >> bitFractionCount;
std::string a = shiftedVal.str();
if(a.size() > (scientificNotationDigits + 2)) {
bool isNegative = val < 0;
size_t exponent = a.size() - (isNegative ? 2 : 1);
a = a.substr(0, scientificNotationDigits + (isNegative ? 2 : 1));
a.insert(a.begin() + (isNegative ? 2 : 1), '.');
a += "e" + std::to_string(exponent);
}
return a;
}
}
friend std::ostream& operator<<(std::ostream& os, Number const& m) {
return os << static_cast<double>(m);
}
bool operator<(const Number& other) const {
return val < other.val;
}
bool operator>(const Number& other) const {
return val > other.val;
}
bool operator>=(const Number& other) const {
return val >= other.val;
}
bool operator<=(const Number& other) const {
return val <= other.val;
}
bool operator==(const Number& other) const {
return val == other.val;
}
Number operator>>(int shiftNum) const {
return Number(val >> shiftNum, true);
}
Number operator<<(int shiftNum) const {
return Number(val << shiftNum, true);
}
Number(const T& initVal, bool isUnderlyingVal):
val(initVal)
{
if(isUnderlyingVal)
val = initVal;
else
val = initVal << bitFractionCount;
}
private:
T val;
};
}
typedef FixedPoint::Number<boost::multiprecision::cpp_int, 32> WorldScalar;
namespace Eigen {
template<> struct NumTraits<WorldScalar> : GenericNumTraits<WorldScalar>
{
typedef WorldScalar Real;
typedef WorldScalar NonInteger;
typedef WorldScalar Nested;
static inline Real epsilon() { return Real(0); }
static inline Real dummy_precision() { return Real(0); }
static inline int digits10() { return 0; }
enum {
IsInteger = 0,
IsSigned = 1,
IsComplex = 0,
RequireInitialization = 1,
ReadCost = 6,
AddCost = 150,
MulCost = 100
};
};
}

View file

@ -0,0 +1,5 @@
#include "Hashes.hpp"
bool operator <(const Vector2d& a, const Vector2d& b) {
return ((a.x() < b.x()) || ((a.x() == b.x()) && (a.y() < b.y())));
}

View file

@ -0,0 +1,64 @@
#pragma once
#include <utility>
#include <Eigen/Dense>
#include "Serializers.hpp"
using namespace Eigen;
typedef Vector<uint32_t, 2> Vector2ui32;
// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/54728293#54728293
template <typename T, typename... Rest>
void hash_combine(uint64_t& seed, const T& v, const Rest&... rest) {
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hash_combine(seed, rest), ...);
}
template <> struct std::hash<Vector2i> {
size_t operator()(const Vector2i& v) const
{
uint64_t h = 0;
hash_combine(h, v.x(), v.y());
return h;
}
};
template <> struct std::hash<Vector2ui32> {
size_t operator()(const Vector2ui32& v) const
{
uint64_t h = 0;
hash_combine(h, v.x(), v.y());
return h;
}
};
template <> struct std::hash<Vector2f> {
size_t operator()(const Vector2f& v) const
{
uint64_t h = 0;
hash_combine(h, v.x(), v.y());
return h;
}
};
template <> struct std::hash<std::array<Vector2f, 2>> {
size_t operator()(const std::array<Vector2f, 2>& v) const
{
uint64_t h = 0;
hash_combine(h, v[0], v[1]);
return h;
}
};
template <> struct std::hash<std::array<float, 4>> {
size_t operator()(const std::array<float, 4>& v) const
{
uint64_t h = 0;
hash_combine(h, v[0], v[1], v[2], v[3]);
return h;
}
};
bool operator <(const Vector2f& a, const Vector2f& b);

109
include/Helpers/HsvRgb.hpp Normal file
View file

@ -0,0 +1,109 @@
#pragma once
#include <cmath>
// https://stackoverflow.com/a/6930407
template <typename S, typename T> S rgb_to_hsv(const T& in)
{
S out;
float min, max, delta;
min = in[0] < in[1] ? in[0] : in[1];
min = min < in[2] ? min : in[2];
max = in[0] > in[1] ? in[0] : in[1];
max = max > in[2] ? max : in[2];
out[2] = max; // v
delta = max - min;
if(delta < 0.000001f)
{
out[1] = 0;
out[0] = 0; // undefined, maybe nan?
return out;
}
if(max > 0.0f) { // NOTE: if Max is == 0, this divide would cause a crash
out[1] = (delta / max); // s
} else {
// if max is 0, then r = g = b = 0
// s = 0, h is undefined
out[1] = 0.0f;
out[0] = NAN; // its now undefined
return out;
}
if(in[0] >= max) // > is bogus, just keeps compiler happy
out[0] = (in[1] - in[2]) / delta; // between yellow & magenta
else {
if(in[1] >= max)
out[0] = 2.0 + ( in[2] - in[0] ) / delta; // between cyan & yellow
else
out[0] = 4.0 + ( in[0] - in[1] ) / delta; // between magenta & cyan
}
out[0] *= 60.0f; // degrees
if(out[0] < 0.0f)
out[0] += 360.0f;
return out;
}
template <typename S, typename T> S hsv_to_rgb(const T& in)
{
float hh, p, q, t, ff;
long i;
S out;
if(in[1] <= 0.0f) { // < is bogus, just shuts up warning
out[0] = in[2];
out[1] = in[2];
out[2] = in[2];
return out;
}
hh = in[0];
if(hh >= 360.0f)
hh = 0.0f;
hh /= 60.0f;
i = (long)hh;
ff = hh - i;
p = in[2] * (1.0f - in[1]);
q = in[2] * (1.0f - (in[1] * ff));
t = in[2] * (1.0f - (in[1] * (1.0f - ff)));
switch(i) {
case 0:
out[0] = in[2];
out[1] = t;
out[2] = p;
break;
case 1:
out[0] = q;
out[1] = in[2];
out[2] = p;
break;
case 2:
out[0] = p;
out[1] = in[2];
out[2] = t;
break;
case 3:
out[0] = p;
out[1] = q;
out[2] = in[2];
break;
case 4:
out[0] = t;
out[1] = p;
out[2] = in[2];
break;
case 5:
default:
out[0] = in[2];
out[1] = p;
out[2] = q;
break;
}
return out;
}

View file

@ -0,0 +1,22 @@
#include "Logger.hpp"
#include <stdexcept>
Logger Logger::global;
Logger& Logger::get() {
return global;
}
void Logger::add_log(const std::string& log, const std::function<void(const std::string&)> callback) {
std::scoped_lock logLock(logMutex);
logs[log] = callback;
}
void Logger::log(const std::string& log, const std::string& text) {
std::scoped_lock logLock(logMutex);
auto it = logs.find(log);
if(it == logs.end())
throw std::runtime_error("[Logger::log] Log " + log + " does not exist to log text: " + text);
else
it->second(text);
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <functional>
#include <mutex>
class Logger {
public:
static Logger& get();
void add_log(const std::string& log, const std::function<void(const std::string&)> callback);
void log(const std::string& log, const std::string& text);
private:
static Logger global;
std::mutex logMutex;
std::unordered_map<std::string, std::function<void(const std::string&)>> logs;
};

View file

@ -0,0 +1,271 @@
#pragma once
#include <cmath>
#include <Eigen/Dense>
#include <array>
#include <limits>
#include <optional>
#include <utility>
#include <iostream>
#include <numbers>
using namespace Eigen;
template <typename T> T color_mul_alpha(const T& c, float a) {
return T{c[0], c[1], c[2], c[3] * a};
}
template <typename T> T circular_mod(T a, T b) {
if(a >= 0)
return a % b;
else
return b - (std::abs(a) % b);
}
template <typename T> T circular_fmod(T a, T b) {
if(a >= 0)
return std::fmod(a, b);
else
return b - std::fmod(a, b);
}
template <typename T, typename MultipleType> T round_to_multiple(T a, MultipleType multiple) {
T toRet = round(a / multiple) * multiple;
if((toRet == +0.0 || toRet == -0.0) && std::signbit(toRet))
toRet = 0.0;
return toRet;
}
template <typename T, typename MultipleType> T round_vec_to_multiple(T a, MultipleType multiple) {
for(size_t i = 0; i < static_cast<size_t>(a.size()); i++)
a[i] = round_to_multiple(a[i], multiple);
return a;
}
template <typename T> Matrix<T, 2, 1> perpendicular_vec2(const Matrix<T, 2, 1>& vec) {
return Matrix<T, 2, 1>{-vec.y(), vec.x()};
}
template <typename T> T dist_point_line_segment(const Matrix<T, 2, 1>& point, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
T lenSqrd = (lineP1 - lineP2).squaredNorm();
if(lenSqrd == 0)
return (point - lineP1).norm();
T t = std::max<T>(0, std::min<T>(1, (point - lineP1).dot(lineP2 - lineP1) / lenSqrd));
return (point - (lineP1 + t * (lineP2 - lineP1))).norm();
}
template <typename T> bool collision_circle_line_segment(const Matrix<T, 2, 1>& circleCenter, T circleRadius, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
return dist_point_line_segment(circleCenter, lineP1, lineP2) <= circleRadius;
}
// https://stackoverflow.com/a/53896859
template <typename T> bool is_collision_ray_line_segment(const Vector<T, 2>& rayOrigin, const Vector<T, 2>& rayDirection, const Vector<T, 2>& point1, const Vector<T, 2>& point2) {
Vector<T, 2> v1 = rayOrigin - point1;
Vector<T, 2> v2 = point2 - point1;
Vector<T, 2> v3 = Vector<T, 2>{-rayDirection.y(), rayDirection.x()};
Vector<T, 3> v2three{v2.x(), v2.y(), 0.0};
Vector<T, 3> v1three{v1.x(), v1.y(), 0.0};
T dot = v2.dot(v3);
if (std::abs(dot) < 0.000001)
return false;
T t1 = v2three.cross(v1three) / dot;
T t2 = v1.dot(v3) / dot;
return (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0));
}
// https://www.geeksforgeeks.org/program-for-point-of-intersection-of-two-lines/
template <typename T> Vector<T, 2> line_line_intersection(const Vector<T, 2>& a, const Vector<T, 2>& b, const Vector<T, 2>& c, const Vector<T, 2>& d) {
T a1 = b.y() - a.y();
T b1 = a.x() - b.x();
T c1 = a1*(a.x()) + b1*(a.y());
T a2 = d.y() - c.y();
T b2 = c.x() - d.x();
T c2 = a2*(c.x())+ b2*(c.y());
T determinant = a1*b2 - a2*b1;
T x = (b2*c1 - b1*c2)/determinant;
T y = (a1*c2 - a2*c1)/determinant;
if(std::fabs(determinant) < 0.0000001f)
return Vector<T, 2>{std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
return Vector<T, 2>{x, y};
}
template <typename T> Vector<T, 2> project_point_on_vec(const Vector<T, 2>& point, const Vector<T, 2> unnormalizedVec) {
return ((point.dot(unnormalizedVec)) / unnormalizedVec.dot(unnormalizedVec)) * unnormalizedVec;
}
template <typename T> Vector<T, 2> project_point_on_normalized_vec(const Vector<T, 2>& point, const Vector<T, 2> normalizedVec) {
return point.dot(normalizedVec) * normalizedVec;
}
// https://stackoverflow.com/a/61342198
template <typename T> Vector<T, 2> project_point_on_line(const Vector<T, 2>& p, const Vector<T, 2>& b, const Vector<T, 2>& a) {
Vector<T, 2> ap = p - a;
Vector<T, 2> ab = b - a;
T t = ap.dot(ab) / ab.dot(ab);
return a + t * ab;
}
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
template <typename T> Vector<T, 2> line_line_intersection_inaccurate(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
T x1 = a.x(); T x2 = b.x(); T x3 = c.x(); T x4 = d.x();
T y1 = a.y(); T y2 = b.y(); T y3 = c.y(); T y4 = d.y();
T p1 = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
T p2 = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
return Vector<T, 2>{p1, p2};
}
// https://www.reddit.com/r/algorithms/comments/9moad4/what_is_the_simplest_to_implement_line_segment/
// Taken from https://github.com/vlecomte/cp-geo
template <typename T> T cross_vals_vec(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b) {
return a.x() * b.y() - a.y() * b.x();
}
template <typename T> T orient_vals_vec(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c) {
return cross_vals_vec((b - a).eval(), (c - a).eval());
}
template <typename T> bool is_collision_line_segment_line_segment(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
T oa = orient_vals_vec(c, d, a);
T ob = orient_vals_vec(c, d, b);
T oc = orient_vals_vec(a, b, c);
T od = orient_vals_vec(a, b, d);
return (oa * ob < T(0) && oc * od < T(0));
}
template <typename T> std::optional<Vector2d> collision_line_segment_line_segment_pos(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
T oa = orient_vals_vec(c, d, a);
T ob = orient_vals_vec(c, d, b);
T oc = orient_vals_vec(a, b, c);
T od = orient_vals_vec(a, b, d);
if(oa * ob < 0 && oc * od < 0)
return std::optional<Vector2d>((a * ob - b * oa) / (ob - oa));
return std::nullopt;
}
template <typename T> bool is_collision_aabb_line_segment(const AlignedBox<T, 2>& aabb, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
return aabb.contains(lineP1) ||
aabb.contains(lineP2) ||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::TopRight)) ||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopRight), aabb.corner(AlignedBox<T, 2>::BottomRight)) ||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::BottomLeft), aabb.corner(AlignedBox<T, 2>::BottomRight)) ||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::BottomLeft));
}
template <typename T> T vec_distance(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
return (v1 - v2).norm();
}
template <typename T> T vec_length(const Vector<T, 2>& v) {
return v.norm();
}
template <typename T> T cwise_vec_min(const T& v1, const T& v2) {
T toRet;
for(unsigned i = 0; i < v1.size(); i++)
toRet[i] = std::min(v1[i], v2[i]);
return toRet;
}
template <typename T> T cwise_vec_max(const T& v1, const T& v2) {
T toRet;
for(unsigned i = 0; i < v1.size(); i++)
toRet[i] = std::max(v1[i], v2[i]);
return toRet;
}
template <typename T> T cwise_vec_clamp(const T& v1, const T& v2, const T& v3) {
T toRet;
for(unsigned i = 0; i < v1.size(); i++)
toRet[i] = std::clamp(v1[i], v2[i], v3[i]);
return toRet;
}
template <typename T> T cwise_vec_modf(const T& v1, T& v2) {
T toRet;
for(unsigned i = 0; i < v1.size(); i++)
toRet[i] = std::modf(v1[i], &v2[i]);
return toRet;
}
template <typename T> std::vector<Vector<T, 2>> arc_vec(const Vector<T, 2>& arcCenter, const Vector<T, 2>& arcDirStart, const Vector<T, 2>& arcDirEnd, const Vector<T, 2>& arcDirCenter, T arcRadius, unsigned numOfDivisions) {
T a1 = std::atan2(arcDirStart.y(), arcDirStart.x());
T a2 = std::atan2(arcDirEnd.y(), arcDirEnd.x());
T a3 = std::atan2(arcDirCenter.y(), arcDirCenter.x());
T minAngle = std::min(a1, a2);
T maxAngle = std::max(a1, a2);
bool swapHappen = (a3 > minAngle && a3 > maxAngle) || (a3 < minAngle && a3 < maxAngle);
if(swapHappen) {
minAngle += 2.0 * std::numbers::pi;
std::swap(minAngle, maxAngle);
}
bool flipped = minAngle == a2;
std::vector<Vector<T, 2>> toRet;
T tStep = 1.0 / static_cast<T>(numOfDivisions);
if(flipped) {
for(unsigned i = numOfDivisions - 1; i > 0; i--) {
T newAngle = std::lerp(minAngle, maxAngle, tStep * i);
toRet.emplace_back(arcCenter + Vector<T, 2>{std::cos(newAngle), std::sin(newAngle)} * arcRadius);
}
}
else {
for(unsigned i = 1; i < numOfDivisions; i++) {
T newAngle = std::lerp(minAngle, maxAngle, tStep * i);
toRet.emplace_back(arcCenter + Vector<T, 2>{std::cos(newAngle), std::sin(newAngle)} * arcRadius);
}
}
return toRet;
}
template <typename T> std::vector<Vector<T, 2>> gen_circle_points(const Vector<T, 2>& center, T radius, unsigned numOfPoints) {
std::vector<Vector<T, 2>> toRet;
for(unsigned i = 0; i < numOfPoints; i++) {
T angle = (static_cast<T>(i) / static_cast<T>(numOfPoints)) * std::numbers::pi * 2;
toRet.emplace_back(std::cos(angle) * radius + center.x(), std::sin(angle) * radius + center.y());
}
return toRet;
}
// https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
template <typename T> T catmull_rom_get_t(T t, T alpha, const Vector<T, 2>& p0, const Vector<T, 2>& p1) {
Vector<T, 2> d = p1 - p0;
T a = d.dot(d);
T b = std::pow(a, alpha * 0.5);
return (b + t);
}
template <typename T> Vector<T, 2> catmull_rom(const Vector<T, 2>& p0, const Vector<T, 2>& p1, const Vector<T, 2>& p2, const Vector<T, 2>& p3, T t, T alpha = 0.5) {
T t0 = 0.0;
T t1 = catmull_rom_get_t(t0, alpha, p0, p1);
T t2 = catmull_rom_get_t(t1, alpha, p1, p2);
T t3 = catmull_rom_get_t(t2, alpha, p2, p3);
t = std::lerp(t1, t2, t);
Vector<T, 2> A1 = ( t1-t )/( t1-t0 )*p0 + ( t-t0 )/( t1-t0 )*p1;
Vector<T, 2> A2 = ( t2-t )/( t2-t1 )*p1 + ( t-t1 )/( t2-t1 )*p2;
Vector<T, 2> A3 = ( t3-t )/( t3-t2 )*p2 + ( t-t2 )/( t3-t2 )*p3;
Vector<T, 2> B1 = ( t2-t )/( t2-t0 )*A1 + ( t-t0 )/( t2-t0 )*A2;
Vector<T, 2> B2 = ( t3-t )/( t3-t1 )*A2 + ( t-t1 )/( t3-t1 )*A3;
Vector<T, 2> C = ( t2-t )/( t2-t1 )*B1 + ( t-t1 )/( t2-t1 )*B2;
return C;
}
template <typename T, int S> T transform_scalar(const Transform<T, 2, S>& transform, T scalar) {
return (transform.linear() * Vector<T, 2>{scalar, scalar}).x();
}
template <typename T> std::array<Vector<T, 2>, 4> triangle_from_rect_points(const Vector<T, 2>& min, const Vector<T, 2>& max) {
return {min, {min.x(), max.y()}, max, {max.x(), min.y()}};
}

View file

@ -0,0 +1,46 @@
#include "ByteStream.hpp"
#include <cereal/archives/portable_binary.hpp>
#include <cereal/types/string.hpp>
#include "NetLibrary.hpp"
ByteMemBuf::ByteMemBuf(char* p, size_t l) {
setg(p, p, p + l);
}
ByteMemStream::ByteMemStream(char* p, size_t l):
std::istream(&buffer),
buffer(p, l)
{
rdbuf(&buffer);
}
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride) {
if(v.length() <= stride)
return {};
uint64_t nextByte = 0;
std::vector<std::shared_ptr<std::stringstream>> toRet;
toRet.emplace_back(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*toRet.back());
m((MessageCommandType)0, (uint64_t)v.length(), cereal::binary_data(v.data() + nextByte, stride));
}
nextByte += stride;
for(;;) {
if((v.length() - nextByte) <= stride) {
toRet.emplace_back(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*toRet.back());
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, (uint64_t)(v.length() - nextByte)));
}
break;
}
toRet.emplace_back(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*toRet.back());
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, stride));
}
nextByte += stride;
}
return toRet;
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <iostream>
#include <sstream>
#include <memory>
#include <vector>
// https://tuttlem.github.io/2014/08/18/getting-istream-to-work-off-a-byte-array.html
class ByteMemBuf : public std::basic_streambuf<char> {
public:
ByteMemBuf(char* p, size_t l);
};
class ByteMemStream : public std::istream {
public:
ByteMemStream(char* p, size_t l);
private:
ByteMemBuf buffer;
};
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride);

View file

@ -0,0 +1,165 @@
#include "NetClient.hpp"
#include "NetLibrary.hpp"
#include "NetServer.hpp"
#include <chrono>
#include <Helpers/Logger.hpp>
NetClient::NetClient(const std::string& serverFullID) {
receiveQueue = std::make_shared<ReceiveQueue>();
this->serverFullID = serverFullID;
lastMessageTime = std::chrono::steady_clock::now();
}
NetClient::NetClient(NetServer& server) {
receiveQueue = std::make_shared<ReceiveQueue>();
directConnectServer = &server;
server.directConnectClient = this;
server.directConnectClientData = std::make_shared<NetServer::ClientData>();
server.directConnectClientData->lastMessageTime = std::chrono::steady_clock::now();
server.clients.emplace_back(server.directConnectClientData);
lastMessageTime = std::chrono::steady_clock::now();
}
void NetClient::init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel) {
if(isDisconnected)
return;
auto commandChannelOnMessage = [channelName, rQueueWeak = make_weak_ptr(receiveQueue)](rtc::message_variant data) {
auto rQueue = rQueueWeak.lock();
if(rQueue) {
std::scoped_lock rQueueLock(rQueue->mut);
rQueue->messages.emplace(channelName, std::get<rtc::binary>(data));
}
};
channel->onMessage(commandChannelOnMessage);
channel->onClosed([cWeak = make_weak_ptr(shared_from_this())]() {
auto cLock = cWeak.lock();
if(cLock)
cLock->isDisconnected = true;
});
channels[channelName] = channel;
Logger::get().log("INFO", "Client channel setup: " + channelName);
lastMessageTime = std::chrono::steady_clock::now();
}
void NetClient::update() {
if(isDisconnected)
return;
if(std::chrono::steady_clock::now() - lastMessageTime >= NetLibrary::TIMEOUT_DURATION) {
Logger::get().log("INFO", "Connection timed out");
isDisconnected = true;
return;
}
parse_received_messages();
send_queued_messages();
}
void NetClient::parse_received_messages() {
std::scoped_lock rQueueLock(receiveQueue->mut);
while(!receiveQueue->messages.empty()) {
auto& receivedMessage = receiveQueue->messages.front();
lastMessageTime = std::chrono::steady_clock::now();
size_t messageOrderDisplacement = 0;
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
receiveQueue->messages.pop();
continue;
}
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
messageOrderDisplacement = sizeof(MessageOrder);
}
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
cereal::PortableBinaryInputArchive inArchive(strm);
auto& spfm = pfm[receivedMessage.channel];
MessageCommandType commandID;
inArchive(commandID);
if(commandID == 0) {
if(spfm.partialFragmentMessage.size() == 0) {
spfm.partialFragmentMessageLoc = 0;
uint64_t messageSize;
inArchive(messageSize);
spfm.partialFragmentMessage.resize(messageSize);
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
}
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
MessageCommandType completeCommandID;
completeArchive(completeCommandID);
recvCallbacks[completeCommandID](completeArchive);
spfm.partialFragmentMessage.clear();
spfm.partialFragmentMessageLoc = 0;
}
else {
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
}
}
else
recvCallbacks[commandID](inArchive);
receiveQueue->messages.pop();
}
}
void NetClient::send_queued_messages() {
for(auto& [channelName, messageQueue] : messageQueues) {
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
if(directConnectServer) {
auto& c = directConnectServer->directConnectClientData;
std::scoped_lock rMessLock(c->receivedMessagesMutex);
while(!messageQueue.empty()) {
std::string toInsert;
if(addMessageOrder)
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
else
toInsert = messageQueue.front()->str();
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
c->receivedMessages.emplace(channelName, b);
messageQueue.pop();
}
}
else {
auto& channel = channels[channelName];
if(channel) {
if(channel->isOpen()) {
while(!messageQueue.empty()) {
#ifdef __EMSCRIPTEN__
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
break;
#endif
if(addMessageOrder)
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
else
send_str_as_bytes(channel, messageQueue.front()->str());
messageQueue.pop();
}
#ifdef __EMSCRIPTEN__
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
#endif
}
}
}
}
}
bool NetClient::is_disconnected() {
return isDisconnected;
}
void NetClient::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
channel->send((const std::byte*)str.c_str(), str.size());
}
void NetClient::add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback) {
recvCallbacks[commandID] = callback;
}

View file

@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <string>
#include <rtc/rtc.hpp>
#include <cereal/cereal.hpp>
#include <unordered_map>
#include <functional>
#include <cereal/cereal.hpp>
#include <cereal/archives/portable_binary.hpp>
#include <queue>
#include "ByteStream.hpp"
#include "NetLibrary.hpp"
typedef std::function<void(cereal::PortableBinaryInputArchive&)> NetClientRecvCallback;
class NetClient : public std::enable_shared_from_this<NetClient> {
public:
NetClient(const std::string& serverFullID);
NetClient(NetServer& server);
template <typename... Args> void send_items_to_server(const std::string& channel, Args&&... items) {
if(isDisconnected)
return;
auto ss(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*ss);
(m(items), ...);
}
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
auto& messageQueue = messageQueues[channel];
if(fragmentedMessage.empty())
messageQueue.emplace(ss);
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
for(auto& ss2 : fragmentedMessage)
messageQueue.emplace(ss2);
}
}
void update();
void add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback);
bool is_disconnected();
private:
std::string localID;
void init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel);
void parse_received_messages();
void send_queued_messages();
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
struct ReceiveQueue {
std::mutex mut;
struct ReceivedMessage {
std::string channel;
rtc::binary data;
};
std::queue<ReceivedMessage> messages;
};
std::shared_ptr<ReceiveQueue> receiveQueue;
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
struct PartialFragmentMessage {
std::string partialFragmentMessage;
uint64_t partialFragmentMessageLoc = 0;
};
std::unordered_map<std::string, PartialFragmentMessage> pfm;
std::unordered_map<MessageCommandType, NetClientRecvCallback> recvCallbacks;
NetServer* directConnectServer = nullptr;
std::atomic<bool> isDisconnected = false;
MessageOrder nextMessageOrderToSend = 0;
MessageOrder nextMessageOrderToExpect = 0;
std::string serverFullID;
bool fullyRegistered = false;
std::chrono::steady_clock::time_point lastMessageTime;
friend class NetLibrary;
friend class NetServer;
};

View file

@ -0,0 +1,389 @@
#include "NetLibrary.hpp"
#include <fstream>
#include <nlohmann/json.hpp>
#include <rtc/peerconnection.hpp>
#include "../Logger.hpp"
#include <rtc/rtc.hpp>
#include <variant>
#include <vector>
#include <Helpers/Random.hpp>
#include "NetClient.hpp"
#include "NetServer.hpp"
#include <bit>
std::atomic<bool> NetLibrary::alreadyInitialized = false;
std::string NetLibrary::signalingAddr;
std::shared_ptr<rtc::WebSocket> NetLibrary::ws;
std::unordered_map<std::string, std::shared_ptr<NetLibrary::PeerData>> NetLibrary::peers;
std::string NetLibrary::globalID;
rtc::Configuration NetLibrary::config;
std::mutex NetLibrary::serverListMutex;
std::mutex NetLibrary::clientListMutex;
std::mutex NetLibrary::peerListMutex;
std::vector<std::weak_ptr<NetClient>> NetLibrary::clients;
std::vector<std::weak_ptr<NetServer>> NetLibrary::servers;
std::promise<void> NetLibrary::wsPromise;
void NetLibrary::init() {
if(alreadyInitialized)
return;
using json = nlohmann::json;
std::ifstream f("data/config/p2p.json");
std::vector<std::string> stunServerList;
std::vector<std::string> turnServerList;
rtc::InitLogger(rtc::LogLevel::Info, [](rtc::LogLevel level, std::string message) {
Logger::get().log("INFO", "[LibDataChannel Log Level " + std::to_string(static_cast<int>(level)) + "] " + message);
});
if(f.is_open()) {
try {
json j;
f >> j;
j.at("signalingServer").get_to<std::string>(signalingAddr);
std::vector<std::string> stunList = j.at("stunList").get<std::vector<std::string>>();
for(std::string& s : stunList) {
if(s.substr(0, 5).compare("stun:"))
s.insert(0, "stun:");
config.iceServers.emplace_back(s);
}
}
catch(...) {
throw std::runtime_error("[NetLibrary::init] Invalid config/p2p.json");
}
}
else
throw std::runtime_error("[NetLibrary::init] Could not open config/p2p.json");
f.close();
// Not sure why, but TLS verification fails on mac
// Should be fixed later, but for now this is fine, as the signaling server doesn't have
// critical information
#ifdef __APPLE__
rtc::WebSocket::Configuration wsConfig;
wsConfig.disableTlsVerification = true;
ws = std::make_shared<rtc::WebSocket>(wsConfig);
#else
ws = std::make_shared<rtc::WebSocket>();
#endif
auto wsFuture = wsPromise.get_future();
ws->onOpen([]() {
Logger::get().log("INFO", "Websocket connected, signaling ready");
wsPromise.set_value();
});
ws->onError([](std::string s) {
Logger::get().log("INFO", "Websocket error: " + s);
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
});
ws->onClosed([]() {
Logger::get().log("INFO", "Websocket closed");
destroy();
});
ws->onMessage([wws = make_weak_ptr(ws)](auto data) {
if(!std::holds_alternative<std::string>(data))
return;
json message = json::parse(std::get<std::string>(data));
auto itID = message.find("id");
if(itID == message.end())
return;
auto id = itID->get<std::string>();
auto itType = message.find("type");
auto type = itType->get<std::string>();
std::shared_ptr<rtc::PeerConnection> peerConnection;
{
std::scoped_lock peerListLock(peerListMutex);
auto peerIt = peers.find(id);
if(peerIt != peers.end())
peerConnection = peerIt->second->connection;
else if(type == "offer") {
Logger::get().log("INFO", "Websocket answering to " + id);
auto peer = setup_peer_connection(config, wws, id);
peers[id] = peer;
peerConnection = peer->connection;
}
}
if(!peerConnection)
return;
if(type == "offer" || type == "answer") {
auto sdp = message["description"].get<std::string>();
peerConnection->setRemoteDescription(rtc::Description(sdp, type));
}
else if(type == "candidate") {
auto sdp = message["candidate"].get<std::string>();
auto mid = message["mid"].get<std::string>();
peerConnection->addRemoteCandidate(rtc::Candidate(sdp, mid));
}
});
get_global_id();
Logger::get().log("INFO", "NetLibrary global id: " + globalID);
const std::string wsPrefix = signalingAddr.find("://") == std::string::npos ? "ws://" : "";
const std::string url = wsPrefix + signalingAddr + "/" + globalID;
Logger::get().log("INFO", "Websocket URL is: " + url);
ws->open(url);
Logger::get().log("INFO", "NetLibrary waiting for signaling to be connected...");
//wsFuture.get();
alreadyInitialized = true;
}
std::string NetLibrary::get_random_server_local_id() {
return Random::get().alphanumeric_str(LOCALID_LEN);
}
const std::string& NetLibrary::get_global_id() {
if(globalID.empty())
globalID = Random::get().alphanumeric_str(GLOBALID_LEN);
return globalID;
}
void NetLibrary::register_server(std::shared_ptr<NetServer> server) {
std::scoped_lock serverListLock(serverListMutex);
servers.emplace_back(server);
}
void NetLibrary::register_client(std::shared_ptr<NetClient> client) {
client->localID = Random::get().alphanumeric_str(LOCALID_LEN);
std::scoped_lock serverListLock(clientListMutex);
clients.emplace_back(client);
}
void NetLibrary::finish_client_registration(std::shared_ptr<NetClient> client) {
try {
std::string serverGlobalID = client->serverFullID.substr(0, GLOBALID_LEN);
std::string serverLocalID = client->serverFullID.substr(GLOBALID_LEN);
std::scoped_lock peerListLock(peerListMutex);
if(!peers.contains(serverGlobalID)) {
auto peer = setup_peer_connection(config, ws, serverGlobalID);
peers[serverGlobalID] = peer;
peer->channel = peer->connection->createDataChannel(MAIN_NETLIBRARY_CHANNEL);
setup_main_data_channel(peer, serverGlobalID);
}
peers[serverGlobalID]->messageQueue.emplace(client->localID + serverLocalID);
client->fullyRegistered = true;
}
catch(const std::exception& e) {
Logger::get().log("INFO", "Invalid client registration: " + std::string(e.what()));
client->isDisconnected = true;
}
}
void NetLibrary::update() {
{
std::scoped_lock clientListLock(clientListMutex);
std::erase_if(clients, [](auto& cWeak) {
auto cLock = cWeak.lock();
if(!cLock)
return true;
if(!cLock->fullyRegistered && ws->isOpen())
finish_client_registration(cLock);
return false;
});
}
{
std::scoped_lock serverListLock(serverListMutex);
std::erase_if(servers, [](auto& sWeak) {
return !sWeak.lock();
});
}
{
std::scoped_lock peerListLock(peerListMutex);
std::erase_if(peers, [](auto& a) {
auto& p = a.second;
rtc::PeerConnection::State s = p->connection->state();
if(s == rtc::PeerConnection::State::Disconnected || s == rtc::PeerConnection::State::Closed || s == rtc::PeerConnection::State::Failed)
return true;
if(p->channel) {
if(p->channel->isOpen()) {
while(!p->messageQueue.empty()) {
p->channel->send(p->messageQueue.front());
Logger::get().log("INFO", "Sent main channel message: " + p->messageQueue.front());
p->messageQueue.pop();
}
}
}
return false;
});
}
}
std::shared_ptr<NetLibrary::PeerData> NetLibrary::setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id) {
using json = nlohmann::json;
auto peer = std::make_shared<PeerData>();
peer->connection = std::make_shared<rtc::PeerConnection>(config);
peer->connection->onLocalDescription([wws, id](rtc::Description description) {
json message = {{"id", id},
{"type", description.typeString()},
{"description", std::string(description)}};
if (auto ws = wws.lock())
ws->send(message.dump());
});
peer->connection->onLocalCandidate([wws, id](rtc::Candidate candidate) {
json message = {{"id", id},
{"type", "candidate"},
{"candidate", std::string(candidate)},
{"mid", candidate.mid()}};
if (auto ws = wws.lock())
ws->send(message.dump());
});
peer->connection->onDataChannel([id, peerWeak = make_weak_ptr(peer)](std::shared_ptr<rtc::DataChannel> dc) {
auto peer = peerWeak.lock();
if(peer) {
if(!peer->channel) {
if(dc->label() == MAIN_NETLIBRARY_CHANNEL) {
peer->channel = dc;
setup_main_data_channel(peer, id);
}
else {
Logger::get().log("INFO", "Client connected with incorrect first channel name: " + dc->label() + ". Could be from a different version of the program");
dc->send("REFUSE");
peer->connection->close();
}
}
else
channel_created_in_client(dc);
}
});
return peer;
}
void NetLibrary::setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id) {
peer->channel->onOpen([id]() {
Logger::get().log("INFO", "Main data channel has opened on " + id);
});
peer->channel->onClosed([id]() {
Logger::get().log("INFO", "Main data channel has closed on " + id);
});
peer->channel->onMessage([id, pConWeak = make_weak_ptr(peer->connection)](auto data) {
if(!std::holds_alternative<std::string>(data)) { // Main channel messages will only carry text
Logger::get().log("INFO", "Main data channel received binary message, which is not expected");
return;
}
std::string message = std::get<std::string>(data);
if(message == "REFUSE") {
Logger::get().log("USERINFO", "Refused connection. Could be on a different version");
auto pConLock = pConWeak.lock();
if(pConLock)
pConLock->close();
}
else {
std::string clientLocalID = message.substr(0, LOCALID_LEN);
std::string serverLocalID = message.substr(LOCALID_LEN);
Logger::get().log("INFO", "Main data channel received message " + message);
auto pConLock = pConWeak.lock();
if(pConLock)
assign_client_connection_to_server(serverLocalID, clientLocalID, pConLock);
}
});
}
void NetLibrary::assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection) {
std::scoped_lock serverListLock(serverListMutex);
auto serverIt = std::find_if(servers.begin(), servers.end(), [&serverLocalID](auto& s) {
auto sLock = s.lock();
if(!sLock)
return false;
return sLock->localID == serverLocalID;
});
if(serverIt != servers.end()) {
auto sLock = serverIt->lock();
if(sLock)
sLock->client_connected(connection, clientLocalID);
}
}
void NetLibrary::channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel) {
std::string clientID = channel->label().substr(0, LOCALID_LEN);
std::string channelName = channel->label().substr(LOCALID_LEN);
std::scoped_lock clientListLock(clientListMutex);
auto clientIt = std::find_if(clients.begin(), clients.end(), [&clientID](auto& c) {
auto cLock = c.lock();
if(!cLock)
return false;
return cLock->localID == clientID;
});
if(clientIt != clients.end()) {
auto cLock = clientIt->lock();
if(cLock)
cLock->init_channel(channelName, channel);
}
}
std::string NetLibrary::attach_message_order_and_update(MessageOrder& order, const std::string& message) {
union {
MessageOrder o;
char b[sizeof(MessageOrder)];
} u;
u.o = (std::endian::native == std::endian::little) ? std::byteswap(order) : order;
order++;
return std::string(u.b, sizeof(MessageOrder)) + message;
}
MessageOrder NetLibrary::get_message_order(rtc::binary& message) {
union {
MessageOrder o;
char b[sizeof(MessageOrder)];
} u;
std::memcpy(u.b, message.data(), sizeof(MessageOrder));
if(std::endian::native == std::endian::little)
u.o = std::byteswap(u.o);
return u.o;
}
void NetLibrary::destroy() {
ws = nullptr;
{
std::scoped_lock peerListLock(peerListMutex);
peers.clear();
}
{
std::scoped_lock clientListLock(clientListMutex);
for(auto& c : clients) {
auto cLock = c.lock();
if(cLock)
cLock->isDisconnected = true;
}
clients.clear();
}
{
std::scoped_lock serverListLock(serverListMutex);
for(auto& s : servers) {
auto sLock = s.lock();
if(sLock)
sLock->isDisconnected = true;
}
servers.clear();
}
wsPromise = std::promise<void>();
//globalID.clear(); // Do we need to do this? (Causes problems when global ID changes from the user's side after they copy a lobby address
alreadyInitialized = false;
}

View file

@ -0,0 +1,73 @@
#pragma once
#include <string>
#include <unordered_map>
#include <rtc/rtc.hpp>
#include <queue>
typedef uint64_t MessageOrder;
class NetClient;
class NetServer;
template <class T> std::weak_ptr<T> make_weak_ptr(std::shared_ptr<T> ptr) { return ptr; }
#define RELIABLE_COMMAND_CHANNEL "reliableCommand"
#define UNRELIABLE_COMMAND_CHANNEL "unreliableCommand"
#define RESOURCE_COMMAND_CHANNEL "resourceCommand"
#define MAIN_NETLIBRARY_CHANNEL "main00001"
typedef uint8_t MessageCommandType;
class NetLibrary {
public:
static void init();
static void update();
static std::string get_random_server_local_id();
static const std::string& get_global_id();
static void register_server(std::shared_ptr<NetServer> server);
static void register_client(std::shared_ptr<NetClient> client);
static void destroy();
static constexpr size_t LOCALID_LEN = 5;
static constexpr size_t GLOBALID_LEN = 20;
private:
static constexpr size_t FRAGMENT_MESSAGE_STRIDE = 4096;
static constexpr size_t MAX_BUFFERED_DATA_PER_CHANNEL = 65500;
static constexpr std::chrono::seconds TIMEOUT_DURATION = std::chrono::seconds(15);
static void finish_client_registration(std::shared_ptr<NetClient> client);
static void assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection);
static void channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel);
static std::string attach_message_order_and_update(MessageOrder& order, const std::string& message);
static MessageOrder get_message_order(rtc::binary& message);
struct PeerData {
std::shared_ptr<rtc::PeerConnection> connection;
std::shared_ptr<rtc::DataChannel> channel;
std::queue<std::string> messageQueue;
};
static void setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id);
static std::shared_ptr<PeerData> setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id);
static std::atomic<bool> alreadyInitialized;
static std::string signalingAddr;
static std::shared_ptr<rtc::WebSocket> ws;
static std::unordered_map<std::string, std::shared_ptr<PeerData>> peers;
static std::string globalID;
static rtc::Configuration config;
static std::mutex clientListMutex;
static std::mutex serverListMutex;
static std::mutex peerListMutex;
static std::vector<std::weak_ptr<NetClient>> clients;
static std::vector<std::weak_ptr<NetServer>> servers;
friend class NetClient;
friend class NetServer;
static std::promise<void> wsPromise;
};

View file

@ -0,0 +1,201 @@
#include "NetServer.hpp"
#include "NetLibrary.hpp"
#include <chrono>
#include <rtc/peerconnection.hpp>
#include <Helpers/Random.hpp>
#include <Helpers/Logger.hpp>
#include "NetClient.hpp"
NetServer::NetServer(const std::string& serverLocalID) {
localID = serverLocalID;
Logger::get().log("INFO", "[NetServer::NetServer] Server has local id: " + serverLocalID);
}
void NetServer::update() {
if(isDisconnected)
return;
std::erase_if(clients, [&](auto& client) {
bool timedOut = std::chrono::steady_clock::now() - client->lastMessageTime > NetLibrary::TIMEOUT_DURATION;
if(client->setToDisconnect || timedOut) {
if(disconnectCallback)
disconnectCallback(*client);
if(timedOut)
Logger::get().log("INFO", "Connection timed out");
return true;
}
client->parse_received_messages(*this);
client->send_queued_messages(*this);
return false;
});
std::scoped_lock clientAddLock(clientsToAddMutex);
clients.insert(clients.begin(), clientsToAdd.begin(), clientsToAdd.end());
clientsToAdd.clear();
}
bool NetServer::is_disconnected() {
return isDisconnected;
}
void NetServer::client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID) {
if(isDisconnected)
return;
auto newClient = std::make_shared<ClientData>();
newClient->lastMessageTime = std::chrono::steady_clock::now();
{
std::scoped_lock clientAddLock(clientsToAddMutex);
clientsToAdd.emplace_back(newClient);
}
Logger::get().log("INFO", "Client connected and creating channels for: " + clientLocalID);
newClient->channels.emplace(RELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RELIABLE_COMMAND_CHANNEL));
newClient->channels.emplace(RESOURCE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RESOURCE_COMMAND_CHANNEL));
rtc::DataChannelInit unreliableCommandInit;
unreliableCommandInit.reliability.maxPacketLifeTime = std::chrono::milliseconds(2000);
unreliableCommandInit.reliability.unordered = true;
newClient->channels.emplace(UNRELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + UNRELIABLE_COMMAND_CHANNEL, unreliableCommandInit));
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
std::shared_ptr<ClientData> clientLock = weakClient.lock();
if(clientLock) {
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
clientLock->receivedMessages.emplace(RELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
}
});
// If any of the local channels are closed, we can assume the client has disconnected
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onClosed([weakClient = make_weak_ptr(newClient)]() {
std::shared_ptr<ClientData> clientLock = weakClient.lock();
if(clientLock)
clientLock->setToDisconnect = true;
});
newClient->channels[UNRELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
std::shared_ptr<ClientData> clientLock = weakClient.lock();
if(clientLock) {
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
clientLock->receivedMessages.emplace(UNRELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
}
});
newClient->channels[RESOURCE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
std::shared_ptr<ClientData> clientLock = weakClient.lock();
if(clientLock) {
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
clientLock->receivedMessages.emplace(RESOURCE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
}
});
}
void NetServer::ClientData::send_queued_messages(NetServer& server) {
for(auto& [channelName, messageQueue] : messageQueues) {
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
if(this == server.directConnectClientData.get()) {
NetClient* c = server.directConnectClient;
std::scoped_lock recvQueueLock(c->receiveQueue->mut);
while(!messageQueue.empty()) {
std::string toInsert;
if(addMessageOrder)
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
else
toInsert = messageQueue.front()->str();
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
c->receiveQueue->messages.emplace(channelName, b);
messageQueue.pop();
}
}
else {
auto& channel = channels[channelName];
if(channel) {
if(channel->isOpen()) {
while(!messageQueue.empty()) {
#ifdef __EMSCRIPTEN__
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
break;
#endif
if(addMessageOrder)
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
else
send_str_as_bytes(channel, messageQueue.front()->str());
messageQueue.pop();
}
#ifdef __EMSCRIPTEN__
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
#endif
}
}
}
}
}
void NetServer::ClientData::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
channel->send((const std::byte*)str.c_str(), str.size());
}
void NetServer::ClientData::parse_received_messages(NetServer& server) {
std::scoped_lock recvMessLock(receivedMessagesMutex);
while(!receivedMessages.empty()) {
auto& receivedMessage = receivedMessages.front();
lastMessageTime = std::chrono::steady_clock::now();
size_t messageOrderDisplacement = 0;
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
receivedMessages.pop();
continue;
}
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
messageOrderDisplacement = sizeof(MessageOrder);
}
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
cereal::PortableBinaryInputArchive inArchive(strm);
auto& spfm = pfm[receivedMessage.channel];
MessageCommandType commandID;
inArchive(commandID);
if(commandID == 0) {
if(spfm.partialFragmentMessage.size() == 0) {
spfm.partialFragmentMessageLoc = 0;
uint64_t messageSize;
inArchive(messageSize);
spfm.partialFragmentMessage.resize(messageSize);
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
}
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
MessageCommandType completeCommandID;
completeArchive(completeCommandID);
server.recvCallbacks[completeCommandID](*this, completeArchive);
spfm.partialFragmentMessage.clear();
spfm.partialFragmentMessageLoc = 0;
}
else {
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
}
}
else
server.recvCallbacks[commandID](*this, inArchive);
receivedMessages.pop();
}
}
void NetServer::add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback) {
recvCallbacks[commandID] = callback;
}
void NetServer::add_disconnect_callback(const NetServerDisconnectCallback& callback) {
disconnectCallback = callback;
}
NetServer::~NetServer() {
if(directConnectClient)
directConnectClient->isDisconnected = true;
}

View file

@ -0,0 +1,134 @@
#pragma once
#include <memory>
#include <string>
#include <rtc/rtc.hpp>
#include <mutex>
#include <unordered_map>
#include <cereal/archives/portable_binary.hpp>
#include <cereal/cereal.hpp>
#include "ByteStream.hpp"
#include "NetLibrary.hpp"
class NetServer : public std::enable_shared_from_this<NetServer> {
public:
struct ClientData;
typedef std::function<void(ClientData&, cereal::PortableBinaryInputArchive&)> NetServerRecvCallback;
typedef std::function<void(ClientData&)> NetServerConnectCallback;
typedef std::function<void(ClientData&)> NetServerDisconnectCallback;
struct ClientData {
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messages;
struct PartialFragmentMessage {
std::string partialFragmentMessage;
uint64_t partialFragmentMessageLoc = 0;
};
std::unordered_map<std::string, PartialFragmentMessage> pfm;
MessageOrder nextMessageOrderToSend = 0;
MessageOrder nextMessageOrderToExpect = 0;
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
std::mutex receivedMessagesMutex;
struct ReceivedMessage {
std::string channel;
rtc::binary data;
};
std::queue<ReceivedMessage> receivedMessages;
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
uint64_t customID;
void send_queued_messages(NetServer& server);
void parse_received_messages(NetServer& server);
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
std::atomic<bool> setToDisconnect = false;
std::chrono::steady_clock::time_point lastMessageTime;
friend class NetServer;
friend class NetClient;
};
NetServer(const std::string& serverLocalID);
~NetServer();
void update();
template <typename... Args> void send_items_to_client(ClientData& client, const std::string& channel, Args&&... items) {
auto ss(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*ss);
(m(items), ...);
}
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
auto& messageQueue = client.messageQueues[channel];
if(fragmentedMessage.empty())
messageQueue.emplace(ss);
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
for(auto& ss2 : fragmentedMessage)
messageQueue.emplace(ss2);
}
}
template <typename... Args> void send_items_to_all_clients(const std::string& channel, Args&&... items) {
send_items_to_client_if([&](const ClientData& c) {
return true;
}, channel, items...);
}
template <typename... Args> void send_items_to_all_clients_except(ClientData& client, const std::string& channel, Args&&... items) {
send_items_to_client_if([&](const ClientData& c) {
return (&c) != (&client);
}, channel, items...);
}
template <typename... Args> void send_items_to_client_if(std::function<bool(const ClientData&)> clientChecker, const std::string& channel, Args&&... items) {
auto ss(std::make_shared<std::stringstream>());
{
cereal::PortableBinaryOutputArchive m(*ss);
(m(items), ...);
}
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
for(auto& client : clients) {
if(clientChecker(*client)) {
auto& messageQueue = client->messageQueues[channel];
if(fragmentedMessage.empty())
messageQueue.emplace(ss);
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
for(auto& ss2 : fragmentedMessage)
messageQueue.emplace(ss2);
}
}
}
}
bool is_disconnected();
void add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback);
void add_disconnect_callback(const NetServerDisconnectCallback& callback);
private:
void parse_received_messages();
void send_queued_messages();
void client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID);
std::atomic<bool> isDisconnected = false;
std::string localID;
std::mutex clientList;
std::vector<std::shared_ptr<ClientData>> clients;
// Adding an add list, because i dont want to block the entire clients vector with a mutex
std::mutex clientsToAddMutex;
std::vector<std::shared_ptr<ClientData>> clientsToAdd;
std::unordered_map<MessageCommandType, NetServerRecvCallback> recvCallbacks;
NetServerDisconnectCallback disconnectCallback;
NetClient* directConnectClient = nullptr;
std::shared_ptr<ClientData> directConnectClientData;
friend class NetLibrary;
friend class NetClient;
};

View file

@ -0,0 +1,26 @@
#include "Random.hpp"
Random Random::global;
Random& Random::get() {
return global;
}
std::string Random::alphanumeric_str(size_t len) {
std::string toRet;
char allowedChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
size_t allowedCharCount = sizeof(allowedChars) / sizeof(char);
// We use allowedCharCount - 2 as the right bound because the range is inclusive,
// and we dont want to include the implicit null terminator in the end of allowedChars
// as a possible character
for(size_t i = 0; i < len; i++)
toRet.push_back(allowedChars[Random::get().int_range<size_t>(0, allowedCharCount - 2)]);
return toRet;
}
Random::Random():
randGen(std::random_device()())
{
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <random>
class Random {
public:
static Random& get();
Random();
template <typename T> T int_range(T start, T end) {
std::uniform_int_distribution<T> distribute(start, end);
return distribute(randGen);
}
template <typename T> T real_range(T start, T end) {
std::uniform_real_distribution<T> distribute(start, end);
return distribute(randGen);
}
std::string alphanumeric_str(size_t len);
std::mt19937_64 randGen;
private:
static Random global;
};

View file

@ -0,0 +1,525 @@
#pragma once
#include "Helpers/MathExtras.hpp"
#include <Eigen/Dense>
#include <array>
#include <limits>
#include <iostream>
#include <ostream>
#include <Eigen/Geometry>
using namespace Eigen;
namespace SCollision {
template <typename T> class AABB {
public:
typedef T UnderlyingType;
AABB() = default; // NOTE: Should not use the min and max numeric limits to initialize, since that wont work for WorldScalar
AABB(const Vector<T, 2>& initMin, const Vector<T, 2>& initMax):
min(initMin),
max(initMax)
{}
Vector<T, 2> min;
Vector<T, 2> max;
Vector<T, 2> center() const { return (min + max) / T(2); }
Vector<T, 2> bottom_left() const { return Vector<T, 2>{min.x(), max.y()}; }
Vector<T, 2> top_right() const { return Vector<T, 2>{max.x(), min.y()}; }
Vector<T, 2> dim() const { return max - min; }
T width() const { return max.x() - min.x(); }
T height() const { return max.y() - min.y(); }
void include_aabb_in_bounds(const AABB<T>& other) {
min = cwise_vec_min(other.min, min);
max = cwise_vec_max(other.max, max);
}
void include_point_in_bounds(const Vector<T, 2>& p) {
min = cwise_vec_min(p, min);
max = cwise_vec_max(p, max);
}
bool operator==(const AABB<T>& other) const {
return min == other.min && max == other.max;
}
template <typename S> AABB<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
Vector<S, 2> aabb1 = transformFunc(min);
Vector<S, 2> aabb2 = transformFunc(max);
Vector<S, 2> aabb3 = transformFunc(top_right());
Vector<S, 2> aabb4 = transformFunc(bottom_left());
AABB<S> toRet{aabb1, aabb1};
toRet.include_point_in_bounds(aabb1);
toRet.include_point_in_bounds(aabb2);
toRet.include_point_in_bounds(aabb3);
toRet.include_point_in_bounds(aabb4);
return toRet;
}
template <typename S> AABB<S> cast() {
AABB<S> toRet{min.template cast<S>(), max.template cast<S>()};
return toRet;
}
};
template <typename T> class Circle {
public:
typedef T UnderlyingType;
Circle() = default;
Circle(const Vector<T, 2>& initPos, T initRadius):
pos(initPos),
radius(initRadius),
bounds(pos - Vector<T, 2>{radius, radius}, pos + Vector<T, 2>{radius, radius})
{}
Vector<T, 2> pos;
T radius;
AABB<T> bounds;
template <typename S> Circle<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
Circle<S> newCirc = Circle<S>();
newCirc.pos = transformFunc(pos);
newCirc.radius = transformLinearFunc(radius);
newCirc.bounds = AABB<S>(newCirc.pos - Vector<S, 2>{newCirc.radius, newCirc.radius}, newCirc.pos + Vector<S, 2>{newCirc.radius, newCirc.radius});
return newCirc;
}
template <typename S> Circle<S> cast() {
Circle<S> toRet;
toRet.pos = pos.template cast<S>();
toRet.radius = static_cast<S>(radius);
toRet.bounds = bounds.template cast<S>();
return toRet;
}
};
template <typename T> class Triangle {
public:
typedef T UnderlyingType;
Triangle() = default;
Triangle(const std::array<Vector<T, 2>, 3>& initP):
p(initP),
bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2]))
{}
Triangle(const Vector<T, 2>& p1, const Vector<T, 2>& p2, const Vector<T, 2>& p3):
p({p1, p2, p3}),
bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2]))
{}
template <typename S> Triangle<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
Triangle<S> toRet = Triangle<S>();
toRet.p[0] = transformFunc(p[0]);
toRet.p[1] = transformFunc(p[1]);
toRet.p[2] = transformFunc(p[2]);
toRet.bounds = AABB<S>(cwise_vec_min(cwise_vec_min(toRet.p[0], toRet.p[1]), toRet.p[2]), cwise_vec_max(cwise_vec_max(toRet.p[0], toRet.p[1]), toRet.p[2]));
return toRet;
}
template <typename S> Triangle<S> cast() {
Triangle<S> toRet;
toRet.p[0] = p[0].template cast<S>();
toRet.p[1] = p[1].template cast<S>();
toRet.p[2] = p[2].template cast<S>();
toRet.bounds = bounds.template cast<S>();
return toRet;
}
std::array<Vector<T, 2>, 3> p;
AABB<T> bounds;
};
template <typename T> struct ColliderCollection {
typedef T UnderlyingType;
AABB<T> bounds;
std::vector<AABB<T>> aabb;
std::vector<Circle<T>> circle;
std::vector<Triangle<T>> triangle;
void clear() {
aabb.clear();
circle.clear();
triangle.clear();
bounds = AABB<T>({0, 0}, {0, 0});
}
size_t size() const { return aabb.size() + circle.size() + triangle.size(); }
bool empty() const { return aabb.empty() && circle.empty() && triangle.empty(); }
template <typename ColliderList> void bound_limit(const ColliderList& list) {
for(auto& o : list)
bounds.include_aabb_in_bounds(get_bounds(o));
}
void recalculate_bounds() {
bounds = AABB<T>();
if(!aabb.empty())
bounds = get_bounds(aabb.front());
else if(!circle.empty())
bounds = get_bounds(circle.front());
else if(!triangle.empty())
bounds = get_bounds(triangle.front());
bound_limit(aabb);
bound_limit(circle);
bound_limit(triangle);
}
template <typename S> ColliderCollection<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
ColliderCollection<S> toRet;
for(auto& o : aabb)
toRet.aabb.emplace_back(o.transform(transformFunc, transformLinearFunc));
for(auto& o : circle)
toRet.circle.emplace_back(o.transform(transformFunc, transformLinearFunc));
for(auto& o : triangle)
toRet.triangle.emplace_back(o.transform(transformFunc, transformLinearFunc));
toRet.recalculate_bounds();
return toRet;
}
};
template <typename T> const AABB<T>& get_bounds(const Circle<T>& c) { return c.bounds; }
template <typename T> const AABB<T>& get_bounds(const Triangle<T>& c) { return c.bounds; }
template <typename T> const AABB<T>& get_bounds(const AABB<T>& a) { return a; }
template <typename T> const AABB<T>& get_bounds(const ColliderCollection<T>& c) { return c.bounds; }
template<typename T> void generate_wide_line(ColliderCollection<T>& collection, const Vector<T, 2>& p1, const Vector<T, 2>& p2, T width, bool circleCaps) {
if(p1 != p2) {
Vector<T, 2> dir = p1 - p2;
Vector<T, 2> perpVec = perpendicular_vec2(dir);
perpVec.normalize();
Vector<T, 2> perpWidth = (perpVec * width * T(0.5)).eval();
collection.triangle.emplace_back(Triangle<T>{p1 + perpWidth, p1 - perpWidth, p2 + perpWidth});
collection.triangle.emplace_back(Triangle<T>{p2 + perpWidth, p2 - perpWidth, p1 - perpWidth});
}
if(circleCaps) {
collection.circle.emplace_back(Circle<T>{p1, width * T(0.5)});
if(p1 != p2)
collection.circle.emplace_back(Circle<T>{p2, width * T(0.5)});
}
collection.recalculate_bounds();
}
template <typename T> Vector<T, 2> two_dim_vec_slerp(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
Vector<T, 2> v1Norm = v1.normalized();
Vector<T, 2> v2Norm = v2.normalized();
Vector<T, 2> avg = (v1Norm + v2Norm) * 0.5;
if(std::abs(avg.x()) < 0.00001 && std::abs(avg.y()) < 0.00001)
return perpendicular_vec2(v1Norm).normalized();
else
return avg.normalized();
}
template <typename T> void generate_polyline(ColliderCollection<T>& collection, const std::vector<Vector<T, 2>>& points, T width, bool closePath) {
if(points.size() < 2) {
return;
}
Vector<T, 2> perpPrev = perpendicular_vec2((points[1] - points.front()).eval());
perpPrev.normalize();
std::array<Vector<T, 2>, 3> vertArray;
unsigned newIndex = 0;
if(!closePath || points.size() == 2) {
vertArray[0] = points.front() + perpPrev * width * 0.5;
vertArray[1] = points.front() - perpPrev * width * 0.5;
newIndex = 2;
}
if(points.size() == 2) {
vertArray[newIndex] = points.back() + perpPrev * width * 0.5;
collection.triangle.emplace_back(Triangle<T>{vertArray});
newIndex = (newIndex + 1) % 3;
vertArray[newIndex] = points.back() - perpPrev * width * 0.5;
collection.triangle.emplace_back(Triangle<T>{vertArray});
return;
}
for(size_t x = 0; x <= points.size(); x++) {
size_t i = x;
size_t iP1 = x + 1;
size_t iM1 = x - 1;
if(x == 0) {
if(!closePath)
continue;
else
iM1 = points.size() - 1;
}
else if(x == points.size() - 1) {
if(!closePath)
continue;
else {
iP1 = 0;
}
}
else if(x == points.size()) {
if(!closePath)
continue;
else {
i = 0;
iM1 = points.size() - 1;
iP1 = 1;
}
}
Vector<T, 2> v1Norm = (points[iM1] - points[i]).normalized();
Vector<T, 2> v2Norm = (points[iP1] - points[i]).normalized();
Vector<T, 2> perp = two_dim_vec_slerp(v1Norm, v2Norm);
if(perp.dot(perpPrev) <= 0.0)
perp = -perp;
T angle1 = std::acos((perp.dot(v1Norm) < 0 ? -perp : perp).dot(v1Norm));
T angle2 = std::acos((perp.dot(v2Norm) < 0 ? -perp : perp).dot(v2Norm));
T sinAngle1 = std::sin(angle1 + angle2);
T alteredWidth = std::lerp(0.5, 0.707, sinAngle1);
vertArray[newIndex] = points[i] + perp * alteredWidth * width;
newIndex = (newIndex + 1) % 3;
if(x != 0)
collection.triangle.emplace_back(Triangle<T>{vertArray});
vertArray[newIndex] = points[i] - perp * alteredWidth * width;
newIndex = (newIndex + 1) % 3;
if(x != 0)
collection.triangle.emplace_back(Triangle<T>{vertArray});
perpPrev = perp;
}
if(!closePath) {
Vector<T, 2> perp = perpendicular_vec2((points[points.size() - 2] - points.back()).eval());
perp.normalize();
if(perp.dot(perpPrev) <= 0.0)
perp = -perp;
vertArray[newIndex] = points.back() + perp * width * 0.5;
newIndex = (newIndex + 1) % 3;
collection.triangle.emplace_back(Triangle<T>{vertArray});
vertArray[newIndex] = points.back() - perp * width * 0.5;
newIndex = (newIndex + 1) % 3;
collection.triangle.emplace_back(Triangle<T>{vertArray});
}
}
// https://blackpawn.com/texts/pointinpoly/default.html
template <typename T> bool collide(const Vector<T, 2>& p, const Triangle<T>& t) {
Vector<T, 2> v0 = t.p[2] - t.p[0];
Vector<T, 2> v1 = t.p[1] - t.p[0];
Vector<T, 2> v2 = p - t.p[0];
T dot00 = v0.dot(v0);
T dot01 = v0.dot(v1);
T dot02 = v0.dot(v2);
T dot11 = v1.dot(v1);
T dot12 = v1.dot(v2);
T invDenom = T(1) / (dot00 * dot11 - dot01 * dot01);
T u = (dot11 * dot02 - dot01 * dot12) * invDenom;
T v = (dot00 * dot12 - dot01 * dot02) * invDenom;
return (u >= T(0)) && (v >= T(0)) && (u + v < T(1));
}
template <typename T> bool collide(const Triangle<T>& t, const Vector<T, 2>& p) {
return collide(p, t);
}
template <typename T> bool collide(const Vector<T, 2>& p, const AABB<T>& a) {
return (p.x() > a.min.x() && p.x() < a.max.x() && p.y() > a.min.y() && p.y() < a.max.y());
}
template <typename T> bool collide(const AABB<T>& a, const Vector<T, 2>& p) {
return collide(p, a);
}
template <typename T> bool collide(const Vector<T, 2>& p, const Circle<T>& a) {
return vec_distance(p, a.pos) <= a.radius;
}
template <typename T> bool collide(const Circle<T>& a, const Vector<T, 2>& p) {
return collide(p, a);
}
template <typename T> bool collide(const Circle<T>& c1, const Circle<T>& c2) {
return vec_distance(c1.pos, c2.pos) <= (c1.radius + c2.radius);
}
// https://gamedev.stackexchange.com/a/178154
template <typename T> bool collide(const Circle<T>& c, const AABB<T>& a) {
if(!collide(get_bounds(c), a))
return false;
Vector<T, 2> aabbCenter = a.center();
Vector<T, 2> aabbBounds = aabbCenter - a.min;
Vector<T, 2> distance = c.pos - aabbCenter;
Vector<T, 2> clampDst = cwise_vec_clamp(distance, (-aabbBounds).eval(), aabbBounds);
Vector<T, 2> closestPoint = aabbCenter + clampDst;
return vec_distance(closestPoint, c.pos) <= c.radius;
}
template <typename T> bool collide(const AABB<T>& a, const Circle<T>& c) {
return collide(c, a);
}
template <typename T> bool collide(const AABB<T>& a1, const AABB<T>& a2) {
return a1.min.x() < a2.max.x() && a1.max.x() > a2.min.x() &&
a1.min.y() < a2.max.y() && a1.max.y() > a2.min.y();
}
template <typename T> bool collide(const Circle<T>& c, const Triangle<T>& t) {
if(!collide(get_bounds(c), get_bounds(t)))
return false;
return collide(t.p[0], c) ||
collide(t.p[1], c) ||
collide(t.p[2], c) ||
collide(c.pos, t) ||
collision_circle_line_segment(c.pos, c.radius, t.p[0], t.p[1]) ||
collision_circle_line_segment(c.pos, c.radius, t.p[1], t.p[2]) ||
collision_circle_line_segment(c.pos, c.radius, t.p[2], t.p[0]);
}
template <typename T> bool collide(const Triangle<T>& t, const Circle<T>& c) {
return collide(c, t);
}
template <typename T> bool collide(const AABB<T>& a, const Triangle<T>& t) {
if(!collide(a, get_bounds(t)))
return false;
return collide(t.p[0], a) ||
collide(t.p[1], a) ||
collide(t.p[2], a) ||
collide(a.min, t) ||
collide(a.max, t) ||
collide(a.bottom_left(), t) ||
collide(a.top_right(), t) ||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.bottom_left()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.bottom_left()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.bottom_left()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.bottom_left()) ||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.bottom_left()) ||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.top_right()) ||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.bottom_left());
}
template <typename T> bool collide(const Triangle<T>& t, const AABB<T>& a) {
return collide(a, t);
}
template <typename T> bool collide(const Triangle<T>& t1, const Triangle<T>& t2) {
//return collide(get_bounds(t1), get_bounds(t2));
if(!collide(get_bounds(t1), get_bounds(t2)))
return false;
return collide(t1.p[0], t2) ||
collide(t1.p[1], t2) ||
collide(t1.p[2], t2) ||
collide(t2.p[0], t1) ||
collide(t2.p[1], t1) ||
collide(t2.p[2], t1) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[0], t2.p[1]) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[1], t2.p[2]) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[2], t2.p[0]) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[0], t2.p[1]) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[1], t2.p[2]) ||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[2], t2.p[0]) ||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[0], t2.p[1]) ||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[1], t2.p[2]) ||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[2], t2.p[0]);
}
template <typename Collection, typename Collider> bool collide(const Collection& collection, const Collider& c) {
for(auto& o : collection.circle)
if(collide(c, o))
return true;
for(auto& o : collection.aabb)
if(collide(c, o))
return true;
for(auto& o : collection.triangle)
if(collide(c, o))
return true;
return false;
}
template <typename T> class BVHContainer {
public:
typedef T UnderlyingType;
std::vector<BVHContainer<T>> children;
ColliderCollection<T> objects;
// NOTE: BVH TRANSFORMATION LEADS TO A BROKEN BVH
//void transform(const std::function<Vector<T, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<T(const T&)>& transformLinearFunc) {
// objects.transform(transformFunc, transformLinearFunc);
// for(auto& a : children)
// a.transform(transformFunc, transformLinearFunc);
//}
void clear() {
children.clear();
objects.clear();
}
bool empty() const {
return children.empty() && objects.empty();
}
unsigned assign_quad_to_point(const Vector<T, 2>& point, const Vector<T, 2>& origin) const {
bool negativeX = point.x() <= origin.x();
bool negativeY = point.y() <= origin.y();
if(negativeX && negativeY)
return 0;
else if(negativeX && !negativeY)
return 1;
else if(!negativeX && !negativeY)
return 2;
else
return 3;
}
void calculate_bvh_recursive(ColliderCollection<T>& bvhObjects) {
bvhObjects.recalculate_bounds();
objects.bounds = bvhObjects.bounds;
if(bvhObjects.empty()) {
return;
}
if(bvhObjects.size() <= 2) {
objects = bvhObjects;
return;
}
ColliderCollection<T> parts[4];
Vector<T, 2> halfwayPoint = bvhObjects.bounds.center();
for(auto& o : bvhObjects.aabb)
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].aabb.emplace_back(o);
for(auto& o : bvhObjects.circle)
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].circle.emplace_back(o);
for(auto& o : bvhObjects.triangle)
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].triangle.emplace_back(o);
for(size_t i = 0; i < 4; i++) {
if(parts[i].empty())
continue;
else if(parts[i].size() == bvhObjects.size()) {
objects = bvhObjects;
break;
}
else {
children.emplace_back();
children.back().calculate_bvh_recursive(parts[i]);
}
}
}
template <typename Collider> bool is_collide(const Collider& collider) const {
if(!collide(get_bounds(collider), get_bounds(objects)))
return false;
if(collide(objects, collider))
return true;
for(const auto& c : children)
if(c.is_collide(collider))
return true;
return false;
}
template <typename BVH> bool is_collide_other_bvh(const BVH& bvh) const {
if(!collide(get_bounds(objects), get_bounds(bvh.objects)))
return false;
if(is_collide(bvh.objects))
return true;
if(bvh.is_collide(objects))
return true;
for(const auto& c : children) {
for(const auto& d : bvh.children) {
if(c.is_collide_other_bvh(d))
return true;
}
}
return false;
}
};
};

View file

@ -0,0 +1,16 @@
#include "Serializers.hpp"
void to_json(nlohmann::json& j, const SkColor4f& d) {
j = nlohmann::json::array();
j.emplace_back(d.fR);
j.emplace_back(d.fG);
j.emplace_back(d.fB);
j.emplace_back(d.fA);
}
void from_json(const nlohmann::json& j, SkColor4f& d) {
j.at(0).template get_to<float>(d.fR);
j.at(1).template get_to<float>(d.fG);
j.at(2).template get_to<float>(d.fB);
j.at(3).template get_to<float>(d.fA);
}

View file

@ -0,0 +1,107 @@
#pragma once
#include <Eigen/Dense>
#include <cereal/archives/portable_binary.hpp>
#include <nlohmann/json.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include "FixedPoint.hpp"
#include <include/core/SkColor.h>
namespace cereal
{
// https://stackoverflow.com/questions/22884216/serializing-eigenmatrix-using-cereal-library (with some modifications using constexpr)
template <class Archive, class Derived> inline
typename std::enable_if<traits::is_output_serializable<BinaryData<typename Derived::Scalar>, Archive>::value, void>::type
save(Archive& ar, Eigen::PlainObjectBase<Derived> const & m){
if constexpr(std::is_same_v<typename Derived::Scalar, WorldScalar>) {
for(int i = 0; i < m.rows(); i++)
ar(m.data()[i]);
}
else {
typedef Eigen::PlainObjectBase<Derived> ArrT;
if constexpr(ArrT::RowsAtCompileTime==Eigen::Dynamic)
ar(static_cast<uint32_t>(m.rows()));
if constexpr(ArrT::ColsAtCompileTime==Eigen::Dynamic)
ar(static_cast<uint32_t>(m.cols()));
ar(binary_data(m.data(),static_cast<uint64_t>(m.rows()*m.cols()*sizeof(typename Derived::Scalar))));
}
}
template <class Archive, class Derived> inline
typename std::enable_if<traits::is_input_serializable<BinaryData<typename Derived::Scalar>, Archive>::value, void>::type
load(Archive& ar, Eigen::PlainObjectBase<Derived>& m) {
if constexpr(std::is_same_v<typename Derived::Scalar, WorldScalar>) {
for(int i = 0; i < m.rows(); i++)
ar(m.data()[i]);
}
else {
typedef Eigen::PlainObjectBase<Derived> ArrT;
const Eigen::Index rows=ArrT::RowsAtCompileTime, cols=ArrT::ColsAtCompileTime;
uint32_t r = rows;
uint32_t c = cols;
if constexpr(rows==Eigen::Dynamic)
ar(r);
if constexpr(cols==Eigen::Dynamic)
ar(c);
if constexpr(cols==Eigen::Dynamic || rows == Eigen::Dynamic)
m.resize(r, c);
ar(binary_data(m.data(),static_cast<uint64_t>(m.rows()*m.cols()*sizeof(typename Derived::Scalar))));
}
}
}
namespace Eigen {
// Some issue in nlohmann json prevents us from using Eigen::PlainObjectBase, so we have to specify the matrix class instead
template<typename Scalar, int Rows, int Cols> void to_json(nlohmann::json& j, const Eigen::Matrix<Scalar, Rows, Cols>& m) {
const Eigen::Index rows=m.RowsAtCompileTime, cols=m.ColsAtCompileTime;
j = nlohmann::json::array();
if(rows != Eigen::Dynamic && rows == 1) {
for(Eigen::Index k = 0; k < m.cols(); k++)
j.emplace_back(m(0, k));
}
else if(cols != Eigen::Dynamic && cols == 1) {
for(Eigen::Index k = 0; k < m.rows(); k++)
j.emplace_back(m(k, 0));
}
else {
for(size_t i = 0; i < m.rows(); i++) {
nlohmann::json a = nlohmann::json::array();
for(Eigen::Index k = 0; k < m.cols(); k++) {
a.emplace_back(m(i, k));
}
j.emplace_back(a);
}
}
}
template<typename Scalar, int Rows, int Cols> void from_json(const nlohmann::json& j, Eigen::Matrix<Scalar, Rows, Cols>& m) {
const Eigen::Index rows=m.RowsAtCompileTime, cols=m.ColsAtCompileTime;
if(rows != Eigen::Dynamic && rows == 1) {
m.resize(1, j.size());
for(Eigen::Index k = 0; k < m.cols(); k++)
m(0, k) = j[k];
}
else if(cols != Eigen::Dynamic && cols == 1) {
m.resize(j.size(), 1);
for(Eigen::Index k = 0; k < m.rows(); k++)
m(k, 0) = j[k];
}
else {
if(j.size() != 0 && j[0].size() != 0) {
m.resize(j.size(), j[0].size());
for(Eigen::Index i = 0; i < m.rows(); i++)
for(Eigen::Index k = 0; k < m.cols(); k++)
m(i, k) = j[i][k];
}
else
m.resize(0, 0);
}
}
}
void to_json(nlohmann::json& j, const SkColor4f& d);
void from_json(const nlohmann::json& j, SkColor4f& d);

View file

@ -0,0 +1,67 @@
#include "StringHelpers.hpp"
#include <fstream>
std::vector<std::string> split_string_by_token(std::string str, std::string token) {
// slightly modified from: https://stackoverflow.com/a/46943631
std::vector<std::string> toRet;
while(!str.empty()) {
size_t index = str.find(token);
if(index != std::string::npos) {
toRet.emplace_back(str.substr(0, index));
str = str.substr(index + token.size());
if(str.empty())
toRet.emplace_back(str);
}
else {
toRet.emplace_back(str);
return toRet;
}
}
return toRet;
}
uint8_t ascii_hex_char_to_number_no_checks(char asciiHex) {
if(asciiHex <= '9')
return static_cast<uint8_t>(asciiHex - '0');
else
return static_cast<uint8_t>(asciiHex - 'A');
}
std::string byte_vector_to_hex_str(const std::vector<uint8_t>& byteVec) {
static constexpr char charList[] = "0123456789ABCDEF";
std::string ret;
ret.reserve(byteVec.size() * 2);
for(uint8_t b : byteVec) {
ret.push_back(charList[b >> 4]);
ret.push_back(charList[b & 0x0F]);
}
return ret;
}
std::string read_file_to_string(const std::filesystem::path& filePath) {
std::string toRet;
// https://nullptr.org/cpp-read-file-into-string/
std::ifstream file(filePath, std::ios::in | std::ios::binary | std::ios::ate);
if(!file.is_open())
throw std::runtime_error("[read_file_to_string] Could not open file");
size_t resourcesize;
auto tellgResult = file.tellg();
if(tellgResult == -1)
throw std::runtime_error("[read_file_to_string] tellg failed for file");
resourcesize = static_cast<size_t>(tellgResult);
file.seekg(0, std::ios_base::beg);
toRet.resize(resourcesize);
file.read(toRet.data(), resourcesize);
file.close();
return toRet;
}

View file

@ -0,0 +1,14 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <filesystem>
std::vector<std::string> split_string_by_token(std::string str, std::string token);
// Hex character must be capitalized if it is a letter
uint8_t ascii_hex_char_to_number_no_checks(char asciiHex);
std::string byte_vector_to_hex_str(const std::vector<uint8_t>& byteVec);
std::string read_file_to_string(const std::filesystem::path& filePath);