Skip to content

Commit

Permalink
feat(reflection): add TypeClient and TypeServer
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Sep 27, 2024
1 parent 0764feb commit 72eea69
Show file tree
Hide file tree
Showing 10 changed files with 1,820 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compute contact points and contact manifold for collision between boxes (#1243, **@fallenatlas**).
- Handle body rotation on physics integration (#1242, **&fallenatlas**).
- Binary Serializer and Deserializer (#1306, **@RiscadoA**).
- Type Client and Type Server (#1302, **@RiscadoA**).

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ set(CUBOS_CORE_SOURCE
"src/reflection/reflect.cpp"
"src/reflection/type.cpp"
"src/reflection/comparison.cpp"
"src/reflection/type_client_server_defaults.cpp"
"src/reflection/type_client.cpp"
"src/reflection/type_server.cpp"
"src/reflection/type_registry.cpp"
"src/reflection/traits/constructible.cpp"
"src/reflection/traits/fields.cpp"
Expand Down
183 changes: 183 additions & 0 deletions core/include/cubos/core/reflection/type_client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/// @file
/// @brief Class @ref cubos::core::reflection::TypeClient.
/// @ingroup core-reflection

#pragma once

#include <cubos/core/data/des/deserializer.hpp>
#include <cubos/core/memory/any_value.hpp>
#include <cubos/core/memory/function.hpp>
#include <cubos/core/memory/opt.hpp>
#include <cubos/core/memory/type_map.hpp>
#include <cubos/core/reflection/traits/constructible.hpp>
#include <cubos/core/reflection/type_registry.hpp>

namespace cubos::core::reflection
{
/// @brief Manages the client-side of a reflection channel on top of a stream.
///
/// This class allows the client to receive type information of types unknown by the client from the server.
/// It also makes it possible to then receive and send values of these types.
///
/// Received types can be either structured or non-structured.
/// Only values of structured types can be communicated between the client and the server.
/// For a type to be structured, it must have a structural trait, so that the client can create new instances of the
/// type.
///
/// Structural traits are traits which define the memory layout of a type.
/// A structural trait should add the @ref TypeClient::RemoteTrait trait to the type.
/// This trait is then used by this class to create a @ref ConstructibleTrait for the type.
///
/// See @ref TypeServer for more information on the protocol.
///
/// @ingroup core-reflection
class CUBOS_CORE_API TypeClient final
{
public:
/// @brief Class which holds connection information such as the type registry.
class Connection;

/// @brief Trait added to all types received by the client.
///
/// Structural traits should add this trait to their types when deserialized by the client.
/// They should also initialize the @ref ConstructibleTrait field to the correct value.
/// See @ref setupTypeClientDefaults for examples on how to register structural traits.
///
/// If a type is not constructible/does not have a structural trait, this trait is added by the client itself,
/// and the @ref ConstructibleTrait field is left empty.
struct RemoteTrait;

/// @brief Function type for deserializing traits.
using Deserialize = memory::Function<bool(const TypeRegistry&, Type&, data::Deserializer&) const>;

/// @brief Function type for discovering new types from a trait.
using DiscoverTypes = void (*)(const Type&, memory::Function<void(const Type&) const>);

/// @brief Default constructor.
TypeClient();

/// @brief Registers a trait to be supported by the channel.
/// @param isStructural Whether the trait is a structural trait.
/// @param traitType Trait type to be registered.
/// @param deserialize Function to deserialize the trait.
/// @param discoverTypes Function to discover new types from the trait.
void addTrait(bool isStructural, const Type& traitType, Deserialize deserialize, DiscoverTypes discoverTypes);

/// @brief Registers a trait to be supported by the channel.
/// @tparam T Trait type.
/// @param isStructural Whether the trait is a structural trait.
/// @param deserialize Function to deserialize the trait.
/// @param discoverTypes Function to discover new types from the trait.
template <typename T>
void addTrait(bool isStructural, Deserialize deserialize, DiscoverTypes discoverTypes)
{
this->addTrait(isStructural, reflect<T>(), memory::move(deserialize), memory::move(discoverTypes));
}

/// @brief Adds a known type to the channel.
///
/// This allows the channel to reuse the given type instead of creating a new runtime type when connecting to a
/// server.
///
/// @note The type must be default constructible.
/// @param type Type.
void addType(const Type& type);

/// @brief Adds a known type to the channel.
///
/// This allows the channel to reuse the given type instead of creating a new runtime type when connecting to a
/// server.
///
/// @note The type must be default constructible.
/// @tparam T Type.
template <typename T>
void addType()
{
this->addType(reflect<T>());
}

/// @brief Connects to a server, using the given stream.
/// @param stream Stream.
/// @return Connection information, or nothing if the connection failed.
memory::Opt<Connection> connect(memory::Stream& stream) const;

private:
struct Trait
{
Deserialize deserialize;
DiscoverTypes discoverTypes;
bool isStructural;
};

/// @brief Lists the known traits of the given type.
/// @param type Type.
std::string listTraits(const Type& type) const;

memory::TypeMap<Trait> mTraits;
TypeRegistry mKnownTypes;
};

class TypeClient::Connection
{
public:
~Connection();

/// @brief Constructs.
/// @param types Type registry.
/// @param externalTypes Types to be destroyed when the connection is destroyed.
Connection(TypeRegistry sharedTypes, TypeRegistry serializableTypes, std::vector<Type*> externalTypes);

/// @brief Move constructs.
/// @param other Other.
Connection(Connection&& other) noexcept;

/// @brief Gets the type registry.
/// @return Type registry.
const TypeRegistry& sharedTypes() const;

/// @brief Gets the type registry.
/// @return Type registry.
const TypeRegistry& serializableTypes() const;

private:
TypeRegistry mSharedTypes;
TypeRegistry mSerializableTypes;
std::vector<Type*> mExternalTypes;
};

struct TypeClient::RemoteTrait
{
CUBOS_REFLECT;

/// @brief Constructible trait which is used to create raw values for instances of the type.
///
/// Values default constructed by this trait do not have the same value as the actual default constructor of the
/// host type. Instead, the @ref TypeClient creates a new @ref ConstructibleTrait for the type, from this one,
/// whose default constructor correctly initializes the type to the default value received from the server.
///
/// If the type has no structural trait, this field is left empty.
/// Structural traits must initialize this field to the correct value.
memory::Opt<ConstructibleTrait> constructibleTrait;

/// @brief Automatically constructs a new RemoteTrait for the given type, with all basic constructors.
/// @tparam T Type.
template <typename T>
static RemoteTrait createBasic()
{
return RemoteTrait{ConstructibleTrait::typed<T>().withBasicConstructors().build()};
}

/// @brief Automatically constructs a new RemoteTrait for the given type, with the copy and move constructors,
/// and a custom default constructor.
/// @tparam T Type.
template <typename T, typename F>
static RemoteTrait createCustom(F defaultConstructor)
{
return RemoteTrait{ConstructibleTrait::typed<T>()
.withCopyConstructor()
.withMoveConstructor()
.build()
.withDefaultConstructor(memory::move(defaultConstructor))};
}
};
} // namespace cubos::core::reflection
132 changes: 132 additions & 0 deletions core/include/cubos/core/reflection/type_server.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/// @file
/// @brief Class @ref cubos::core::reflection::TypeServer.
/// @ingroup core-reflection

#pragma once

#include <vector>

#include <cubos/core/data/ser/serializer.hpp>
#include <cubos/core/memory/function.hpp>
#include <cubos/core/memory/opt.hpp>
#include <cubos/core/memory/type_map.hpp>
#include <cubos/core/reflection/reflect.hpp>
#include <cubos/core/reflection/type_registry.hpp>

namespace cubos::core::reflection
{
/// @brief Used to setup a @ref TypeChannel on top of a stream, from the server side.
///
/// This class allows the server to send and receive values of types unknown by the client to the client.
/// Not all types registered on the server will support this: some may not have enough information to be serialized.
/// The set of types which can be safely serialized is returned by the @ref connect function.
///
/// The protocol used by this class is as follows:
/// 1. Server receives a list of supported trait types from the client, and whether they are structural.
/// 2. Server receives a list of the types which the client already knows.
/// 3. Server sends the type information of the types which the client does not know. This includes:
/// a) name of each type,
/// b) structural trait of the type which is supported by the client, if any,
/// b) non-structural traits of the type which are supported by the client,
/// d) if the type has a structural trait and is default-constructible, the default-constructed serialized value.
///
/// If the type has multiple structural traits, the first one added to the server is used.
/// The non-structural traits are sent in the order they were added to the server.
///
/// @ingroup core-reflection
class CUBOS_CORE_API TypeServer final
{
public:
/// @brief Class which holds connection information such as the type registry.
class Connection;

/// @brief Function type for serializing traits.
///
/// Receives a serializer, the value to serialize, and whether the type is already serializable.
using Serialize = memory::Function<bool(data::Serializer&, const Type&, bool) const>;

/// @brief Function type for discovering new types from a trait.
using DiscoverTypes = void (*)(const Type&, memory::Function<void(const Type&) const>);

/// @brief Default constructs.
TypeServer();

/// @brief Registers a trait to be supported by the channel.
/// @note If a connection is already active, will only take effect after the channel is reconnected.
/// @param type Trait type to be registered.
/// @param serialize Function to serialize the trait.
/// @param discoverTypes Function to discover new types from the trait.
void addTrait(const Type& traitType, Serialize serialize, DiscoverTypes discoverTypes);

/// @brief Registers a trait to be supported by the channel.
/// @note If a connection is already active, will only take effect after the channel is reconnected.
/// @tparam T Trait type.
/// @param serialize Function to serialize the trait.
/// @param discoverTypes Function to discover new types from the trait.
template <typename T>
void addTrait(Serialize serialize, DiscoverTypes discoverTypes)
{
this->addTrait(reflect<T>(), memory::move(serialize), memory::move(discoverTypes));
}

/// @brief Adds a type to be supported by the channel.
///
/// Also adds any types used by the type, recursively. I.e., types of fields if the type has a @ref FieldsTrait.
///
/// @param type Type to be supported.
void addType(const Type& type);

/// @brief Adds a type to be supported by the channel.
///
/// Also adds any types used by the type, recursively. I.e., types of fields if the type has a @ref FieldsTrait.
///
/// @tparam T Type to be supported.
template <typename T>
void addType()
{
this->addType(reflect<T>());
}

/// @brief Connects to a new client, using the given stream.
/// @param stream Stream.
/// @return Connection information, or nothing if the connection failed.
memory::Opt<Connection> connect(memory::Stream& stream);

private:
struct Trait
{
Serialize serialize;
DiscoverTypes discoverTypes;
};

TypeRegistry mKnownTypes;
std::vector<const Type*> mOrderedKnownTypes;
memory::TypeMap<Trait> mTraits;
std::vector<const Type*> mOrderedTraits; // Preserve order of insertion.
};

class TypeServer::Connection
{
public:
/// @brief Constructs.
/// @param sharedTypes All types which are shared between the server and the client.
/// @param serializableTypes All types which are serializable by the client.
Connection(TypeRegistry sharedTypes, TypeRegistry serializableTypes);

/// @brief Move constructs.
/// @param other Other.
Connection(Connection&& other) noexcept = default;

/// @brief Gets the shared types.
/// @return Shared types.
const TypeRegistry& sharedTypes() const;

/// @brief Gets the serializable types.
/// @return Serializable types.
const TypeRegistry& serializableTypes() const;

private:
TypeRegistry mSharedTypes;
TypeRegistry mSerializableTypes;
};
} // namespace cubos::core::reflection
Loading

0 comments on commit 72eea69

Please sign in to comment.