From: Mikko Rasa Date: Sun, 11 Jun 2023 19:22:34 +0000 (+0300) Subject: Add a mechanism for spawning entities over the network X-Git-Url: https://git.tdb.fi/?a=commitdiff_plain;h=f7136b62685b94ed271ac6e6a09c98f47d3e270d;p=libs%2Fgame.git Add a mechanism for spawning entities over the network --- diff --git a/source/game/messages.h b/source/game/messages.h index 947b046..8cdfb8a 100644 --- a/source/game/messages.h +++ b/source/game/messages.h @@ -1,6 +1,9 @@ #ifndef MSP_GAME_MESSAGES_H_ #define MSP_GAME_MESSAGES_H_ +#include +#include + namespace Msp::Game { namespace Messages { @@ -33,6 +36,15 @@ struct InternString std::string value; }; +struct SpawnEntity +{ + std::uint16_t type; + std::uint16_t setup; + LinAl::Vector position; + Geometry::Quaternion rotation; + LinAl::Vector scale; +}; + struct StageActivated { std::uint16_t name; diff --git a/source/game/protocol.cpp b/source/game/protocol.cpp index 9eddbb7..025731e 100644 --- a/source/game/protocol.cpp +++ b/source/game/protocol.cpp @@ -14,4 +14,17 @@ CoreProtocol::CoreProtocol() add(&StageActivated::name); } + +StageProtocol::StageProtocol() +{ + using namespace Messages; + + using Vector3f = LinAl::Vector; + using Quaternion = Geometry::Quaternion; + add(&Vector3f::x, &Vector3f::y, &Vector3f::z); + add(&Quaternion::a, &Quaternion::b, &Quaternion::c, &Quaternion::d); + + add(&SpawnEntity::type, &SpawnEntity::setup, &SpawnEntity::position, &SpawnEntity::rotation, &SpawnEntity::scale); +} + } // namespace Msp::Game diff --git a/source/game/protocol.h b/source/game/protocol.h index 0749693..72f63c3 100644 --- a/source/game/protocol.h +++ b/source/game/protocol.h @@ -11,6 +11,13 @@ public: CoreProtocol(); }; + +class StageProtocol: public Net::Protocol +{ +public: + StageProtocol(); +}; + } // namespace Msp::Game #endif diff --git a/source/game/replicator.cpp b/source/game/replicator.cpp new file mode 100644 index 0000000..a24e629 --- /dev/null +++ b/source/game/replicator.cpp @@ -0,0 +1,84 @@ +#include "replicator.h" +#include "entity.h" +#include "networking.h" +#include "spawner.h" +#include "transform.h" +#include "zygote.h" + +using namespace std; + +namespace Msp::Game { + +Replicator::Replicator(Stage &s, Networking &n): + System(s), + observer(stage.get_event_bus()), + networking(n) +{ + observer.observe([this](const auto &e){ component_created(e); }); + + const Net::Protocol &protocol = networking.set_receiver(&dispatcher); + dispatcher.add_receiver(protocol.get_packet_id(), *this); +} + +void Replicator::add_spawner(Spawner &spawner, const string &type) +{ + bool exists = false; + for(Spawner *s: spawners) + { + if(s==&spawner) + exists = true; + else if(s->can_spawn(type)) + throw key_error(type); + } + + if(!exists) + spawners.push_back(&spawner); +} + +void Replicator::add_target_player(unsigned id) +{ + players.push_back(id); + for(const ReplicatedEntity &e: entities) + send_spawn(e, id); +} + +void Replicator::component_created(const Events::ComponentCreated &event) +{ + if(Handle zygote = dynamic_handle_cast(event.component)) + { + Handle entity = zygote->get_entity(); + Handle transform = entity->get_transform(); + auto i = lower_bound_member(entities, transform, &ReplicatedEntity::transform); + i = entities.emplace(i, entity, transform, zygote); + send_spawn(*i, -1); + } +} + +void Replicator::send_spawn(const ReplicatedEntity &re, int target) +{ + const SpawnInfo &info = re.zygote->get_spawn_info(); + Messages::SpawnEntity message; + message.type = networking.intern_string(info.type_name); + message.setup = networking.intern_string(info.setup_name); + const TransformValues &tf = re.transform->get_values(); + message.position = tf.position; + message.rotation = tf.rotation; + message.scale = tf.scale; + if(target>=0) + networking.send(target, message); + else + networking.send(message); +} + +void Replicator::receive(const Messages::SpawnEntity &message) +{ + const string &type_name = networking.get_string(message.type); + auto i = ranges::find_if(spawners, [&type_name](Spawner *s){ return s->can_spawn(type_name); }); + if(i==spawners.end()) + return; // TODO report the error somehow + + const string &setup_name = networking.get_string(message.setup); + (*i)->spawn(type_name, setup_name, TransformValues(message.position, message.rotation, message.scale)); +} + +} // namespace Msp::Game diff --git a/source/game/replicator.h b/source/game/replicator.h new file mode 100644 index 0000000..573a5fc --- /dev/null +++ b/source/game/replicator.h @@ -0,0 +1,51 @@ +#ifndef MSP_GAME_REPLICATOR_H_ +#define MSP_GAME_REPLICATOR_H_ + +#include +#include +#include "messages.h" +#include "protocol.h" +#include "system.h" + +namespace Msp::Game { + +class Networking; +class Spawner; +class Transform; +class Zygote; + +class MSPGAME_API Replicator: public System, private Net::PacketReceiver +{ +private: + struct ReplicatedEntity + { + Handle entity; + Handle transform; + Handle zygote; + }; + + EventObserver observer; + Networking &networking; + Net::DynamicDispatcher dispatcher; + std::vector spawners; + std::vector entities; + std::vector players; + +public: + Replicator(Stage &, Networking &); + + void add_spawner(Spawner &, const std::string &); + void add_target_player(unsigned); + + void tick(Time::TimeDelta) override { } + +private: + void component_created(const Events::ComponentCreated &); + void send_spawn(const ReplicatedEntity &, int); + + void receive(const Messages::SpawnEntity &) override; +}; + +} // namespace Msp::Game + +#endif diff --git a/source/game/spawner.cpp b/source/game/spawner.cpp new file mode 100644 index 0000000..f696d4d --- /dev/null +++ b/source/game/spawner.cpp @@ -0,0 +1,54 @@ +#include "spawner.h" +#include "entity.h" +#include "replicator.h" +#include "system.h" +#include "transform.h" +#include "zygote.h" + +using namespace std; + +namespace Msp::Game { + +Spawner::Spawner(Stage &s, Replicator *r, Handle p, SpawningHandler &h): + resources(s.get_resources()), + reflector(s.get_reflector()), + replicator(r), + parent(p), + handler(h) +{ } + +void Spawner::add_to_replicator(const string &type_name) +{ + if(replicator) + replicator->add_spawner(*this, type_name); +} + +bool Spawner::can_spawn(const string &type_name) const +{ + return ranges::any_of(spawnable_types, [&type_name](const SpawnableType &t){ return t.type.get_name()==type_name; }); +} + +void Spawner::spawn(const string &type_name, const string &setup_name, const TransformValues &tf) +{ + auto i = ranges::find_if(spawnable_types, [&type_name](const SpawnableType &t){ return t.type.get_name()==type_name; }); + if(i==spawnable_types.end()) + throw key_error(type_name); + + spawn(*i, setup_name, tf); +} + +void Spawner::spawn(const SpawnableType &type, const string &setup_name, const TransformValues &tf) +{ + auto i = ranges::find_if(spawn_infos, [&type, &setup_name](const auto &s){ + return s->type_name==type.type.get_name() && s->setup_name==setup_name; + }); + if(i==spawn_infos.end()) + i = spawn_infos.emplace(spawn_infos.end(), make_unique(this, type.type.get_name(), setup_name)); + + Owned entity = (this->*type.create_func)(setup_name, tf); + if(Handle zygote = entity->get_component()) + zygote->set_spawn_info(**i); + handler.spawned(move(entity)); +} + +} // namespace Msp::Game diff --git a/source/game/spawner.h b/source/game/spawner.h new file mode 100644 index 0000000..95ac903 --- /dev/null +++ b/source/game/spawner.h @@ -0,0 +1,105 @@ +#ifndef MSP_GAME_SPAWNER_H_ +#define MSP_GAME_SPAWNER_H_ + +#include +#include "messages.h" +#include "owned.h" + +namespace Msp::Game { + +class Replicator; +class Spawner; +struct TransformValues; + +struct SpawnInfo +{ + Spawner *spawner; + std::string type_name; + std::string setup_name; +}; + + +class MSPGAME_API SpawningHandler +{ +public: + virtual void spawned(Owned) = 0; +}; + + +class MSPGAME_API Spawner +{ +private: + struct SpawnableType + { + const Reflection::ClassBase &type; + Owned (Spawner::*create_func)(const std::string &, const TransformValues &) const; + }; + + DataFile::Collection &resources; + Reflection::Reflector &reflector; + Replicator *replicator = nullptr; + Handle parent; + SpawningHandler &handler; + std::vector spawnable_types; + std::vector> spawn_infos; + +public: + Spawner(Stage &, Replicator *, Handle, SpawningHandler &); + + template + requires std::is_base_of_v + void add_spawnable_type(); + +private: + void add_to_replicator(const std::string &); + +public: + bool can_spawn(const std::string &) const; + + template + requires std::is_base_of_v + void spawn(const std::string &, const TransformValues &); + + void spawn(const std::string &, const std::string &, const TransformValues &); + +private: + void spawn(const SpawnableType &, const std::string &, const TransformValues &); + + template + Owned create(const std::string &, const TransformValues &) const; +}; + + +template + requires std::is_base_of_v +void Spawner::add_spawnable_type() +{ + const Reflection::ClassBase &type = reflector.get_or_create_class(); + /* There's assumed to be only a few types per spawner, so sorting is not + necessary */ + spawnable_types.emplace_back(type, &Spawner::create); + add_to_replicator(type.get_name()); +} + +template + requires std::is_base_of_v +void Spawner::spawn(const std::string &sname, const TransformValues &tf) +{ + const Reflection::ClassBase &type = reflector.get_or_create_class(); + auto i = std::ranges::find_if(spawnable_types, [&type](const SpawnableType &t){ return &t.type==&type; }); + if(i==spawnable_types.end()) + throw key_error(type.get_name()); + + spawn(*i, sname, tf); +} + +template +Owned Spawner::create(const std::string &setup_name, const TransformValues &tf) const +{ + const typename T::Setup &setup = resources.get(setup_name); + return Owned(parent, setup, tf); +} + +} // namespace Msp::Game + +#endif diff --git a/source/game/zygote.cpp b/source/game/zygote.cpp new file mode 100644 index 0000000..c78e203 --- /dev/null +++ b/source/game/zygote.cpp @@ -0,0 +1,20 @@ +#include "zygote.h" +#include + +namespace Msp::Game { + +void Zygote::set_spawn_info(const SpawnInfo &s) +{ + if(spawn_info) + throw already_called("spawn_info already set"); + spawn_info = &s; +} + +const SpawnInfo &Zygote::get_spawn_info() const +{ + if(!spawn_info) + throw invalid_state("no spawn_info"); + return *spawn_info; +} + +} // namespace Msp::Game diff --git a/source/game/zygote.h b/source/game/zygote.h new file mode 100644 index 0000000..5f0f1e9 --- /dev/null +++ b/source/game/zygote.h @@ -0,0 +1,24 @@ +#ifndef MSP_GAME_ZYGOTE_H_ +#define MSP_GAME_ZYGOTE_H_ + +#include "component.h" + +namespace Msp::Game { + +struct SpawnInfo; + +class Zygote: public Component +{ +private: + const SpawnInfo *spawn_info = nullptr; + +public: + Zygote(Handle e): Component(e) { } + + void set_spawn_info(const SpawnInfo &); + const SpawnInfo &get_spawn_info() const; +}; + +} // namespace Msp::Game + +#endif