Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_subdirectory(Logging)
# Layer 2
add_subdirectory(Core)
# Layer 3
add_subdirectory(Network)
add_subdirectory(Scene)
# Layer 4
add_subdirectory(Physics)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
setup_module(
NAME networking
NAME network
TARGET_DEPS logging utils core
ENABLE_TESTS
FATAL_ERROR
)
5 changes: 5 additions & 0 deletions Engine/Network/include/Network.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2024 Stone-Engine

#pragma once

#include "Network/ObjectPool.hpp"
24 changes: 24 additions & 0 deletions Engine/Network/include/Network/CommandExecutor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2024 Stone-Engine

#pragma once

#include <memory>

namespace Stone::Network {

template <typename ObjectType>
class CommandExecutor {
public:
using Command = std::function<void(const std::shared_ptr<ObjectType> &)>;

CommandExecutor() = default;
CommandExecutor(const CommandExecutor &) = delete;

virtual ~CommandExecutor() = default;

void execute(const Command &command, const std::shared_ptr<ObjectType> &object) {
command(object);
}
};

} // namespace Stone::Network
85 changes: 85 additions & 0 deletions Engine/Network/include/Network/Dispatcher/JsonRpcDispatcher.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2024 Stone-Engine

#pragma once

#include "Utils/Json.hpp"
#include "Utils/SigSlot.hpp"

#include <future>

#define JSONRPC_ID "id"
#define JSONRPC_METHOD "method"
#define JSONRPC_PARAMS "params"
#define JSONRPC_RESULT "result"
#define JSONRPC_ERROR "error"

namespace Stone::Network {

class JsonRpcDispatcher {
public:
using Id = int;
using Method = std::string;
using Params = Json::Value;
using Result = Json::Value;
using Error = std::string;

using SuccessCallback = std::function<void(const Result &)>;
using FailureCallback = std::function<void(const Error &)>;

using SyncRequestHandler = std::function<Result(const Params &)>;
using AsyncRequestHandler = std::function<void(const Params &, const std::promise<Result> &)>;
using RequestHandler = std::function<void(const Params &, const SuccessCallback &, const FailureCallback &)>;

using NotificationSignal = Signal<const Params &>;

using ResponseCallbacks = std::pair<SuccessCallback, FailureCallback>;

JsonRpcDispatcher() = default;
JsonRpcDispatcher(const JsonRpcDispatcher &other) = default;

virtual ~JsonRpcDispatcher() = default;

bool registerRequestHandler(const Method &method, const SyncRequestHandler &syncHandler);
bool registerRequestHandler(const Method &method, const AsyncRequestHandler &asyncHandler);
bool registerRequestHandler(const Method &method, const RequestHandler &handler);

bool hasRequestHandler(const Method &method) const;

NotificationSignal &getNotificationSignal(const Method &method);

bool hasNotificationSignal(const Method &method) const;

bool sendRequest(std::ostream &output, const Method &method, const Params &params,
const ResponseCallbacks &callbacks, float timeout = 10.0f);

bool sendNotification(std::ostream &output, const Method &method, const Params &params);

bool handleString(const std::string &message, std::ostream &output);
bool handleStream(std::istream &stream, std::ostream &output);
bool handleJsonArray(const Json::Array &message, std::ostream &output);
bool handleJsonObject(const Json::Object &message, std::ostream &output);

bool handleRequest(Id id, const Method &method, const Params &params, std::ostream &output);
bool handleNotification(const Method &method, const Params &params);
bool handleResponseSuccess(Id id, const Result &result);
bool handleResponseError(Id id, const Error &error);

void cleanup();
void cleanupTimedOutPendingRequests();
void cleanupEmptyNotificationSignals();

private:
std::unordered_map<Method, RequestHandler> _requestHandlers;
std::unordered_map<Method, std::unique_ptr<NotificationSignal>> _notificationSignals;

struct PendingResponse {
ResponseCallbacks callbacks;
std::chrono::steady_clock::time_point expiration;
};
std::unordered_map<Id, PendingResponse> _pendingRequests;
Id _nextId = 0;

bool _sendErrorMessage = false;
};

} // namespace Stone::Network
36 changes: 36 additions & 0 deletions Engine/Network/include/Network/NetworkObject.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2024 Stone-Engine

#include "Core/Object.hpp"
#include "Network/ObjectPool.hpp"
#include "Utils/SigSlot.hpp"

namespace Stone::Network {

class NetworkObject : public Core::Object {
STONE_OBJECT(NetworkObject)

public:
NetworkObject() = default;
NetworkObject(const NetworkObject &other) = default;

~NetworkObject() override = default;

void writeToJson(Json::Object &json) const override;

ObjectPool<NetworkObject>::Id getPoolId() const;

Signal<const NetworkObject &, const std::uint8_t *, std::size_t> onReceivingData;
Signal<const NetworkObject &, const std::uint8_t *, std::size_t> onSendingData;

protected:
virtual void receiveData(const std::uint8_t *data, std::size_t size);

virtual void sendData(const std::uint8_t *data, std::size_t &size) const;

private:
ObjectPool<NetworkObject>::Id _poolId = 0;

friend class NetworkSession;
};

} // namespace Stone::Network
26 changes: 26 additions & 0 deletions Engine/Network/include/Network/NetworkSession.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 Stone-Engine

#pragma once

#include "Network/NetworkObject.hpp"
#include "Network/ObjectPool.hpp"

namespace Stone::Network {

class NetworkSession {
public:
NetworkSession() = default;

ObjectPool<NetworkObject> &getObjectPool() {
return _objectPool;
}

private:
// TODO: Add CommunicationDevice class, which will be used to send and receive data

// TODO: Add CommandExecutor class, which will be used to execute commands

ObjectPool<NetworkObject> _objectPool;
};

} // namespace Stone::Network
71 changes: 71 additions & 0 deletions Engine/Network/include/Network/ObjectPool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2024 Stone-Engine

#pragma once

#include <memory>
#include <vector>

namespace Stone::Network {

template <typename ObjectType>
class ObjectPool {
public:
using Id = unsigned int;

ObjectPool() : _lastUsableId(1), _objects() {
_incrementLastUsableId();
};

ObjectPool(const ObjectPool &) = delete;

virtual ~ObjectPool() = default;

Id add(const std::shared_ptr<ObjectType> &object) {
Id id = _makeUsableId();
_objects[id] = object;
return id;
}

void set(Id id, const std::shared_ptr<ObjectType> &object) {
if (id >= _objects.size()) {
_objects.resize(id + 1);
}
_objects[id] = object;
_incrementLastUsableId();
}

std::shared_ptr<ObjectType> get(Id id) const {
return _objects[id].lock();
}

const std::weak_ptr<ObjectType> &weak_get(Id id) const {
return _objects[id];
}

void refreshId() {
_lastUsableId = 1;
_incrementLastUsableId();
}

private:
Id _makeUsableId() {
Id id = _lastUsableId++;
_incrementLastUsableId();
return id;
}

void _incrementLastUsableId() {
while (_lastUsableId < _objects.size() && !_objects[_lastUsableId].expired()) {
++_lastUsableId;
}
if (_lastUsableId >= _objects.size()) {
_objects.resize(_lastUsableId + 1);
}
}

Id _lastUsableId; // The last id that is directly usable for the next object

std::vector<std::weak_ptr<ObjectType>> _objects; // The objects in the pool
};

} // namespace Stone::Network
Loading