Хорошо, первый вопрос:
Мне нужно знать, какие у меня есть альтернативы переключателю Incredible Evil (TM) в моем методе MethodCall для удаленной отправки объектов (RPC для бедняков).
virtual void methodCall(unsigned int method) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
switch(method) {
case 0: /* GetType */
/* emit object type to output queue as string */
vqueue.push(string(getType()));
break;
}
Конечно, каждый спрашивает, чего я пытаюсь достичь с помощью этого метода.
Я создал протокол (сеть), который реализует облегченную систему распределенных объектов, которая позволяет одному концу или другому выполнять вызовы методов на одном конце или другом, используя ссылки на объекты и индексы методов, каждый из которых является целым числом, передаваемым по сети (поэтому не может использовать указатели, ptr-to-method и т. д.). Кроме того, у меня есть карта реестра, которая отслеживает, какие ссылки на объекты являются живыми (имеется в виду, для какого идентификатора объекта целые числа входящие вызовы метода с другого конца являются действительными).
Аргументы обрабатываются с помощью стека четвертого стиля, поэтому у вас будет такой входящий поток, как:
3 5 math.plus() 7 math.multiply()
достичь эквивалента (3 + 5) * 7
vqueue<> .push () и getarg<> () обрабатывает помещение значений в исходящий поток через vqueue и чтение аргументов через getarg<> () с <> в зависимости от поддерживаемых типов, идущих по проводам
Это животное является базовым классом, и его целью является предоставление объектов, зарегистрированных для определенного сеанса соединения (каждая конечная точка имеет свой собственный реестр, который вставляется в каждый раз, когда создается новый объект для ссылки на удаленный узел.
/**
** @brief ABC for remotable objects.
**
** Base used for objects exchangeable via object references.
**
** Since secure referencing requires a way to
** track object lifetime a registry reference
** is required for construction.
*/
class object {
protected:
session &ctx; /**< @brief for objects to attach to the session's registry */
public:
/** @brief construction of an object @param sess reference to session to attach */
object(session &sess) :
ctx(sess) {
LOCK_COUT
cout << "object [" << this << "] ctor" << endl;
UNLOCK_COUT
ctx.register_object(this);
}
/** @brief base dtor to automatically unregister the object */
virtual ~object() {
LOCK_COUT
cout << "object [" << this << "] dtor" << endl;
UNLOCK_COUT
ctx.unregister(this);
}
/**
* @brief Get object's identity.
* @return Object identity string
*
* Overriden by superclass to announce it's identity.
*/
virtual const char *getType() {return "baseObject";}
/**
* @brief Method call switchboard.
*
* Overidden by superclass to implement methods callable
* by the remote. Currently the superclasses are using
* big switchbanks which looks plain evil but at this
* point I'm not sure of what to refactor with.
*
* @todo
* Base class to implement some sort of glue to take out the switch boilerplate?
* @todo
* some sort of static enum to get rid of the magic number
* method call #s from remote POV?
* @todo
* automatically declare the methods for method calling
* via some sort of macro or metacode?
*
*/
virtual void methodCall(unsigned int idx)=0;
};
Уродство заключается в том, что производные объекты реализуют реальный интерфейс. Это тот переключатель в MethodCall ():
class Account : public bvnet::object {
private:
s64 userId;
public:
Account(bvnet::session &sess,s64 who)
: bvnet::object(sess),userId(who) {}
virtual ~Account() {}
virtual const char *getType() {return "userAccount";}
virtual void methodCall(unsigned int method) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
switch(method) {
case 0: /* GetType */
/* emit object type to output queue as string */
vqueue.push(string(getType()));
break;
}
}
};
Итак, еще раз вопрос заключается в том, есть ли другой способ добиться нумерованного метода отправки чего-то более дружественного для C ++ и таких махинаций, как какой-то будущий объект, решающий, что он хочет сделать
MyFutureAccount : public Account {...}
(и боюсь, что я получил электронное письмо от яростного деления, которое я получил от разгневанного сопровождающего, который хотел сделать это, чтобы максимизировать близлежащий счетчик Гейгера …)
Кажется, что мне может потребоваться выполнить некоторую работу в конструкторе bvnet :: Object и настроить некоторую форму того, что C ++ делает внутренне (vtable), возможно, с использованием STL-карты intMethodId-to-ptrToMember (предоставляя нижестоящим элементам простой способ переопределить вещи) , Будут ли указатели на члены базового класса bvnet :: Object по-прежнему работать, как и ожидалось, в производных классах, расположенных ниже по потоку? Еще так много вопросительных знаков. Не уверен, что я на правильном пути или лаю не на том дереве в этом потенциальном решении …
Вместо того, чтобы вставлять больше (я не могу предсказать, что еще захотят увидеть другие, я мог бы просто указать вам на github, потому что это LGPL3 с открытым исходным кодом: https://github.com/gau-veldt/Minetest-Blockiverse/tree/master/blockiverse
Наиболее актуальный файл будет https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/protocol.hpp но возьми добычу на serverRoot в https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/server.hpp чтобы ясно увидеть, как злой метод переключения уже становится в дикой природе …
По крайней мере, одно возражение по этому вопросу приводит меня к выводу, что, по крайней мере, один человек может быть заинтересован в решении / ответе на этот вопрос.
Мне удалось удалить злые ключи и заменить их на то, что я маркирую как «отправленный вызов метода» или dmc в коде. Они отправляются при входе из сети для преобразования в вызовы методов, следовательно, из номенклатуры «отправленные вызовы методов»
Теперь иерархия использует функцию указателя на член, заключенную в адаптеры std :: function, которые являются типами значений std :: map:
typedef std::function<void(value_queue&)> dmcMethod;
typedef std::map<unsigned int,dmcMethod> call_map;
Веселье начинается в bvnet :: object (базовый класс), где карта вызовов инициализируется для ссылки на базовый класс dmc_GetType (функтор dmc для вызова переопределяемого GetType и отправки значения в очередь исходящих значений):
/**
** @brief ABC for remotable objects.
**
** Base used for objects exchangeable via object references.
**
** Since secure referencing requires a way to
** track object lifetime a registry reference
** is required for construction.
*/
typedef void(bvnet::object::*dmc)(value_queue&);
class object {
private:
/**
* @brief Displateched Method Call
*
* Implements dispatched method call (dmc)
*
* Superclass-installed dmc methods in dmcTable are
* callable by the remote. A superclass sets up his
* dmc methods in his ctor by accessing object's dmcTable
*
* As a plus the lock and value queue boilerplate has been
* been moved to the base class dispatcher and the dmc methods
* will be in locked context and given the value queue as a
* parameter. He also has an exception now to trap calls to
* an unimplemented method index.
*
* @todo
* some sort of static enum to get rid of the magic number
* method call #s from remote POV? I'll have the ctors store
* method labels in the base class dmcName map while it is
* setting up the dmc methods.
*
* @todo
* automatically declare the methods for method calling
* via some sort of macro or metacode?
*
*/
friend class session;
void methodCall(unsigned int idx) {
bvnet::scoped_lock lock(ctx.getMutex());
bvnet::value_queue &vqueue=ctx.getSendQueue();
auto dmcFunc=dmcTable.find(idx);
if (dmcFunc!=dmcTable.end()) {
(dmcFunc->second)(this,vqueue);
} else {
// called a method that doesn't exist
throw method_notimpl(getType(),idx);
}
}
protected:
session &ctx; /**< @brief for objects to attach to the session's registry */
call_map dmcTable; /**< @brief method mapper for method call and OO mechanism */
name_map dmcLabel; /**< @brief name labels of dmc methods */
void dmc_GetType(value_queue&); /**< @brief the GetType dispatched method call (dmc) */
private:
const string methodLabel(const unsigned int idx) {
const auto &s=dmcLabel.find(idx);
if (s!=dmcLabel.end()) {
return s->second;
}
return std::to_string(idx);
}
public:
/** @brief construction of an object @param sess reference to session to attach */
object(session &sess) :
ctx(sess) {
LOCK_COUT
cout << "object [" << this << "] ctor" << endl;
UNLOCK_COUT
ctx.register_object(this);
// dmtTable[0] is the GetType method
dmcTable[0]=&object::dmc_GetType;
dmcLabel[0]="GetType";
}
/** @brief base dtor to automatically unregister the object */
virtual ~object() {
LOCK_COUT
cout << "object [" << this << "] dtor" << endl;
UNLOCK_COUT
ctx.unregister(this);
}
/**
* @brief Get object's identity.
* @return Object identity string
*
* Overriden by superclass to announce it's identity.
*/
virtual const char *getType() {return "baseObject";}
};
Это пример нового объекта serverRoot, когда-то модифицированного для использования механизма dmc (монолитный MethodCall и безумие переключателя исчезли):
class serverRoot : public bvnet::object {
private:
BigInt cli_pub_mod;
BigInt cli_pub_exp;
Key *clientKey;
bool clientValid;
string challenge;
SQLiteDB db;
unsigned int randbits[8];
protected:
void dmc_LoginClient(value_queue &vqueue);
void dmc_AnswerChallenge(value_queue &vqueue);
void dmc_GetAccount(value_queue &vqueue);
public:
serverRoot(bvnet::session &sess)
: bvnet::object(sess) {
dmcLabel[1]="LoginClient";
dmcTable[1]=(dmc)&serverRoot::dmc_LoginClient;
dmcLabel[2]="AnswerChallenge";
dmcTable[2]=(dmc)&serverRoot::dmc_AnswerChallenge;
dmcLabel[3]="GetAccount";
dmcTable[3]=(dmc)&serverRoot::dmc_GetAccount;
clientValid=false;
challenge="";
clientKey=NULL;
}
virtual ~serverRoot() {
if (clientKey!=NULL)
delete clientKey;
}
virtual const char *getType() {return "serverRoot";}
};
Здесь важно отметить, что MethodCall больше не существует внутри serverRoot (или Account, clientRoot и последующих друзей), и что он должен что-то делать с dmcLabel [0] / dmcTabel [0] — он наследует настройки от bvnet: : Объектный ctor. Точно так же, если бы futureServerRoot создавал подкласс serverRoot, он бы унаследовал таблицу dmc от обоих cvnet :: Object и serverRoot. Таким образом, у меня теперь есть надлежащий механизм наследования в иерархии dmc, и нет электронной почты с ядерной бомбой от будущего сопровождающего (ну, в любом случае, по этой причине), пытающегося сделать класс futureServerRoot: public serverRoot;)
В целом полезный рефакторинг. Это исключило кучу котельной плиты (которая не понадобится и на последующих объектах). Шаблон dmc является менее громоздким в классах (реализации могут быть перемещены из встроенных классов или иным образом) и больше не застревает в злом монолитном переключении из ада.
Не идеально, но это большое улучшение по сравнению с тем, что у меня было раньше, и оно дает ответ на мой первоначальный вопрос.