Added args and user replication

This commit is contained in:
Chewico 2026-10-18 21:41:15 +02:00
parent f98d577b89
commit c8cafd00f8
22 changed files with 968 additions and 133 deletions

9
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"cmake.debugConfig": {
"args": [
"--single",
"--port",
"8080"
]
}
}

View File

@ -1,9 +0,0 @@
#include <deerith/network/router.h>
#include "network/network_internals.h"
#include <deerith/packets/sync_world_tick.h>
void deerith::setup_base_protocols()
{
router_add_packet_processor<sync_world_tick_packet_t>();
}

View File

@ -1,27 +1,36 @@
#include <deerith/engine.h>
#include <deerith/graphics/input.h>
#include <deerith/network/router.h>
#include <deerith/world/character_sync_interface.h>
#include <deerith/packets/sync_character_interface.h>
#include <deerith/network/packets.h>
#include <deerith/world/entity.h>
#include "world/world_internals.h"
namespace deerith
{
extern character_sync_interface_t character_sync_interface;
}
void deerith::process_player_input()
{
if (self_entity == entt::null)
return;
glm::vec2 movment = get_joystick_axes(0, 0);
character_sync_interface.move_input.push_value(movment);
entity_sync_client_position_t &entity_sync_position = get_component<entity_sync_client_position_t>(self_entity);
entity_sync_position.move_input = movment;
entity_sync_position.position = entity_sync_position.position.read_value() + movment * 0.1f;
}
void deerith::send_player_input()
{
sync_character_interface_t sync_character_interface;
sync_character_interface.load(character_sync_interface);
if (self_entity == entt::null)
return;
entity_sync_client_position_t &entity_sync_position = get_component<entity_sync_client_position_t>(self_entity);
router_send_packet<sync_character_interface_t>(sync_character_interface, get_server_client());
sync_entity_player_position_packet_t packet{};
packet.input_transport.load(entity_sync_position.move_input);
packet.position_transport.load(entity_sync_position.position);
router_send_packet(packet, get_server_client());
}

View File

@ -5,9 +5,14 @@
#include <deerith/world.h>
#include "world/world_internals.h"
#include "network/network_internals.h"
namespace deerith
void deerith::setup_base_protocols()
{
router_add_packet_processor<sync_world_tick_packet_t>();
router_add_packet_processor<create_player_entity_packet_t>();
router_add_packet_processor<create_entity_packet_t>();
router_add_packet_processor<sync_entities_coordinates_packet_t>();
}
template <>
@ -22,4 +27,31 @@ void deerith::process_packet(deerith::create_player_entity_packet_t &packet, cli
{
self_entity = create_entity();
add_component<entity_sync_client_position_t>(self_entity);
}
template <>
void deerith::process_packet(deerith::create_entity_packet_t &packet, client_t *client)
{
deerith_trace("creating entity with network id {}", packet.network_sync_id);
entity_t entity = create_entity();
entity_network_id_t &entity_network_id = add_entity_network_with_id(entity, packet.network_sync_id);
if (packet.add_entity_sync_coordinates)
{
deerith_trace("-> adding entity sync coordinates to entity");
entity_sync_coordinates_t &sync_coords = add_component<entity_sync_coordinates_t>(entity);
}
}
template <>
void deerith::process_packet(deerith::sync_entities_coordinates_packet_t &packet, client_t *client)
{
for (int i = 0; i < packet.entities_ids.size(); i++)
{
entity_t entity = get_network_entity_with_id(packet.entities_ids[i]);
entity_sync_coordinates_t &sync_coords = get_component<entity_sync_coordinates_t>(entity);
packet.positions_to_update[i].recive(sync_coords.position);
}
}

View File

@ -1,5 +0,0 @@
#include <deerith/world.h>
void deerith::render_world()
{
}

View File

@ -1,5 +1,20 @@
#include <deerith/world.h>
#include <deerith/world/entity.h>
#include <deerith/graphics/primitive.h>
void deerith::render_world()
{
define_primitive_viewspace(glm::vec2(-10, -10), glm::vec2(10, 10));
auto entities_view = get_entity_registry().view<entity_sync_coordinates_t>();
for (auto [entity, sync_coords] : entities_view.each())
{
glm::vec2 chords;
if (sync_coords.position.read_synced(chords))
{
define_primitive_color(glm::vec4(1, 1, 1, 1));
draw_primitive_rectangle(chords - glm::vec2(0.5f, 0.5f), chords + glm::vec2(0.5f, 0.5f));
}
}
}

View File

@ -0,0 +1,485 @@
#pragma once
#include <algorithm>
#include <sstream>
#include <limits>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <cassert>
namespace argh
{
// Terminology:
// A command line is composed of 2 types of args:
// 1. Positional args, i.e. free standing values
// 2. Options: args beginning with '-'. We identify two kinds:
// 2.1: Flags: boolean options => (exist ? true : false)
// 2.2: Parameters: a name followed by a non-option value
#if !defined(__GNUC__) || (__GNUC__ >= 5)
using string_stream = std::istringstream;
#else
// Until GCC 5, istringstream did not have a move constructor.
// stringstream_proxy is used instead, as a workaround.
class stringstream_proxy
{
public:
stringstream_proxy() = default;
// Construct with a value.
stringstream_proxy(std::string const& value) :
stream_(value)
{}
// Copy constructor.
stringstream_proxy(const stringstream_proxy& other) :
stream_(other.stream_.str())
{
stream_.setstate(other.stream_.rdstate());
}
void setstate(std::ios_base::iostate state) { stream_.setstate(state); }
// Stream out the value of the parameter.
// If the conversion was not possible, the stream will enter the fail state,
// and operator bool will return false.
template<typename T>
stringstream_proxy& operator >> (T& thing)
{
stream_ >> thing;
return *this;
}
// Get the string value.
std::string str() const { return stream_.str(); }
std::stringbuf* rdbuf() const { return stream_.rdbuf(); }
// Check the state of the stream.
// False when the most recent stream operation failed
explicit operator bool() const { return !!stream_; }
~stringstream_proxy() = default;
private:
std::istringstream stream_;
};
using string_stream = stringstream_proxy;
#endif
class multimap_iteration_wrapper
{
public:
using container_t = std::multimap<std::string, std::string>;
using iterator_t = container_t::const_iterator;
using difference_t = container_t::difference_type;
explicit multimap_iteration_wrapper(const iterator_t& lb, const iterator_t& ub)
: lb_(lb)
, ub_(ub)
{}
iterator_t begin() const { return lb_; }
iterator_t end() const { return ub_; }
difference_t size() const { return std::distance(lb_, ub_); }
private:
iterator_t lb_;
iterator_t ub_;
};
class parser
{
public:
enum Mode { PREFER_FLAG_FOR_UNREG_OPTION = 1 << 0,
PREFER_PARAM_FOR_UNREG_OPTION = 1 << 1,
NO_SPLIT_ON_EQUALSIGN = 1 << 2,
SINGLE_DASH_IS_MULTIFLAG = 1 << 3,
};
parser() = default;
parser(std::initializer_list<char const* const> pre_reg_names)
{ add_params(pre_reg_names); }
parser(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
{ parse(argv, mode); }
parser(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
{ parse(argc, argv, mode); }
void add_param(std::string const& name);
void add_params(std::string const& name);
void add_param(std::initializer_list<char const* const> init_list);
void add_params(std::initializer_list<char const* const> init_list);
void parse(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
void parse(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
std::multiset<std::string> const& flags() const { return flags_; }
std::multimap<std::string, std::string> const& params() const { return params_; }
multimap_iteration_wrapper params(std::string const& name) const;
std::vector<std::string> const& pos_args() const { return pos_args_; }
// begin() and end() for using range-for over positional args.
std::vector<std::string>::const_iterator begin() const { return pos_args_.cbegin(); }
std::vector<std::string>::const_iterator end() const { return pos_args_.cend(); }
size_t size() const { return pos_args_.size(); }
//////////////////////////////////////////////////////////////////////////
// Accessors
// flag (boolean) accessors: return true if the flag appeared, otherwise false.
bool operator[](std::string const& name) const;
// multiple flag (boolean) accessors: return true if at least one of the flag appeared, otherwise false.
bool operator[](std::initializer_list<char const* const> init_list) const;
// returns positional arg string by order. Like argv[] but without the options
std::string const& operator[](size_t ind) const;
// returns a std::istream that can be used to convert a positional arg to a typed value.
string_stream operator()(size_t ind) const;
// same as above, but with a default value in case the arg is missing (index out of range).
template<typename T>
string_stream operator()(size_t ind, T&& def_val) const;
// parameter accessors, give a name get an std::istream that can be used to convert to a typed value.
// call .str() on result to get as string
string_stream operator()(std::string const& name) const;
// accessor for a parameter with multiple names, give a list of names, get an std::istream that can be used to convert to a typed value.
// call .str() on result to get as string
// returns the first value in the list to be found.
string_stream operator()(std::initializer_list<char const* const> init_list) const;
// same as above, but with a default value in case the param was missing.
// Non-string def_val types must have an operator<<() (output stream operator)
// If T only has an input stream operator, pass the string version of the type as in "3" instead of 3.
template<typename T>
string_stream operator()(std::string const& name, T&& def_val) const;
// same as above but for a list of names. returns the first value to be found.
template<typename T>
string_stream operator()(std::initializer_list<char const* const> init_list, T&& def_val) const;
private:
string_stream bad_stream() const;
std::string trim_leading_dashes(std::string const& name) const;
bool is_number(std::string const& arg) const;
bool is_option(std::string const& arg) const;
bool got_flag(std::string const& name) const;
bool is_param(std::string const& name) const;
private:
std::vector<std::string> args_;
std::multimap<std::string, std::string> params_;
std::vector<std::string> pos_args_;
std::multiset<std::string> flags_;
std::set<std::string> registeredParams_;
std::string empty_;
};
//////////////////////////////////////////////////////////////////////////
inline void parser::parse(const char * const argv[], int mode)
{
int argc = 0;
for (auto argvp = argv; *argvp; ++argc, ++argvp);
parse(argc, argv, mode);
}
//////////////////////////////////////////////////////////////////////////
inline void parser::parse(int argc, const char* const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/)
{
// clear out possible previous parsing remnants
flags_.clear();
params_.clear();
pos_args_.clear();
// convert to strings
args_.resize(static_cast<decltype(args_)::size_type>(argc));
std::transform(argv, argv + argc, args_.begin(), [](const char* const arg) { return arg; });
// parse line
for (auto i = 0u; i < args_.size(); ++i)
{
if (!is_option(args_[i]))
{
pos_args_.emplace_back(args_[i]);
continue;
}
auto name = trim_leading_dashes(args_[i]);
if (!(mode & NO_SPLIT_ON_EQUALSIGN))
{
auto equalPos = name.find('=');
if (equalPos != std::string::npos)
{
params_.insert({ name.substr(0, equalPos), name.substr(equalPos + 1) });
continue;
}
}
// if the option is unregistered and should be a multi-flag
if (1 == (args_[i].size() - name.size()) && // single dash
argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode
!is_param(name)) // unregistered
{
std::string keep_param;
if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param
{
keep_param += name.back();
name.resize(name.size() - 1);
}
for (auto const& c : name)
{
flags_.emplace(std::string{ c });
}
if (!keep_param.empty())
{
name = keep_param;
}
else
{
continue; // do not consider other options for this arg
}
}
// any potential option will get as its value the next arg, unless that arg is an option too
// in that case it will be determined a flag.
if (i == args_.size() - 1 || is_option(args_[i + 1]))
{
flags_.emplace(name);
continue;
}
// if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it is skipped
// otherwise we have 2 modes:
// PREFER_FLAG_FOR_UNREG_OPTION: a non-registered 'name' is determined a flag.
// The following value (the next arg) will be a free parameter.
//
// PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the next arg
// will be the value of that option.
assert(!(mode & argh::parser::PREFER_FLAG_FOR_UNREG_OPTION)
|| !(mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION));
bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION;
if (is_param(name) || preferParam)
{
params_.insert({ name, args_[i + 1] });
++i; // skip next value, it is not a free parameter
continue;
}
else
{
flags_.emplace(name);
}
}
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::bad_stream() const
{
string_stream bad;
bad.setstate(std::ios_base::failbit);
return bad;
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::is_number(std::string const& arg) const
{
// inefficient but simple way to determine if a string is a number (which can start with a '-')
std::istringstream istr(arg);
double number;
istr >> number;
return !(istr.fail() || istr.bad());
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::is_option(std::string const& arg) const
{
assert(0 != arg.size());
if (is_number(arg))
return false;
return '-' == arg[0];
}
//////////////////////////////////////////////////////////////////////////
inline std::string parser::trim_leading_dashes(std::string const& name) const
{
auto pos = name.find_first_not_of('-');
return std::string::npos != pos ? name.substr(pos) : name;
}
//////////////////////////////////////////////////////////////////////////
inline bool argh::parser::got_flag(std::string const& name) const
{
return flags_.end() != flags_.find(trim_leading_dashes(name));
}
//////////////////////////////////////////////////////////////////////////
inline bool argh::parser::is_param(std::string const& name) const
{
return registeredParams_.count(name);
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::operator[](std::string const& name) const
{
return got_flag(name);
}
//////////////////////////////////////////////////////////////////////////
inline bool parser::operator[](std::initializer_list<char const* const> init_list) const
{
return std::any_of(init_list.begin(), init_list.end(), [&](char const* const name) { return got_flag(name); });
}
//////////////////////////////////////////////////////////////////////////
inline std::string const& parser::operator[](size_t ind) const
{
if (ind < pos_args_.size())
return pos_args_[ind];
return empty_;
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(std::string const& name) const
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
return bad_stream();
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(std::initializer_list<char const* const> init_list) const
{
for (auto& name : init_list)
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
}
return bad_stream();
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
string_stream parser::operator()(std::string const& name, T&& def_val) const
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str()); // use default
}
//////////////////////////////////////////////////////////////////////////
// same as above but for a list of names. returns the first value to be found.
template<typename T>
string_stream parser::operator()(std::initializer_list<char const* const> init_list, T&& def_val) const
{
for (auto& name : init_list)
{
auto optIt = params_.find(trim_leading_dashes(name));
if (params_.end() != optIt)
return string_stream(optIt->second);
}
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str()); // use default
}
//////////////////////////////////////////////////////////////////////////
inline string_stream parser::operator()(size_t ind) const
{
if (pos_args_.size() <= ind)
return bad_stream();
return string_stream(pos_args_[ind]);
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
string_stream parser::operator()(size_t ind, T&& def_val) const
{
if (pos_args_.size() <= ind)
{
std::ostringstream ostr;
ostr.precision(std::numeric_limits<long double>::max_digits10);
ostr << def_val;
return string_stream(ostr.str());
}
return string_stream(pos_args_[ind]);
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_param(std::string const& name)
{
registeredParams_.insert(trim_leading_dashes(name));
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_param(std::initializer_list<const char *const> init_list)
{
parser::add_params(init_list);
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_params(std::initializer_list<char const* const> init_list)
{
for (auto& name : init_list)
registeredParams_.insert(trim_leading_dashes(name));
}
//////////////////////////////////////////////////////////////////////////
inline void parser::add_params(const std::string &name)
{
parser::add_param(name);
}
//////////////////////////////////////////////////////////////////////////
inline multimap_iteration_wrapper parser::params(std::string const& name) const
{
auto trimmed_name = trim_leading_dashes(name);
return multimap_iteration_wrapper(params_.lower_bound(trimmed_name), params_.upper_bound(trimmed_name));
}
}

View File

@ -10,6 +10,8 @@
namespace deerith
{
using id_t = uint32_t;
void init_engine();
void shutdown_engine();

View File

@ -1,5 +1,8 @@
#pragma once
#include <deerith/engine.h>
#include <deerith/world/entity.h>
#include <vector>
namespace deerith
{
@ -22,4 +25,52 @@ namespace deerith
static constexpr uint16_t packet_id = 2;
static constexpr bool reliable = true;
};
struct sync_entity_player_position_packet_t
{
static constexpr uint16_t packet_id = 3;
static constexpr bool reliable = false;
sync_variable_transport_t<glm::vec2> position_transport;
sync_variable_transport_t<glm::vec2> input_transport;
template <typename Archive>
void serialize(Archive &archive)
{
archive(position_transport);
archive(input_transport);
}
};
struct create_entity_packet_t
{
static constexpr uint16_t packet_id = 4;
static constexpr bool reliable = true;
id_t network_sync_id;
bool add_entity_sync_coordinates;
template <typename Archive>
void serialize(Archive &archive)
{
archive(network_sync_id);
archive(add_entity_sync_coordinates);
}
};
struct sync_entities_coordinates_packet_t
{
static constexpr uint16_t packet_id = 5;
static constexpr bool reliable = false;
std::vector<id_t> entities_ids;
std::vector<sync_variable_transport_t<glm::vec2>> positions_to_update;
template <typename Archive>
void serialize(Archive &archive)
{
archive(entities_ids);
archive(positions_to_update);
}
};
}

View File

@ -4,6 +4,8 @@
#include <cereal/cereal.hpp>
#include <cereal/archives/portable_binary.hpp>
#include <cereal/types/array.hpp>
#include <cereal/types/vector.hpp>
#include <enet/enet.h>

View File

@ -1,9 +1,11 @@
#pragma once
#include <deerith/engine.h>
#include <deerith/world.h>
#include <glm/glm.hpp>
#include <deerith/engine.h>
#include <deerith/world/world_core.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cereal/types/array.hpp>
#include <deerith/core/serialization_ext.h>
@ -13,98 +15,195 @@
namespace deerith
{
inline size_t get_sync_variable_index(size_t tick = get_world_tick())
{
return ((size_t)tick) % BUFFER_SIZE;
};
template <typename T>
struct sync_variable_transport_t;
template <typename T>
struct sync_variable_entry_t
{
T value;
int tick_id = 0;
T value{};
uint32_t tick_id = 0;
template <typename Archive>
void serialize(Archive &archive)
{
archive(value);
archive(tick_id);
}
void serialize(Archive &archive);
};
template <typename T>
class sync_variable_t
{
public:
T pull_value()
{
sync_variable_entry_t<T> &entry = buffer[get_sync_variable_index()];
if (entry.tick_id != get_world_tick())
{
return T();
}
return entry.value;
}
T read_value() const;
operator T() const;
void push_value(const T &v)
{
set_value(v, get_world_tick() + NETWORK_DELAY);
}
bool read_synced(T &value) const;
void set_value(const T &v, int tick_id = 0)
{
if (tick_id > get_world_tick() + SYNC_VARIABLE_STORAGE)
{
deerith_error("set_value error, tick_id > get_world_tick() + SYNC_VARIABLE_STORAGE, tick_id : {}", tick_id);
return;
}
sync_variable_entry_t<T> &entry = buffer[get_sync_variable_index(tick_id)];
entry.tick_id = tick_id;
entry.value = v;
}
sync_variable_t &operator=(const T &value);
private:
std::array<sync_variable_entry_t<T>, BUFFER_SIZE> buffer;
friend sync_variable_transport_t<T>;
protected:
void assign_value(const T &value);
void receive_entry(const sync_variable_entry_t<T> &entry);
void backfill();
sync_variable_entry_t<T> last_assigned_value{};
std::array<sync_variable_entry_t<T>, BUFFER_SIZE> buffer{};
template <typename U>
friend struct sync_variable_transport_t;
};
template <typename T>
struct sync_variable_transport_t
{
std::array<sync_variable_entry_t<T>, NETWORK_DELAY> variable_transport_data;
std::array<sync_variable_entry_t<T>, NETWORK_DELAY> variable_transport_data{};
void load(const sync_variable_t<T> &sync_var)
{
int begin_tick_id = get_world_tick();
for (int i = 0; i < NETWORK_DELAY; i++)
{
variable_transport_data[i] =
sync_var.buffer[get_sync_variable_index(i + begin_tick_id)];
}
}
void dipose(sync_variable_t<T> &sync_var)
{
int begin_tick_id = get_world_tick();
for (int i = 0; i < NETWORK_DELAY; i++)
{
const sync_variable_entry_t<T> &entry = variable_transport_data[i];
if (entry.tick_id < begin_tick_id || entry.tick_id >= begin_tick_id + NETWORK_DELAY)
{
continue;
}
sync_var.buffer[get_sync_variable_index(entry.tick_id)] = entry;
}
}
void load(const sync_variable_t<T> &sync_var);
void recive(sync_variable_t<T> &sync_var);
template <typename Archive>
void serialize(Archive &archive)
{
archive(variable_transport_data);
}
void serialize(Archive &archive);
};
}
inline size_t get_sync_variable_index(uint32_t tick)
{
return static_cast<size_t>(tick) % BUFFER_SIZE;
}
}
// ============================================================================
// Implementations
// ============================================================================
template <typename T>
template <typename Archive>
void deerith::sync_variable_entry_t<T>::serialize(Archive &archive)
{
archive(value, tick_id);
}
template <typename T>
T deerith::sync_variable_t<T>::read_value() const
{
return static_cast<T>(*this);
}
template <typename T>
deerith::sync_variable_t<T>::operator T() const
{
const uint32_t tick = get_world_tick() + NETWORK_DELAY;
const auto &entry = buffer[get_sync_variable_index(tick)];
if (entry.tick_id == tick)
{
return entry.value;
}
return last_assigned_value.value;
}
template <typename T>
deerith::sync_variable_t<T> &deerith::sync_variable_t<T>::operator=(const T &value)
{
this->assign_value(value);
return *this;
}
template <typename T>
void deerith::sync_variable_t<T>::assign_value(const T &value)
{
backfill();
const uint32_t tick = get_world_tick() + NETWORK_DELAY;
auto &entry = buffer[get_sync_variable_index(tick)];
entry.value = value;
entry.tick_id = tick;
last_assigned_value = entry;
}
template <typename T>
void deerith::sync_variable_t<T>::receive_entry(const sync_variable_entry_t<T> &entry)
{
const uint32_t current_tick = get_world_tick();
if (entry.tick_id < current_tick || entry.tick_id >= current_tick + NETWORK_DELAY)
{
return;
}
buffer[get_sync_variable_index(entry.tick_id)] = entry;
}
template <typename T>
void deerith::sync_variable_t<T>::backfill()
{
const uint32_t current_tick = get_world_tick();
if (last_assigned_value.tick_id == 0 || last_assigned_value.tick_id >= current_tick + NETWORK_DELAY)
{
return;
}
const uint32_t start_tick = std::max(
last_assigned_value.tick_id,
current_tick > 0 ? current_tick - 1 : 0);
const T previous_value = last_assigned_value.value;
for (uint32_t tick = start_tick; tick < current_tick + NETWORK_DELAY; ++tick)
{
auto &entry = buffer[get_sync_variable_index(tick)];
entry.value = previous_value;
entry.tick_id = tick;
}
}
template <typename T>
bool deerith::sync_variable_t<T>::read_synced(T &value) const
{
const uint32_t tick = get_world_tick();
const auto &entry = buffer[get_sync_variable_index(tick)];
if (entry.tick_id != tick)
{
return false;
}
value = entry.value;
return true;
}
template <typename T>
void deerith::sync_variable_transport_t<T>::load(const sync_variable_t<T> &sync_var)
{
const uint32_t begin_tick_id = get_world_tick();
for (int i = 0; i < NETWORK_DELAY; ++i)
{
variable_transport_data[i] =
sync_var.buffer[get_sync_variable_index(i + begin_tick_id)];
}
}
template <typename T>
void deerith::sync_variable_transport_t<T>::recive(sync_variable_t<T> &sync_var)
{
const uint32_t begin_tick_id = get_world_tick();
for (int i = 0; i < NETWORK_DELAY; ++i)
{
const sync_variable_entry_t<T> &entry = variable_transport_data[i];
if (entry.tick_id < begin_tick_id || entry.tick_id >= begin_tick_id + NETWORK_DELAY)
{
continue;
}
sync_var.buffer[get_sync_variable_index(entry.tick_id)] = entry;
}
}
template <typename T>
template <typename Archive>
void deerith::sync_variable_transport_t<T>::serialize(Archive &archive)
{
archive(variable_transport_data);
}

View File

@ -10,10 +10,20 @@ namespace deerith
client_t *client = nullptr;
};
struct entity_network_id_t
{
id_t network_id;
};
// struct for linking a entity to a client
struct entity_sync_client_position_t
{
sync_variable_t<glm::vec2> move_input;
sync_variable_t<glm::vec2> position;
};
struct entity_sync_coordinates_t
{
sync_variable_t<glm::vec2> position;
};
}

View File

@ -4,6 +4,8 @@
namespace deerith
{
struct entity_network_id_t;
using entity_registry_t = entt::registry;
using entity_t = entt::entity;
@ -12,18 +14,28 @@ namespace deerith
void set_tick(uint32_t);
entity_registry_t &get_entity_registry_t();
inline entity_t create_entity() { return get_entity_registry_t().create(); }
entity_registry_t &get_entity_registry();
inline entity_t create_entity() { return get_entity_registry().create(); }
template <typename T>
T &add_component(entity_t ent)
{
return get_entity_registry_t().emplace<T>(ent);
return get_entity_registry().emplace<T>(ent);
}
template <typename T>
T &get_component(entity_t ent)
{
return get_entity_registry_t().get<T>(ent);
return get_entity_registry().get<T>(ent);
}
template <typename T>
bool has_component(entity_t ent)
{
return get_entity_registry().all_of<T>(ent);
}
entity_network_id_t &add_entity_network_id(entity_t ent);
entity_network_id_t &add_entity_network_with_id(entity_t ent, id_t id);
entity_t get_network_entity_with_id(id_t id);
}

View File

@ -1,11 +1,45 @@
#include <deerith/world/world_core.h>
#include <deerith/world/entity.h>
#include <vector>
namespace deerith
{
id_t sequential_network_id = 1;
std::unordered_map<uint32_t, entity_t> network_entities;
entity_registry_t entity_registry;
}
entt::registry &deerith::get_entity_registry_t()
entt::registry &deerith::get_entity_registry()
{
return entity_registry;
}
deerith::entity_network_id_t &deerith::add_entity_network_id(entity_t ent)
{
while (network_entities.contains(sequential_network_id))
{
sequential_network_id++;
}
entity_network_id_t &network_id = add_component<entity_network_id_t>(ent);
network_id.network_id = sequential_network_id;
network_entities[sequential_network_id] = ent;
sequential_network_id++;
return network_id;
}
deerith::entity_network_id_t &deerith::add_entity_network_with_id(entity_t ent, id_t id)
{
entity_network_id_t &network_id = add_component<entity_network_id_t>(ent);
network_id.network_id = id;
network_entities[id] = ent;
return network_id;
}
deerith::entity_t deerith::get_network_entity_with_id(id_t id)
{
return network_entities[id];
}

View File

@ -1,9 +1,9 @@
#include "network/network_internals.h"
#include <deerith/network/router.h>
#include <deerith/packets/sync_character_interface.h>
#include <deerith/network/packets.h>
void deerith::load_base_protocols()
{
router_add_packet_processor<sync_character_interface_t>();
router_add_packet_processor<sync_entity_player_position_packet_t>();
}

View File

@ -1,7 +1,7 @@
#include <deerith/core/log.h>
#include <deerith/network/client.h>
#include <deerith/network/router.h>
#include <deerith/packets/sync_world_tick.h>
#include <deerith/network/packets.h>
#include <deerith/world.h>

View File

@ -1,7 +1,12 @@
#include <deerith/engine.h>
#include <deerith/packets/sync_character_interface.h>
#include <deerith/world.h>
#include <deerith/world/entity.h>
#include <deerith/network/packets.h>
#include <deerith/network/router.h>
#include <deerith/network/client.h>
#include "network/network_internals.h"
#include "world/world_internals.h"
namespace deerith
{
@ -9,7 +14,55 @@ namespace deerith
}
template <>
void deerith::process_packet(sync_character_interface_t &interface, client_t *client)
void deerith::process_packet(sync_entity_player_position_packet_t &packet, client_t *client)
{
deerith_trace("sync player input {} {}");
entity_t entity = client->entity_id;
entity_sync_client_position_t &sync_client = get_component<entity_sync_client_position_t>(entity);
packet.input_transport.recive(sync_client.move_input);
packet.position_transport.recive(sync_client.position);
}
void deerith::client_connects(client_t *client)
{
// sync world tick
sync_world_tick_packet_t sync_world_tick;
sync_world_tick.tick_id = get_world_tick();
router_send_packet(sync_world_tick, client);
// create entity player entity
entity_t entity = create_entity();
entity_link_to_client_t &link = add_component<entity_link_to_client_t>(entity);
entity_sync_client_position_t &position_sync = add_component<entity_sync_client_position_t>(entity);
create_player_entity_packet_t create_player_entity;
router_send_packet(create_player_entity, client);
link.client = client;
client->entity_id = entity;
// create the server representation of the entity
add_entity_network_id(entity);
add_component<entity_sync_coordinates_t>(entity);
send_create_entity_to_all_clients(entity);
}
void deerith::send_create_entity_to_all_clients(deerith::entity_t ent)
{
entity_network_id_t &entity_network_id = get_component<entity_network_id_t>(ent);
create_entity_packet_t create_entity;
create_entity.network_sync_id = entity_network_id.network_id;
if (has_component<entity_link_to_client_t>(ent))
{
create_entity.add_entity_sync_coordinates = true;
}
auto view = get_entity_registry().view<entity_link_to_client_t>();
for (auto [entity, link_to_client] : view.each())
{
router_send_packet(create_entity, link_to_client.client);
}
}

View File

@ -6,7 +6,7 @@
#include "world/world_internals.h"
#include <deerith/world/entity.h>
#include <deerith/packets.h>
#include <deerith/network/packets.h>
#include <csignal>
#include <atomic>
@ -51,24 +51,12 @@ void deerith::world_simulation()
flush_network_events();
// process movments
update_players_characters();
if (get_world_tick() % 2 == 0)
{
send_entity_data();
}
wait_till_next_tick();
}
void deerith::client_connects(client_t *client)
{
sync_world_tick_packet_t sync_world_tick;
sync_world_tick.tick_id = get_world_tick();
create_player_entity_packet_t create_player_entity;
router_send_packet(sync_world_tick, client);
router_send_packet(create_player_entity, client);
entity_t entity = create_entity();
entity_link_to_client_t &link = add_component<entity_link_to_client_t>(entity);
entity_sync_client_position_t &position_sync = add_component<entity_sync_client_position_t>(entity);
link.client = client;
client->entity_id = entity;
}

View File

@ -1,8 +1,13 @@
#pragma once
#include <deerith/network/client.h>
#include <deerith/world/entity.h>
namespace deerith
{
void update_players_characters();
void client_connects(client_t *client);
void send_entity_data();
void send_create_entity_to_all_clients(entity_t ent);
}

View File

@ -1,5 +1,44 @@
#include "world/world_internals.h"
void update_players_characters()
#include <deerith/world.h>
#include <deerith/world/entity.h>
#include <deerith/network/packets.h>
#include <deerith/network/router.h>
void deerith::update_players_characters()
{
auto view = get_entity_registry().view<entity_sync_client_position_t, entity_link_to_client_t, entity_sync_coordinates_t>();
for (auto [entity, position_input, client, sync_coords] : view.each())
{
glm::vec2 ps{};
if (position_input.position.read_synced(ps))
{
sync_coords.position = ps;
}
else
{
sync_coords.position = sync_coords.position;
}
}
}
void deerith::send_entity_data()
{
sync_entities_coordinates_packet_t sync_entities_coordinates;
auto coordinates_view = get_entity_registry().view<entity_network_id_t, entity_sync_coordinates_t>();
for (auto [entity, network_id, sync_coords] : coordinates_view.each())
{
sync_entities_coordinates.entities_ids.push_back(network_id.network_id);
sync_entities_coordinates.positions_to_update.push_back({});
sync_variable_transport_t<glm::vec2> &position_trasport = sync_entities_coordinates.positions_to_update.back();
position_trasport.load(sync_coords.position);
}
auto clients_view = get_entity_registry().view<entity_link_to_client_t>();
for (auto [entity, client] : clients_view.each())
{
router_send_packet(sync_entities_coordinates, client.client);
}
}

View File

@ -1,21 +1,25 @@
#include <deerith/engine.h>
#include <deerith/world.h>
#include <thread>
#include <args/argh.h>
#ifdef DEERITH_CLIENT
#include <deerith/graphics/window.h>
#endif
int main(int args_count, const char **args)
int main(int argc, const char **argv)
{
deerith::init_engine();
auto cmdl = argh::parser(argc, argv);
int port = 8080;
cmdl("port", 8080) >> port;
#ifdef DEERITH_SERVER
deerith::run_world(8080);
#endif
#ifdef DEERITH_CLIENT
if (!deerith::execute_server("./SandboxServer", 8080))
if (cmdl[{"-s", "--single"}] && !deerith::execute_server("./SandboxServer", port))
{
deerith_error("could not start server");
deerith::shutdown_engine();
@ -24,7 +28,7 @@ int main(int args_count, const char **args)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (deerith::connect_to_world("127.0.0.1", 8080))
if (deerith::connect_to_world("127.0.0.1", port))
{
deerith_assert(deerith::init_window() == 0, "Failed to init_window");