]> git.tdb.fi Git - libs/game.git/commitdiff
Add a mechanism for spawning entities over the network
authorMikko Rasa <tdb@tdb.fi>
Sun, 11 Jun 2023 19:22:34 +0000 (22:22 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sun, 11 Jun 2023 19:22:34 +0000 (22:22 +0300)
source/game/messages.h
source/game/protocol.cpp
source/game/protocol.h
source/game/replicator.cpp [new file with mode: 0644]
source/game/replicator.h [new file with mode: 0644]
source/game/spawner.cpp [new file with mode: 0644]
source/game/spawner.h [new file with mode: 0644]
source/game/zygote.cpp [new file with mode: 0644]
source/game/zygote.h [new file with mode: 0644]

index 947b0463531d786b7b21733270c6e0742d733660..8cdfb8adebe034b63cf8c778120d9a9b01fbbf57 100644 (file)
@@ -1,6 +1,9 @@
 #ifndef MSP_GAME_MESSAGES_H_
 #define MSP_GAME_MESSAGES_H_
 
+#include <msp/geometry/quaternion.h>
+#include <msp/linal/vector.h>
+
 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<float, 3> position;
+       Geometry::Quaternion<float> rotation;
+       LinAl::Vector<float, 3> scale;
+};
+
 struct StageActivated
 {
        std::uint16_t name;
index 9eddbb7f791d80191c9aa9ccb0858e0977558607..025731ec07be47581bf9fdd0ec0d14cc970e9a6c 100644 (file)
@@ -14,4 +14,17 @@ CoreProtocol::CoreProtocol()
        add<StageActivated>(&StageActivated::name);
 }
 
+
+StageProtocol::StageProtocol()
+{
+       using namespace Messages;
+
+       using Vector3f = LinAl::Vector<float, 3>;
+       using Quaternion = Geometry::Quaternion<float>;
+       add<Vector3f, float, float, float>(&Vector3f::x, &Vector3f::y, &Vector3f::z);
+       add<Quaternion>(&Quaternion::a, &Quaternion::b, &Quaternion::c, &Quaternion::d);
+
+       add<SpawnEntity>(&SpawnEntity::type, &SpawnEntity::setup, &SpawnEntity::position, &SpawnEntity::rotation, &SpawnEntity::scale);
+}
+
 } // namespace Msp::Game
index 0749693ea1c51d9ff98300d2a140685c728ef6c6..72f63c3fc6f01750d727b13931d54d1af6e9b0a2 100644 (file)
@@ -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 (file)
index 0000000..a24e629
--- /dev/null
@@ -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<Events::ComponentCreated>([this](const auto &e){ component_created(e); });
+
+       const Net::Protocol &protocol = networking.set_receiver<StageProtocol>(&dispatcher);
+       dispatcher.add_receiver<Messages::SpawnEntity>(protocol.get_packet_id<Messages::SpawnEntity>(), *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> zygote = dynamic_handle_cast<Zygote>(event.component))
+       {
+               Handle<Entity> entity = zygote->get_entity();
+               Handle<Transform> 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 (file)
index 0000000..573a5fc
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef MSP_GAME_REPLICATOR_H_
+#define MSP_GAME_REPLICATOR_H_
+
+#include <vector>
+#include <msp/net/receiver.h>
+#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<Messages::SpawnEntity>
+{
+private:
+       struct ReplicatedEntity
+       {
+               Handle<Entity> entity;
+               Handle<Transform> transform;
+               Handle<Zygote> zygote;
+       };
+
+       EventObserver observer;
+       Networking &networking;
+       Net::DynamicDispatcher dispatcher;
+       std::vector<Spawner *> spawners;
+       std::vector<ReplicatedEntity> entities;
+       std::vector<unsigned> 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 (file)
index 0000000..f696d4d
--- /dev/null
@@ -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<Entity> 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<SpawnInfo>(this, type.type.get_name(), setup_name));
+
+       Owned<Entity> entity = (this->*type.create_func)(setup_name, tf);
+       if(Handle<Zygote> zygote = entity->get_component<Zygote>())
+               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 (file)
index 0000000..95ac903
--- /dev/null
@@ -0,0 +1,105 @@
+#ifndef MSP_GAME_SPAWNER_H_
+#define MSP_GAME_SPAWNER_H_
+
+#include <algorithm>
+#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<Entity>) = 0;
+};
+
+
+class MSPGAME_API Spawner
+{
+private:
+       struct SpawnableType
+       {
+               const Reflection::ClassBase &type;
+               Owned<Entity> (Spawner::*create_func)(const std::string &, const TransformValues &) const;
+       };
+
+       DataFile::Collection &resources;
+       Reflection::Reflector &reflector;
+       Replicator *replicator = nullptr;
+       Handle<Entity> parent;
+       SpawningHandler &handler;
+       std::vector<SpawnableType> spawnable_types;
+       std::vector<std::unique_ptr<SpawnInfo>> spawn_infos;
+
+public:
+       Spawner(Stage &, Replicator *, Handle<Entity>, SpawningHandler &);
+
+       template<typename T>
+               requires std::is_base_of_v<Entity, T>
+       void add_spawnable_type();
+
+private:
+       void add_to_replicator(const std::string &);
+
+public:
+       bool can_spawn(const std::string &) const;
+
+       template<typename T>
+               requires std::is_base_of_v<Entity, T>
+       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<typename T>
+       Owned<Entity> create(const std::string &, const TransformValues &) const;
+};
+
+
+template<typename T>
+       requires std::is_base_of_v<Entity, T>
+void Spawner::add_spawnable_type()
+{
+       const Reflection::ClassBase &type = reflector.get_or_create_class<T>();
+       /* There's assumed to be only a few types per spawner, so sorting is not
+       necessary */
+       spawnable_types.emplace_back(type, &Spawner::create<T>);
+       add_to_replicator(type.get_name());
+}
+
+template<typename T>
+       requires std::is_base_of_v<Entity, T>
+void Spawner::spawn(const std::string &sname, const TransformValues &tf)
+{
+       const Reflection::ClassBase &type = reflector.get_or_create_class<T>();
+       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<typename T>
+Owned<Entity> Spawner::create(const std::string &setup_name, const TransformValues &tf) const
+{
+       const typename T::Setup &setup = resources.get<typename T::Setup>(setup_name);
+       return Owned<T>(parent, setup, tf);
+}
+
+} // namespace Msp::Game
+
+#endif
diff --git a/source/game/zygote.cpp b/source/game/zygote.cpp
new file mode 100644 (file)
index 0000000..c78e203
--- /dev/null
@@ -0,0 +1,20 @@
+#include "zygote.h"
+#include <msp/core/except.h>
+
+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 (file)
index 0000000..5f0f1e9
--- /dev/null
@@ -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<Entity> e): Component(e) { }
+
+       void set_spawn_info(const SpawnInfo &);
+       const SpawnInfo &get_spawn_info() const;
+};
+
+} // namespace Msp::Game
+
+#endif