Multiplayer working

This commit is contained in:
Chewico 2026-10-20 16:22:10 +02:00
parent 106d5ced0b
commit 2af77fa468
15 changed files with 350 additions and 188 deletions

View File

@ -3,7 +3,7 @@
"args": [ "args": [
"--single", "--single",
"--port", "--port",
"8080" "8080-"
] ]
} }
} }

View File

@ -14,4 +14,5 @@ namespace deerith
float get_window_aspect_ratio(); float get_window_aspect_ratio();
int get_current_frame_id(); int get_current_frame_id();
bool is_windows_focused();
} }

View File

@ -0,0 +1,9 @@
#pragma once
#include <deerith/engine.h>
namespace deerith
{
struct chunk_render_t
{
};
}

View File

@ -1,4 +1,5 @@
#include "deerith/graphics/shader.h" #include "deerith/graphics/shader.h"
#include <deerith/engine.h>
#include <glad/gl.h> #include <glad/gl.h>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
@ -6,6 +7,24 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <filesystem>
std::filesystem::path get_executable_directory()
{
std::array<char, 4096> buffer{};
const auto length = readlink(
"/proc/self/exe",
buffer.data(),
buffer.size() - 1);
if (length == -1)
throw std::runtime_error("Failed to get executable path");
return std::filesystem::path(
std::string(buffer.data(), length))
.parent_path();
}
deerith::Shader::Shader(const char *vertexPath, const char *fragmentPath) deerith::Shader::Shader(const char *vertexPath, const char *fragmentPath)
{ {
@ -23,10 +42,11 @@ deerith::Shader::Shader(const char *vertexPath, const char *fragmentPath)
std::ifstream::failbit | std::ifstream::failbit |
std::ifstream::badbit); std::ifstream::badbit);
std::filesystem::path cwd = get_executable_directory();
try try
{ {
vertexFile.open(vertexPath); vertexFile.open(cwd / vertexPath);
fragmentFile.open(fragmentPath); fragmentFile.open(cwd / fragmentPath);
std::stringstream vertexStream; std::stringstream vertexStream;
std::stringstream fragmentStream; std::stringstream fragmentStream;

View File

@ -77,6 +77,11 @@ int deerith::init_window()
return 0; return 0;
} }
bool deerith::is_windows_focused()
{
return window && glfwGetWindowAttrib(window, GLFW_FOCUSED);
}
bool deerith::begin_window_display() bool deerith::begin_window_display()
{ {
glfwPollEvents(); glfwPollEvents();

View File

@ -1,5 +1,6 @@
#include <deerith/engine.h> #include <deerith/engine.h>
#include <deerith/graphics/input.h> #include <deerith/graphics/input.h>
#include <deerith/graphics/window.h>
#include <deerith/network/router.h> #include <deerith/network/router.h>
#include <deerith/network/packets.h> #include <deerith/network/packets.h>
#include <deerith/world/entity.h> #include <deerith/world/entity.h>
@ -15,7 +16,13 @@ void deerith::process_player_input()
{ {
if (self_entity == entt::null) if (self_entity == entt::null)
return; return;
glm::vec2 movment = get_joystick_axes(0, 0); glm::vec2 movment = get_joystick_axes(0, 0);
if (!is_windows_focused())
{
movment = glm::vec2(0, 0);
}
entity_sync_client_position_t &entity_sync_position = get_component<entity_sync_client_position_t>(self_entity); 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.move_input = movment;

View File

@ -13,6 +13,7 @@ void deerith::setup_base_protocols()
router_add_packet_processor<create_player_entity_packet_t>(); router_add_packet_processor<create_player_entity_packet_t>();
router_add_packet_processor<create_entity_packet_t>(); router_add_packet_processor<create_entity_packet_t>();
router_add_packet_processor<sync_entities_coordinates_packet_t>(); router_add_packet_processor<sync_entities_coordinates_packet_t>();
router_add_packet_processor<destroy_entity_packet_t>();
} }
template <> template <>
@ -34,7 +35,17 @@ void deerith::process_packet(deerith::create_entity_packet_t &packet, client_t *
{ {
deerith_trace("creating entity with network id {}", packet.network_sync_id); deerith_trace("creating entity with network id {}", packet.network_sync_id);
entity_t entity = create_entity(); entity_t entity;
if (packet.is_self_client)
{
deerith_trace("creating self entity");
entity = self_entity;
}
else
{
entity = create_entity();
}
entity_network_id_t &entity_network_id = add_entity_network_with_id(entity, packet.network_sync_id); entity_network_id_t &entity_network_id = add_entity_network_with_id(entity, packet.network_sync_id);
if (packet.add_entity_sync_coordinates) if (packet.add_entity_sync_coordinates)
@ -60,3 +71,11 @@ void deerith::process_packet(deerith::sync_entities_coordinates_packet_t &packet
packet.positions_to_update[i].recive(sync_coords.position); packet.positions_to_update[i].recive(sync_coords.position);
} }
} }
template <>
void deerith::process_packet(deerith::destroy_entity_packet_t &packet, client_t *client)
{
deerith_trace("destroying entity : {}", packet.network_sync_id);
entity_t specific_entity = get_network_entity_with_id(packet.network_sync_id);
get_entity_registry().destroy(specific_entity);
}

View File

@ -7,7 +7,7 @@ void deerith::render_world()
{ {
define_primitive_viewspace(glm::vec2(-10, -10), glm::vec2(10, 10)); define_primitive_viewspace(glm::vec2(-10, -10), glm::vec2(10, 10));
auto entities_view = get_entity_registry().view<entity_sync_coordinates_t>(); auto entities_view = get_entity_registry().view<entity_sync_coordinates_t>(entt::exclude<entity_sync_client_position_t>);
for (auto [entity, sync_coords] : entities_view.each()) for (auto [entity, sync_coords] : entities_view.each())
{ {
glm::vec2 chords; glm::vec2 chords;
@ -17,4 +17,12 @@ void deerith::render_world()
draw_primitive_rectangle(chords - glm::vec2(0.5f, 0.5f), chords + glm::vec2(0.5f, 0.5f)); draw_primitive_rectangle(chords - glm::vec2(0.5f, 0.5f), chords + glm::vec2(0.5f, 0.5f));
} }
} }
// we should expect only one registry
auto self_view = get_entity_registry().view<entity_sync_coordinates_t, entity_sync_client_position_t>();
for (auto [entity, sync_coords, client_sync_coords] : self_view.each())
{
define_primitive_color(glm::vec4(1, 0.5f, 0.6f, 1));
draw_primitive_rectangle(client_sync_coords.position.read_value() - glm::vec2(0.5f, 0.5f), client_sync_coords.position.read_value() + glm::vec2(0.5f, 0.5f));
}
} }

View File

@ -84,7 +84,10 @@ namespace cereal
public: public:
//! Represents desired endianness //! Represents desired endianness
enum class Endianness : std::uint8_t enum class Endianness : std::uint8_t
{ big, little }; {
big,
little
};
//! Default options, preserve system endianness //! Default options, preserve system endianness
static Options Default() { return Options(); } static Options Default() { return Options(); }
@ -97,17 +100,20 @@ namespace cereal
//! Specify specific options for the PortableBinaryOutputArchive //! Specify specific options for the PortableBinaryOutputArchive
/*! @param outputEndian The desired endianness of saved (output) data */ /*! @param outputEndian The desired endianness of saved (output) data */
explicit Options( Endianness outputEndian = getEndianness() ) : explicit Options(Endianness outputEndian = getEndianness()) : itsOutputEndianness(outputEndian) {}
itsOutputEndianness( outputEndian ) { }
private: private:
//! Gets the endianness of the system //! Gets the endianness of the system
inline static Endianness getEndianness() inline static Endianness getEndianness()
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; } {
return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big;
}
//! Checks if Options is set for little endian //! Checks if Options is set for little endian
inline std::uint8_t is_little_endian() const inline std::uint8_t is_little_endian() const
{ return itsOutputEndianness == Endianness::little; } {
return itsOutputEndianness == Endianness::little;
}
friend class PortableBinaryOutputArchive; friend class PortableBinaryOutputArchive;
Endianness itsOutputEndianness; Endianness itsOutputEndianness;
@ -117,8 +123,7 @@ namespace cereal
/*! @param stream The stream to output to. Should be opened with std::ios::binary flag. /*! @param stream The stream to output to. Should be opened with std::ios::binary flag.
@param options The PortableBinary specific options to use. See the Options struct @param options The PortableBinary specific options to use. See the Options struct
for the values of default parameters */ for the values of default parameters */
PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) : PortableBinaryOutputArchive(std::ostream &stream, Options const &options = Options::Default()) : OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
itsStream(stream), itsStream(stream),
itsConvertEndianness(portable_binary_detail::is_little_endian() ^ options.is_little_endian()) itsConvertEndianness(portable_binary_detail::is_little_endian() ^ options.is_little_endian())
{ {
@ -128,8 +133,8 @@ namespace cereal
~PortableBinaryOutputArchive() CEREAL_NOEXCEPT = default; ~PortableBinaryOutputArchive() CEREAL_NOEXCEPT = default;
//! Writes size bytes of data to the output stream //! Writes size bytes of data to the output stream
template <std::streamsize DataSize> inline template <std::streamsize DataSize>
void saveBinary( const void * data, std::streamsize size ) inline void saveBinary(const void *data, std::streamsize size)
{ {
std::streamsize writtenSize = 0; std::streamsize writtenSize = 0;
@ -186,7 +191,10 @@ namespace cereal
public: public:
//! Represents desired endianness //! Represents desired endianness
enum class Endianness : std::uint8_t enum class Endianness : std::uint8_t
{ big, little }; {
big,
little
};
//! Default options, preserve system endianness //! Default options, preserve system endianness
static Options Default() { return Options(); } static Options Default() { return Options(); }
@ -199,17 +207,20 @@ namespace cereal
//! Specify specific options for the PortableBinaryInputArchive //! Specify specific options for the PortableBinaryInputArchive
/*! @param inputEndian The desired endianness of loaded (input) data */ /*! @param inputEndian The desired endianness of loaded (input) data */
explicit Options( Endianness inputEndian = getEndianness() ) : explicit Options(Endianness inputEndian = getEndianness()) : itsInputEndianness(inputEndian) {}
itsInputEndianness( inputEndian ) { }
private: private:
//! Gets the endianness of the system //! Gets the endianness of the system
inline static Endianness getEndianness() inline static Endianness getEndianness()
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; } {
return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big;
}
//! Checks if Options is set for little endian //! Checks if Options is set for little endian
inline std::uint8_t is_little_endian() const inline std::uint8_t is_little_endian() const
{ return itsInputEndianness == Endianness::little; } {
return itsInputEndianness == Endianness::little;
}
friend class PortableBinaryInputArchive; friend class PortableBinaryInputArchive;
Endianness itsInputEndianness; Endianness itsInputEndianness;
@ -219,8 +230,7 @@ namespace cereal
/*! @param stream The stream to read from. Should be opened with std::ios::binary flag. /*! @param stream The stream to read from. Should be opened with std::ios::binary flag.
@param options The PortableBinary specific options to use. See the Options struct @param options The PortableBinary specific options to use. See the Options struct
for the values of default parameters */ for the values of default parameters */
PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) : PortableBinaryInputArchive(std::istream &stream, Options const &options = Options::Default()) : InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
itsStream(stream), itsStream(stream),
itsConvertEndianness(false) itsConvertEndianness(false)
{ {
@ -235,8 +245,8 @@ namespace cereal
/*! @param data The data to save /*! @param data The data to save
@param size The number of bytes in the data @param size The number of bytes in the data
@tparam DataSize T The size of the actual type of the data elements being loaded */ @tparam DataSize T The size of the actual type of the data elements being loaded */
template <std::streamsize DataSize> inline template <std::streamsize DataSize>
void loadBinary( void * const data, std::streamsize size ) inline void loadBinary(void *const data, std::streamsize size)
{ {
// load data // load data
auto const readSize = itsStream.rdbuf()->sgetn(reinterpret_cast<char *>(data), size); auto const readSize = itsStream.rdbuf()->sgetn(reinterpret_cast<char *>(data), size);
@ -262,7 +272,8 @@ namespace cereal
// Common BinaryArchive serialization functions // Common BinaryArchive serialization functions
//! Saving for POD types to portable binary //! Saving for POD types to portable binary
template<class T> inline template <class T>
inline
typename std::enable_if<std::is_arithmetic<T>::value, void>::type typename std::enable_if<std::is_arithmetic<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive &ar, T const &t) CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive &ar, T const &t)
{ {
@ -273,7 +284,8 @@ namespace cereal
} }
//! Loading for POD types from portable binary //! Loading for POD types from portable binary
template<class T> inline template <class T>
inline
typename std::enable_if<std::is_arithmetic<T>::value, void>::type typename std::enable_if<std::is_arithmetic<T>::value, void>::type
CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive &ar, T &t) CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive &ar, T &t)
{ {
@ -284,24 +296,24 @@ namespace cereal
} }
//! Serializing NVP types to portable binary //! Serializing NVP types to portable binary
template <class Archive, class T> inline template <class Archive, class T>
CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) inline CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
CEREAL_SERIALIZE_FUNCTION_NAME(Archive &ar, NameValuePair<T> &t) CEREAL_SERIALIZE_FUNCTION_NAME(Archive &ar, NameValuePair<T> &t)
{ {
ar(t.value); ar(t.value);
} }
//! Serializing SizeTags to portable binary //! Serializing SizeTags to portable binary
template <class Archive, class T> inline template <class Archive, class T>
CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) inline CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
CEREAL_SERIALIZE_FUNCTION_NAME(Archive &ar, SizeTag<T> &t) CEREAL_SERIALIZE_FUNCTION_NAME(Archive &ar, SizeTag<T> &t)
{ {
ar(t.size); ar(t.size);
} }
//! Saving binary data to portable binary //! Saving binary data to portable binary
template <class T> inline template <class T>
void CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar, BinaryData<T> const & bd) inline void CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive &ar, BinaryData<T> const &bd)
{ {
typedef typename std::remove_pointer<T>::type TT; typedef typename std::remove_pointer<T>::type TT;
static_assert(!std::is_floating_point<TT>::value || static_assert(!std::is_floating_point<TT>::value ||
@ -312,8 +324,8 @@ namespace cereal
} }
//! Loading binary data from portable binary //! Loading binary data from portable binary
template <class T> inline template <class T>
void CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar, BinaryData<T> & bd) inline void CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive &ar, BinaryData<T> &bd)
{ {
typedef typename std::remove_pointer<T>::type TT; typedef typename std::remove_pointer<T>::type TT;
static_assert(!std::is_floating_point<TT>::value || static_assert(!std::is_floating_point<TT>::value ||

View File

@ -47,14 +47,16 @@ namespace deerith
static constexpr uint16_t packet_id = 4; static constexpr uint16_t packet_id = 4;
static constexpr bool reliable = true; static constexpr bool reliable = true;
id_t network_sync_id; id_t network_sync_id = 0;
bool add_entity_sync_coordinates; bool add_entity_sync_coordinates = false;
bool is_self_client = false;
template <typename Archive> template <typename Archive>
void serialize(Archive &archive) void serialize(Archive &archive)
{ {
archive(network_sync_id); archive(network_sync_id);
archive(add_entity_sync_coordinates); archive(add_entity_sync_coordinates);
archive(is_self_client);
} }
}; };
@ -73,4 +75,18 @@ namespace deerith
archive(positions_to_update); archive(positions_to_update);
} }
}; };
struct destroy_entity_packet_t
{
static constexpr uint16_t packet_id = 6;
static constexpr bool reliable = true;
id_t network_sync_id = 0;
template <typename Archive>
void serialize(Archive &archive)
{
archive(network_sync_id);
}
};
} }

View File

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

View File

@ -21,6 +21,21 @@ namespace deerith
std::vector<std::unique_ptr<client_t>> clients; std::vector<std::unique_ptr<client_t>> clients;
void create_client(ENetPeer *peer); void create_client(ENetPeer *peer);
void delete_client(client_t *client);
}
void deerith::delete_client(client_t *client)
{
auto it = std::find_if(
clients.begin(),
clients.end(),
[client](const std::unique_ptr<client_t> &ptr)
{ return ptr.get() == client; });
if (it != clients.end())
{
clients.erase(it);
}
} }
bool deerith::init_server(int port) bool deerith::init_server(int port)
@ -71,6 +86,8 @@ void deerith::flush_network_events()
break; break;
case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT:
client_disconnects((client_t *)event.peer->data);
delete_client((client_t *)event.peer->data);
event.peer->data = NULL; event.peer->data = NULL;
break; break;

View File

@ -12,6 +12,10 @@ namespace deerith
{ {
} }
void deerith::load_base_protocols()
{
router_add_packet_processor<sync_entity_player_position_packet_t>();
}
template <> template <>
void deerith::process_packet(sync_entity_player_position_packet_t &packet, client_t *client) void deerith::process_packet(sync_entity_player_position_packet_t &packet, client_t *client)
@ -32,37 +36,85 @@ void deerith::client_connects(client_t *client)
// create entity player entity // create entity player entity
entity_t entity = create_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); entity_sync_client_position_t &position_sync = add_component<entity_sync_client_position_t>(entity);
create_player_entity_packet_t create_player_entity; create_player_entity_packet_t create_player_entity;
router_send_packet(create_player_entity, client); router_send_packet(create_player_entity, client);
link.client = client;
client->entity_id = entity;
// create the server representation of the entity // create the server representation of the entity
add_entity_network_id(entity); add_entity_network_id(entity);
add_component<entity_sync_coordinates_t>(entity); add_component<entity_sync_coordinates_t>(entity);
send_create_entity_to_all_clients(entity); send_create_entity_to_all_clients(entity);
// we do this the last to avoid send_create_entity_to_all_clients send_create_actual_entities_to_client create the same entity for the player
entity_link_to_client_t &link = add_component<entity_link_to_client_t>(entity);
client->entity_id = entity;
link.client = client;
send_create_actual_entities_to_client(client);
} }
void deerith::send_create_entity_to_all_clients(deerith::entity_t ent) void deerith::send_create_entity_to_all_clients(deerith::entity_t ent)
{
auto view = get_entity_registry().view<entity_link_to_client_t>();
for (auto [entity, link_to_client] : view.each())
{
send_create_entity_to_client(ent, link_to_client.client);
}
}
void deerith::send_create_entity_to_client(entity_t ent, client_t *client)
{ {
entity_network_id_t &entity_network_id = get_component<entity_network_id_t>(ent); entity_network_id_t &entity_network_id = get_component<entity_network_id_t>(ent);
create_entity_packet_t create_entity; create_entity_packet_t create_entity;
create_entity.network_sync_id = entity_network_id.network_id; create_entity.network_sync_id = entity_network_id.network_id;
// in case its the self entity we let it know to the client
if (has_component<entity_link_to_client_t>(ent)) if (has_component<entity_link_to_client_t>(ent))
{
entity_link_to_client_t &link = get_component<entity_link_to_client_t>(ent);
create_entity.is_self_client = link.client == client;
deerith_trace("entity could be self {}", create_entity.is_self_client);
}
if (has_component<entity_sync_coordinates_t>(ent))
{ {
create_entity.add_entity_sync_coordinates = true; create_entity.add_entity_sync_coordinates = true;
} }
router_send_packet(create_entity, client);
}
void deerith::client_disconnects(client_t *client)
{
entity_t client_entity = client->entity_id;
send_delete_entity_to_all_clients(client_entity);
get_entity_registry().destroy(client_entity);
}
void deerith::send_delete_entity_to_all_clients(entity_t ent)
{
destroy_entity_packet_t destroy_entity;
entity_network_id_t &network_id = get_component<entity_network_id_t>(ent);
destroy_entity.network_sync_id = network_id.network_id;
auto view = get_entity_registry().view<entity_link_to_client_t>(); auto view = get_entity_registry().view<entity_link_to_client_t>();
for (auto [entity, link_to_client] : view.each()) for (auto [entity, link_to_client] : view.each())
{ {
router_send_packet(create_entity, link_to_client.client); router_send_packet(destroy_entity, link_to_client.client);
}
}
void deerith::send_create_actual_entities_to_client(client_t *client)
{
auto entities_view = get_entity_registry().view<entity_network_id_t>();
for (auto [ent, network_id] : entities_view.each())
{
send_create_entity_to_client(ent, client);
} }
} }

View File

@ -6,8 +6,13 @@ namespace deerith
{ {
void update_players_characters(); void update_players_characters();
void client_connects(client_t *client); void client_connects(client_t *client);
void client_disconnects(client_t *client);
void send_entity_data(); void send_entity_data();
void send_create_entity_to_all_clients(entity_t ent); void send_create_entity_to_all_clients(entity_t ent);
void send_create_entity_to_client(entity_t ent, client_t *client);
void send_create_actual_entities_to_client(client_t *client);
void send_delete_entity_to_all_clients(entity_t ent);
} }