implement object registry
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
|||||||
--header ${HEADER}
|
--header ${HEADER}
|
||||||
--source ${SOURCE}
|
--source ${SOURCE}
|
||||||
--out-base ${OUT_BASENAME}
|
--out-base ${OUT_BASENAME}
|
||||||
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${HEADER} ${SOURCE}
|
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES}/* ${HEADER} ${SOURCE}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
|
|||||||
+15
-2
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ipc/IpcConfig.h>
|
#include <ipc/IpcConfig.h>
|
||||||
|
#include <rpc/RpcRegistry.h>
|
||||||
#include <rpc/RpcValue.h>
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -10,11 +11,18 @@
|
|||||||
// (RpcValue/RpcArgs).
|
// (RpcValue/RpcArgs).
|
||||||
namespace IpcCodec {
|
namespace IpcCodec {
|
||||||
|
|
||||||
// Запрос: имя метода + вектор аргументов.
|
// Используем ObjectId из специализированного RPC-реестра.
|
||||||
inline IpcMessage encodeRequest(const std::string& method,
|
using ObjectId = RpcRegistry::ObjectId;
|
||||||
|
|
||||||
|
// Запрос: ObjectId + имя метода + вектор аргументов.
|
||||||
|
inline IpcMessage encodeRequest(ObjectId objectId,
|
||||||
|
const std::string& method,
|
||||||
const RpcArgs& args) {
|
const RpcArgs& args) {
|
||||||
IpcMessage msg;
|
IpcMessage msg;
|
||||||
|
|
||||||
|
// ObjectId (PoC: приводим к int)
|
||||||
|
msg.add(static_cast<int>(objectId));
|
||||||
|
|
||||||
// имя метода
|
// имя метода
|
||||||
msg.add(method);
|
msg.add(method);
|
||||||
|
|
||||||
@@ -27,10 +35,15 @@ inline IpcMessage encodeRequest(const std::string& method,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void decodeRequest(const IpcMessage& msg,
|
inline void decodeRequest(const IpcMessage& msg,
|
||||||
|
ObjectId& objectId,
|
||||||
std::string& method,
|
std::string& method,
|
||||||
RpcArgs& args) {
|
RpcArgs& args) {
|
||||||
IpcMessage copy = msg;
|
IpcMessage copy = msg;
|
||||||
|
|
||||||
|
// ObjectId (PoC: читаем как int и приводим к ObjectId)
|
||||||
|
int rawId = copy.get<int>();
|
||||||
|
objectId = static_cast<ObjectId>(rawId);
|
||||||
|
|
||||||
// имя метода
|
// имя метода
|
||||||
method = copy.get<std::string>();
|
method = copy.get<std::string>();
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcCodec::ObjectId objectId;
|
||||||
std::string method;
|
std::string method;
|
||||||
RpcArgs args;
|
RpcArgs args;
|
||||||
IpcCodec::decodeRequest(req, method, args);
|
IpcCodec::decodeRequest(req, objectId, method, args);
|
||||||
|
|
||||||
RpcValue result = invoker_.dispatch(method, args);
|
RpcValue result = invoker_.dispatch(objectId, method, args);
|
||||||
IpcMessage resp = IpcCodec::encodeResponse(result);
|
IpcMessage resp = IpcCodec::encodeResponse(result);
|
||||||
channel_.send(resp);
|
channel_.send(resp);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
// и IpcCodec, но снаружи предъявляет только callTyped<T>(...).
|
// и IpcCodec, но снаружи предъявляет только callTyped<T>(...).
|
||||||
class IpcMarshaller {
|
class IpcMarshaller {
|
||||||
public:
|
public:
|
||||||
explicit IpcMarshaller(IpcChannel& ch)
|
explicit IpcMarshaller(IpcChannel& ch, IpcCodec::ObjectId objectId = 0)
|
||||||
: channel(ch) {}
|
: channel(ch)
|
||||||
|
, objectId_(objectId) {}
|
||||||
|
|
||||||
// Базовый type-erased вызов: принимает вектор RpcValue и возвращает RpcValue.
|
// Базовый type-erased вызов: принимает вектор RpcValue и возвращает RpcValue.
|
||||||
RpcValue call(const std::string& method, const RpcArgs& args) {
|
RpcValue call(const std::string& method, const RpcArgs& args) {
|
||||||
// упаковать запрос в IpcMessage
|
// упаковать запрос в IpcMessage
|
||||||
IpcMessage msg = IpcCodec::encodeRequest(method, args);
|
IpcMessage msg = IpcCodec::encodeRequest(objectId_, method, args);
|
||||||
|
|
||||||
// отправить
|
// отправить
|
||||||
channel.send(msg);
|
channel.send(msg);
|
||||||
@@ -39,6 +40,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
IpcChannel& channel;
|
IpcChannel& channel;
|
||||||
|
IpcCodec::ObjectId objectId_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-36
@@ -1,56 +1,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <rpc/RpcRegistry.h>
|
||||||
#include <rpc/RpcValue.h>
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
|
// Инвокер — тонкий фасад над RpcRegistry:
|
||||||
|
// по ObjectId находит объект и делегирует вызов его IRpcObject::invoke().
|
||||||
class RpcInvoker {
|
class RpcInvoker {
|
||||||
public:
|
public:
|
||||||
template<typename Obj, typename Ret, typename... Args>
|
using ObjectId = RpcRegistry::ObjectId; // согласован с IpcCodec::ObjectId
|
||||||
void registerMethod(Obj* instance,
|
|
||||||
const std::string& name,
|
|
||||||
Ret (Obj::*method)(Args...)) {
|
|
||||||
handlers[name] =
|
|
||||||
[instance, method](const RpcArgs& args) -> RpcValue {
|
|
||||||
auto tuple = readArgs<Args...>(args);
|
|
||||||
Ret result = std::apply(
|
|
||||||
method, std::tuple_cat(std::make_tuple(instance), tuple));
|
|
||||||
|
|
||||||
// PoC: считаем, что Ret == int.
|
explicit RpcInvoker(RpcRegistry& registry)
|
||||||
return RpcValue::fromInt(result);
|
: registry_(registry) {}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
RpcValue dispatch(const std::string& method, const RpcArgs& args) const {
|
RpcValue dispatch(ObjectId objectId,
|
||||||
auto it = handlers.find(method);
|
const std::string& method,
|
||||||
if (it == handlers.end()) {
|
const RpcArgs& args) const {
|
||||||
|
IRpcObject* obj = registry_.get(objectId);
|
||||||
|
if (!obj) {
|
||||||
// PoC: в случае ошибки возвращаем 0.
|
// PoC: в случае ошибки возвращаем 0.
|
||||||
return RpcValue::fromInt(0);
|
return RpcValue::fromInt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second(args);
|
return obj->invoke(method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename... Args, std::size_t... I>
|
RpcRegistry& registry_;
|
||||||
static std::tuple<Args...>
|
|
||||||
readArgsImpl(const RpcArgs& args, std::index_sequence<I...>) {
|
|
||||||
return std::tuple<Args...>{
|
|
||||||
static_cast<Args>(args[I].asInt())...}; // PoC: только int
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
static std::tuple<Args...> readArgs(const RpcArgs& args) {
|
|
||||||
return readArgsImpl<Args...>(
|
|
||||||
args, std::make_index_sequence<sizeof...(Args)>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string,
|
|
||||||
std::function<RpcValue(const RpcArgs&)>>
|
|
||||||
handlers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
|
// Базовый интерфейс для всех RPC-объектов, живущих в реестре.
|
||||||
|
// Каждая реализация (обычно *Skeleton) знает, как вызвать реальные методы.
|
||||||
|
struct IRpcObject {
|
||||||
|
virtual ~IRpcObject() = default;
|
||||||
|
|
||||||
|
virtual RpcValue invoke(const std::string& method,
|
||||||
|
const RpcArgs& args) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Специализированный реестр объектов именно для RPC-уровня.
|
||||||
|
// Владеет скелетонами (IRpcObject) через unique_ptr. Сами доменные объекты
|
||||||
|
// (MyService и т.п.) живут снаружи и могут быть обёрнуты в shared_ptr.
|
||||||
|
class RpcRegistry {
|
||||||
|
public:
|
||||||
|
// PoC: ObjectId храним как int-совместимый тип, чтобы его можно было
|
||||||
|
// передавать через BaseIpcMessage, который поддерживает только int/string.
|
||||||
|
using ObjectId = int;
|
||||||
|
|
||||||
|
RpcRegistry() = default;
|
||||||
|
|
||||||
|
// Зарегистрировать объект-обёртку (обычно Skeleton) и вернуть его
|
||||||
|
// идентификатор. По сути, это registerSkeleton<T>(...).
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
ObjectId registerObject(Args&&... args) {
|
||||||
|
static_assert(std::is_base_of_v<IRpcObject, T>,
|
||||||
|
"T must inherit IRpcObject");
|
||||||
|
|
||||||
|
const ObjectId id = nextId_++;
|
||||||
|
objects_.emplace(
|
||||||
|
id, std::make_unique<T>(std::forward<Args>(args)...));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уничтожить объект по идентификатору.
|
||||||
|
void destroyObject(ObjectId id) {
|
||||||
|
objects_.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Найти объект по идентификатору.
|
||||||
|
IRpcObject* get(ObjectId id) const {
|
||||||
|
auto it = objects_.find(id);
|
||||||
|
return it == objects_.end() ? nullptr : it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<ObjectId, std::unique_ptr<IRpcObject>> objects_;
|
||||||
|
ObjectId nextId_ = 0; // первый объект будет иметь id = 0 (PoC)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+17
-6
@@ -16,11 +16,22 @@ int main() {
|
|||||||
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
||||||
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||||
|
|
||||||
// RPC‑уровень: создаём IpcMarshaller поверх канала и передаём его в прокси.
|
// RPC‑уровень: создаём два маршаллера с разными ObjectId и два прокси.
|
||||||
IpcMarshaller marshaller(ch);
|
// По договорённости на сервере:
|
||||||
MyServiceProxy<IpcMarshaller> proxy(marshaller);
|
// ObjectId = 0 -> первый MyService
|
||||||
|
// ObjectId = 1 -> второй MyService
|
||||||
|
IpcMarshaller m1(ch, 0);
|
||||||
|
IpcMarshaller m2(ch, 1);
|
||||||
|
MyServiceProxy<IpcMarshaller> obj1(m1);
|
||||||
|
MyServiceProxy<IpcMarshaller> obj2(m2);
|
||||||
|
|
||||||
proxy.add(7, 9);
|
// obj1 увеличивает свой счётчик.
|
||||||
int counter = proxy.get();
|
obj1.add(7, 9);
|
||||||
std::cout << "RESULT: " << counter << std::endl;
|
|
||||||
|
// obj2 увеличивает счётчик на текущее значение счётчика obj1.
|
||||||
|
obj2.add(0, obj1.get());
|
||||||
|
|
||||||
|
int c1 = obj1.get();
|
||||||
|
int c2 = obj2.get();
|
||||||
|
std::cout << "OBJ1: " << c1 << " OBJ2: " << c2 << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-4
@@ -4,6 +4,7 @@
|
|||||||
#include "ipc/IpcPipeChannel.h"
|
#include "ipc/IpcPipeChannel.h"
|
||||||
#include "ipc/IpcDispatcher.h"
|
#include "ipc/IpcDispatcher.h"
|
||||||
#include "rpc/RpcInvoker.h"
|
#include "rpc/RpcInvoker.h"
|
||||||
|
#include "rpc/RpcRegistry.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
@@ -16,10 +17,20 @@ int main() {
|
|||||||
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
||||||
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
||||||
|
|
||||||
// RPC‑уровень: инвокер и скелет, который лишь регистрирует методы.
|
// RPC‑уровень: реестр объектов и инвокер, который знает, как их вызывать.
|
||||||
RpcInvoker invoker;
|
RpcRegistry registry;
|
||||||
MyService realObj;
|
RpcInvoker invoker(registry);
|
||||||
MyServiceSkeleton skeleton(realObj, invoker);
|
|
||||||
|
// PoC: создаём два "реальных" объекта и оборачиваем их в Skeleton'ы,
|
||||||
|
// которыми владеет RpcRegistry.
|
||||||
|
MyService obj1;
|
||||||
|
MyService obj2;
|
||||||
|
RpcRegistry::ObjectId id1 =
|
||||||
|
registry.registerObject<MyServiceSkeleton>(obj1); // id1 == 0
|
||||||
|
RpcRegistry::ObjectId id2 =
|
||||||
|
registry.registerObject<MyServiceSkeleton>(obj2); // id2 == 1
|
||||||
|
(void)id1;
|
||||||
|
(void)id2;
|
||||||
|
|
||||||
// IPC‑диспетчер, который декодирует IpcMessage в RPC-вызовы и обратно.
|
// IPC‑диспетчер, который декодирует IpcMessage в RPC-вызовы и обратно.
|
||||||
IpcDispatcher dispatcher(ch, invoker);
|
IpcDispatcher dispatcher(ch, invoker);
|
||||||
|
|||||||
@@ -1,16 +1,41 @@
|
|||||||
#include "{{ cls.name }}.skeleton.h"
|
#include "{{ cls.name }}.skeleton.h"
|
||||||
|
|
||||||
{{ cls.name }}Skeleton::{{ cls.name }}Skeleton({{ cls.name }}& obj, RpcInvoker& inv)
|
{{ cls.name }}Skeleton::{{ cls.name }}Skeleton({{ cls.name }}& obj)
|
||||||
: invoker(inv) {
|
: obj_(obj) {
|
||||||
{% for m in cls.methods %}
|
|
||||||
invoker.registerMethod(&obj,
|
|
||||||
"{{ cls.name }}.{{ m.name }}",
|
|
||||||
&{{ cls.name }}::{{ m.name }});
|
|
||||||
{% endfor %}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcValue {{ cls.name }}Skeleton::dispatch(const std::string& method,
|
RpcValue {{ cls.name }}Skeleton::invoke(const std::string& method,
|
||||||
const RpcArgs& args) {
|
const RpcArgs& args) {
|
||||||
return invoker.dispatch(method, args);
|
const auto& table = handlers();
|
||||||
|
auto it = table.find(method);
|
||||||
|
if (it == table.end()) {
|
||||||
|
// Неизвестный метод — PoC: возвращаем 0.
|
||||||
|
return RpcValue::fromInt(0);
|
||||||
|
}
|
||||||
|
Handler fn = it->second;
|
||||||
|
return (this->*fn)(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<std::string, {{ cls.name }}Skeleton::Handler>&
|
||||||
|
{{ cls.name }}Skeleton::handlers() {
|
||||||
|
static const std::unordered_map<std::string, Handler> kHandlers = {
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
{ "{{ cls.name }}.{{ m.name }}", &{{ cls.name }}Skeleton::call_{{ m.name }} }{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
return kHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
RpcValue {{ cls.name }}Skeleton::call_{{ m.name }}(const RpcArgs& args) {
|
||||||
|
// PoC: единственный тип аргументов и результата — int.
|
||||||
|
int idx = 0;
|
||||||
|
{% for a in m.args %}
|
||||||
|
int {{ a.name }} = args[idx++].asInt();
|
||||||
|
{% endfor %}
|
||||||
|
int result = obj_.{{ m.name }}({% for a in m.args %}{{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||||
|
return RpcValue::fromInt(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "{{ cls.name }}.h"
|
#include "{{ cls.name }}.h"
|
||||||
#include "rpc/RpcInvoker.h"
|
#include "rpc/RpcRegistry.h"
|
||||||
|
#include "rpc/RpcValue.h"
|
||||||
|
|
||||||
class {{ cls.name }}Skeleton {
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Skeleton-обёртка над {{ cls.name }}, реализующая IRpcObject::invoke().
|
||||||
|
class {{ cls.name }}Skeleton : public IRpcObject {
|
||||||
public:
|
public:
|
||||||
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj, RpcInvoker& invoker);
|
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj);
|
||||||
|
|
||||||
// IPC-независимый диспетчер: принимает имя метода и RpcArgs.
|
RpcValue invoke(const std::string& method,
|
||||||
RpcValue dispatch(const std::string& method, const RpcArgs& args);
|
const RpcArgs& args) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RpcInvoker& invoker;
|
using Handler = RpcValue ({{ cls.name }}Skeleton::*)(const RpcArgs&);
|
||||||
|
|
||||||
|
// Статическая таблица method-name -> member-function.
|
||||||
|
static const std::unordered_map<std::string, Handler>& handlers();
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
RpcValue call_{{ m.name }}(const RpcArgs& args);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{ cls.name }}& obj_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user