diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..463c0e3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cmake.debugConfig": { + "args": [ + "--single", + "--port", + "8080" + ] + } +} \ No newline at end of file diff --git a/deerith_engine_client/src/network/base_protocol.cpp b/deerith_engine_client/src/network/base_protocol.cpp index 0c9c0d0..e69de29 100644 --- a/deerith_engine_client/src/network/base_protocol.cpp +++ b/deerith_engine_client/src/network/base_protocol.cpp @@ -1,9 +0,0 @@ -#include -#include "network/network_internals.h" - -#include - -void deerith::setup_base_protocols() -{ - router_add_packet_processor(); -} \ No newline at end of file diff --git a/deerith_engine_client/src/world/player_movment.cpp b/deerith_engine_client/src/world/player_movment.cpp index 58a47c8..4f4424f 100644 --- a/deerith_engine_client/src/world/player_movment.cpp +++ b/deerith_engine_client/src/world/player_movment.cpp @@ -1,27 +1,36 @@ #include #include #include - -#include -#include +#include +#include #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(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(self_entity); - router_send_packet(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()); } \ No newline at end of file diff --git a/deerith_engine_client/src/world/server_communication.cpp b/deerith_engine_client/src/world/server_communication.cpp index fa839ea..14bcf74 100644 --- a/deerith_engine_client/src/world/server_communication.cpp +++ b/deerith_engine_client/src/world/server_communication.cpp @@ -5,9 +5,14 @@ #include #include "world/world_internals.h" +#include "network/network_internals.h" -namespace deerith +void deerith::setup_base_protocols() { + router_add_packet_processor(); + router_add_packet_processor(); + router_add_packet_processor(); + router_add_packet_processor(); } template <> @@ -22,4 +27,31 @@ void deerith::process_packet(deerith::create_player_entity_packet_t &packet, cli { self_entity = create_entity(); add_component(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); + } +} + +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); + + packet.positions_to_update[i].recive(sync_coords.position); + } } \ No newline at end of file diff --git a/deerith_engine_client/src/world/universe_render.cpp b/deerith_engine_client/src/world/universe_render.cpp deleted file mode 100644 index a9e95ab..0000000 --- a/deerith_engine_client/src/world/universe_render.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -void deerith::render_world() -{ -} \ No newline at end of file diff --git a/deerith_engine_client/src/world/world_render.cpp b/deerith_engine_client/src/world/world_render.cpp index a9e95ab..00044ef 100644 --- a/deerith_engine_client/src/world/world_render.cpp +++ b/deerith_engine_client/src/world/world_render.cpp @@ -1,5 +1,20 @@ #include +#include + +#include void deerith::render_world() { + define_primitive_viewspace(glm::vec2(-10, -10), glm::vec2(10, 10)); + + auto entities_view = get_entity_registry().view(); + 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)); + } + } } \ No newline at end of file diff --git a/deerith_engine_core/include/args/argh.h b/deerith_engine_core/include/args/argh.h new file mode 100644 index 0000000..5c1d1cb --- /dev/null +++ b/deerith_engine_core/include/args/argh.h @@ -0,0 +1,485 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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 + 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; + 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 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 init_list); + void add_params(std::initializer_list 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 const& flags() const { return flags_; } + std::multimap const& params() const { return params_; } + multimap_iteration_wrapper params(std::string const& name) const; + std::vector const& pos_args() const { return pos_args_; } + + // begin() and end() for using range-for over positional args. + std::vector::const_iterator begin() const { return pos_args_.cbegin(); } + std::vector::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 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 + 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 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 + 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 + string_stream operator()(std::initializer_list 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 args_; + std::multimap params_; + std::vector pos_args_; + std::multiset flags_; + std::set 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(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 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 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 + 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::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 + string_stream parser::operator()(std::initializer_list 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::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 + string_stream parser::operator()(size_t ind, T&& def_val) const + { + if (pos_args_.size() <= ind) + { + std::ostringstream ostr; + ostr.precision(std::numeric_limits::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 init_list) + { + parser::add_params(init_list); + } + + ////////////////////////////////////////////////////////////////////////// + + inline void parser::add_params(std::initializer_list 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)); + } +} diff --git a/deerith_engine_core/include/deerith/engine.h b/deerith_engine_core/include/deerith/engine.h index c93d45a..d5cd602 100644 --- a/deerith_engine_core/include/deerith/engine.h +++ b/deerith_engine_core/include/deerith/engine.h @@ -10,6 +10,8 @@ namespace deerith { + using id_t = uint32_t; + void init_engine(); void shutdown_engine(); diff --git a/deerith_engine_core/include/deerith/network/packets.h b/deerith_engine_core/include/deerith/network/packets.h index 5391317..849a71c 100644 --- a/deerith_engine_core/include/deerith/network/packets.h +++ b/deerith_engine_core/include/deerith/network/packets.h @@ -1,5 +1,8 @@ #pragma once #include +#include + +#include 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 position_transport; + sync_variable_transport_t input_transport; + + template + 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 + 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 entities_ids; + std::vector> positions_to_update; + + template + void serialize(Archive &archive) + { + archive(entities_ids); + archive(positions_to_update); + } + }; } \ No newline at end of file diff --git a/deerith_engine_core/include/deerith/network/router.h b/deerith_engine_core/include/deerith/network/router.h index ac2a59d..b7793f8 100644 --- a/deerith_engine_core/include/deerith/network/router.h +++ b/deerith_engine_core/include/deerith/network/router.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include diff --git a/deerith_engine_core/include/deerith/network/sync_variable.h b/deerith_engine_core/include/deerith/network/sync_variable.h index f0fe8c4..90a7bc7 100644 --- a/deerith_engine_core/include/deerith/network/sync_variable.h +++ b/deerith_engine_core/include/deerith/network/sync_variable.h @@ -1,9 +1,11 @@ #pragma once -#include -#include -#include +#include +#include + +#include #include +#include #include #include @@ -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 - struct sync_variable_transport_t; - template struct sync_variable_entry_t { - T value; - int tick_id = 0; + T value{}; + uint32_t tick_id = 0; template - void serialize(Archive &archive) - { - archive(value); - archive(tick_id); - } + void serialize(Archive &archive); }; template class sync_variable_t { public: - T pull_value() - { - sync_variable_entry_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 &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, BUFFER_SIZE> buffer; - friend sync_variable_transport_t; + protected: + void assign_value(const T &value); + void receive_entry(const sync_variable_entry_t &entry); + void backfill(); + + sync_variable_entry_t last_assigned_value{}; + std::array, BUFFER_SIZE> buffer{}; + + template + friend struct sync_variable_transport_t; }; template struct sync_variable_transport_t { - std::array, NETWORK_DELAY> variable_transport_data; + std::array, NETWORK_DELAY> variable_transport_data{}; - void load(const sync_variable_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 &sync_var) - { - int begin_tick_id = get_world_tick(); - for (int i = 0; i < NETWORK_DELAY; i++) - { - const sync_variable_entry_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 &sync_var); + void recive(sync_variable_t &sync_var); template - void serialize(Archive &archive) - { - archive(variable_transport_data); - } + void serialize(Archive &archive); }; -} \ No newline at end of file + + inline size_t get_sync_variable_index(uint32_t tick) + { + return static_cast(tick) % BUFFER_SIZE; + } +} + +// ============================================================================ +// Implementations +// ============================================================================ + +template +template +void deerith::sync_variable_entry_t::serialize(Archive &archive) +{ + archive(value, tick_id); +} + +template +T deerith::sync_variable_t::read_value() const +{ + return static_cast(*this); +} + +template +deerith::sync_variable_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 +deerith::sync_variable_t &deerith::sync_variable_t::operator=(const T &value) +{ + this->assign_value(value); + return *this; +} + +template +void deerith::sync_variable_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 +void deerith::sync_variable_t::receive_entry(const sync_variable_entry_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 +void deerith::sync_variable_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 +bool deerith::sync_variable_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 +void deerith::sync_variable_transport_t::load(const sync_variable_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 +void deerith::sync_variable_transport_t::recive(sync_variable_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 &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 +template +void deerith::sync_variable_transport_t::serialize(Archive &archive) +{ + archive(variable_transport_data); +} diff --git a/deerith_engine_core/include/deerith/world/entity.h b/deerith_engine_core/include/deerith/world/entity.h index b66a437..8a4759d 100644 --- a/deerith_engine_core/include/deerith/world/entity.h +++ b/deerith_engine_core/include/deerith/world/entity.h @@ -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 move_input; sync_variable_t position; }; + + struct entity_sync_coordinates_t + { + sync_variable_t position; + }; } \ No newline at end of file diff --git a/deerith_engine_core/include/deerith/world/world_core.h b/deerith_engine_core/include/deerith/world/world_core.h index 0af86fc..3367153 100644 --- a/deerith_engine_core/include/deerith/world/world_core.h +++ b/deerith_engine_core/include/deerith/world/world_core.h @@ -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 T &add_component(entity_t ent) { - return get_entity_registry_t().emplace(ent); + return get_entity_registry().emplace(ent); } template T &get_component(entity_t ent) { - return get_entity_registry_t().get(ent); + return get_entity_registry().get(ent); } + + template + bool has_component(entity_t ent) + { + return get_entity_registry().all_of(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); } diff --git a/deerith_engine_core/src/world.cpp b/deerith_engine_core/src/world.cpp index c305590..f11390e 100644 --- a/deerith_engine_core/src/world.cpp +++ b/deerith_engine_core/src/world.cpp @@ -1,11 +1,45 @@ #include +#include + +#include namespace deerith { + id_t sequential_network_id = 1; + std::unordered_map 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(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(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]; +} \ No newline at end of file diff --git a/deerith_engine_server/src/network/base_protocols.cpp b/deerith_engine_server/src/network/base_protocols.cpp index 742e671..220dd4f 100644 --- a/deerith_engine_server/src/network/base_protocols.cpp +++ b/deerith_engine_server/src/network/base_protocols.cpp @@ -1,9 +1,9 @@ #include "network/network_internals.h" #include -#include +#include void deerith::load_base_protocols() { - router_add_packet_processor(); + router_add_packet_processor(); } \ No newline at end of file diff --git a/deerith_engine_server/src/network/network.cpp b/deerith_engine_server/src/network/network.cpp index fa6bb56..eb222fb 100644 --- a/deerith_engine_server/src/network/network.cpp +++ b/deerith_engine_server/src/network/network.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include diff --git a/deerith_engine_server/src/world/character_sync.cpp b/deerith_engine_server/src/world/character_sync.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/deerith_engine_server/src/world/player_communication.cpp b/deerith_engine_server/src/world/player_communication.cpp index 774598f..4e4a703 100644 --- a/deerith_engine_server/src/world/player_communication.cpp +++ b/deerith_engine_server/src/world/player_communication.cpp @@ -1,7 +1,12 @@ #include - -#include +#include +#include +#include #include +#include + +#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); + + 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); + entity_sync_client_position_t &position_sync = add_component(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); + + 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(ent); + + create_entity_packet_t create_entity; + create_entity.network_sync_id = entity_network_id.network_id; + + if (has_component(ent)) + { + create_entity.add_entity_sync_coordinates = true; + } + + auto view = get_entity_registry().view(); + for (auto [entity, link_to_client] : view.each()) + { + router_send_packet(create_entity, link_to_client.client); + } +} \ No newline at end of file diff --git a/deerith_engine_server/src/world.cpp b/deerith_engine_server/src/world/world.cpp similarity index 60% rename from deerith_engine_server/src/world.cpp rename to deerith_engine_server/src/world/world.cpp index 6fba7de..534bb56 100644 --- a/deerith_engine_server/src/world.cpp +++ b/deerith_engine_server/src/world/world.cpp @@ -6,7 +6,7 @@ #include "world/world_internals.h" #include -#include +#include #include #include @@ -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); - entity_sync_client_position_t &position_sync = add_component(entity); - - link.client = client; - client->entity_id = entity; -} diff --git a/deerith_engine_server/src/world/world_internals.h b/deerith_engine_server/src/world/world_internals.h index 2e3eb97..160ce31 100644 --- a/deerith_engine_server/src/world/world_internals.h +++ b/deerith_engine_server/src/world/world_internals.h @@ -1,8 +1,13 @@ #pragma once #include +#include 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); } \ No newline at end of file diff --git a/deerith_engine_server/src/world/world_simulation.cpp b/deerith_engine_server/src/world/world_simulation.cpp index 116967b..5b409e5 100644 --- a/deerith_engine_server/src/world/world_simulation.cpp +++ b/deerith_engine_server/src/world/world_simulation.cpp @@ -1,5 +1,44 @@ #include "world/world_internals.h" -void update_players_characters() +#include +#include +#include +#include + +void deerith::update_players_characters() { + auto view = get_entity_registry().view(); + + 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(); + 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 &position_trasport = sync_entities_coordinates.positions_to_update.back(); + position_trasport.load(sync_coords.position); + } + + auto clients_view = get_entity_registry().view(); + for (auto [entity, client] : clients_view.each()) + { + router_send_packet(sync_entities_coordinates, client.client); + } } \ No newline at end of file diff --git a/sandbox/src/runtime/main.cpp b/sandbox/src/runtime/main.cpp index 0d542e8..dc5eb88 100644 --- a/sandbox/src/runtime/main.cpp +++ b/sandbox/src/runtime/main.cpp @@ -1,21 +1,25 @@ #include #include #include +#include #ifdef DEERITH_CLIENT #include #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");