Skip to content

Принципы взаимодействия плагинов

CORRUPTOR2037 edited this page Jul 31, 2018 · 10 revisions

DeskChan - модульная система, которая специально пишется такой, что её можно без вреда для неё в любой момент дополнить плагином. Такое устройство налагает серьёзные ограничения на взаимодействия плагинов. Одна из самых главных - это то, что плагины в DeskChan не взаимодействуют между собой непосредственно, а используют посредника. Потому что иначе её модульность будет под угрозой, ведь любая неудовлетворённая зависимость, возникшая при изменении или удалении модуля, грозит сломать всю программу. С другой стороны, вам не нужно копаться в коде DeskChan для того, чтобы её использовать.

Все плагины взаимодействуют между собой через определённый интерфейс PluginProxyInterface. Для любых взаимодействий между плагинами обязательно используйте его, а не пытайтесь вызвать классы напрямую.

Сообщения

Все взаимодействия между плагинами происходят с помощью интерфейса сообщений, который предоставляется плагином core. Этот плагин хранит все подключенные к программе плагины, предоставляемый ими интерфейс и зависимости. Все запросы вы компонуете ваши данные в сообщения и посылаете плагину core, и вам не надо думать о том, получит ли кто-либо ваше сообщение и кто это будет (за исключением запросов с требованием ответа).

Любое сообщение имеет:

  • Тег - текстовое представление функции. Тег принято составлять в формате отправитель:название. Запрещено использовать служебный символ # в теге, если не указано иное.
  • Данные - аргументы функции (данные могут быть нескольких типов или равны null, если они не нужны). Каждый прослушиватель сам задаёт, какие данные он принимает.

С помощью функции sendMessage вы можете отправлять сообщения:

sendMessage( "DeskChan:say" , "Привет!" );

Разрешено использовать только следующие типы данных для сообщений:

  • Boolean
  • String
  • Integer
  • Float
  • Double
  • * Map<String, Object>
  • * List<Object>

* - под Object понимаются только объекты перечисленных типов.

Данное требование необходимо для того, чтобы любое сообщение могло быть сериализовано и десериализовано в JSON, ведь нет никакой гарантии, что другая сторона будет всего лишь модулем, а не отдельной программой на несовместимом с JVM языке и даже возможно на другой машине.

Подписчики

На каждое сообщение могут быть подписано сколько угодно плагинов, в том числе ни один. Каждый подписанный на тег плагин получает все сообщения, которые отправлены с этим тегом. Подписаться на сообщения можно следующим образом:

addMessageListener("gui:register-simple-actions",
    (sender, tag, data) -> {
        /// тут вы выполняете какие-либо действия с присланными данными
    }
};

где:

  • sender - имя плагина, который отправил вам это сообщение (опционально с идентификатором возврата через #)
  • tag - на какое имя отправлено сообщение
  • data - отправленные данные (могут быть null)

Функция, которую вы передаёте в addMessageListener, не обязана быть анонимной, иначе говоря вы можете создать эту функцию в другом месте и просто передать ей управление, либо создать экземпляр класса ResponseListener и передать этот экземпляр функции.

Возврат

Зачастую необходимы такие операции, в которых нужно вызвать функцию и получить от неё результат выполненных действий. К сожалению, реализовать подобный механизм безукоризненно в данной системе невозможно. Но можно.

Если вы хотите, чтобы после выполнения функция вернула вам результат, вам необходимо использовать функцию sendMessage с тремя аргументами:

  • тег
  • данные
  • функция обратного вызова / callback

Внутри ядра эта функция сохраняется, но не передаётся далее. Вместо этого получатель сообщения в качестве sender получит не только имя плагина, но и идентификатор возврата. Например, это будет выглядеть так: "core#3". В случае наличия такого идентификатора (вы можете просто проверить sender на наличие решётки) вы обязаны отправить ответное сообщение, указав sender в качестве тега.

Со стороны отправителя это выглядит таким образом:

pluginProxy.sendMessage( "core-utils:notify-after-delay" , 100000,
                            (sender, tag, data) -> {
                                /// тут вы выполняете какие-либо действия с присланными данными
                            }
);

А со стороны получателя:

pluginProxy.addMessageListener("gui:register-simple-actions",
    (sender, tag, data) -> {
        /// тут вы выполняете какие-либо действия с присланными данными
        Map<String, Object> toReturn = new HashMap<>();
        toReturn.put("data", "hello");
        /// отправляете ответ
        pluginProxy.sendMessage(sender, toReturn);
    }
};

Помимо обычного возврата есть другой типа возврата, когда ваше сообщение вызывают сразу после того, как на ваш запрос ответили все подписанные на запрос плагины. Для этого используется 4-харгументная реализация sendMessage.

Альтернативы

Возможно вы слышали о таком паттерне программирования, как hooking - перехватывание. Подобное вы могли видеть при работе с операционной средой или же в wordpress, где это называется фильтрами. Суть в том, что в программе существует определённый канал обработки данных и канал выполнен так, что в него можно внедриться. У нас это называется альтернативами.

Например, вы хотите, чтобы ваш персонаж заикался, тогда вы скорее всего сделаете плагин, который будет внедряться в процесс подготовки фразы, и там будете фразу видоизменять.

Помимо этого альтернативы можно применять для создания нескольких реализаций одной и той же операции, например для вывода сообщений на экран можно использовать визуальный интерфейс, мессенждер или консоль, тогда это надо делать через альтернативы.

Обычно такие каналы обработки данных начинаются с названия "DeskChan", например "DeskChan:say" или "DeskChan:request-user-speech". Все остальные программисты договариваются, что будут отсылать большую часть сообщений именно на эти каналы, а дальше вы можете их перегрузить.

Канал состоит из цепочки прослушивателей сообщений, у каждого из которых есть свой приоритет. Чем больше приоритет у прослушивателя, тем раньше его вызовут в цепи. В свою очередь, каждый прослушиватель имеет право передавать или не передавать управление дальше по цепи, а также выбирает, какие конкретно данные он передаст.

Чтобы добавить прослушиватель, воспользуйтесь сообщением "core:register-alternative" или "core:register-alternatives". Пример:

pluginProxy.sendMessage("core:register-alternative",
			new HashMap<String, Object>() {{
				put("srcTag", "DeskChan:say");
				put("dstTag", "gui:say");
				put("priority", 100);
			}}
);

Теперь прослушиватель "gui:say" будет получать все сообщения, отправленные на "DeskChan:say". Предположим, что на событие "DeskChan:say" подписаны два прослушивателя: "gui:say" и "core-utils:say", причём с приоритетами 100 и 50 соответственно. Тогда первым будет вызван "gui:say". В свою очередь "gui:say" может передавать или не передавать управление дальше по цепи. Если он вдруг захочет это сделать, то ему следует сделать это так:

pluginProxy.addMessageListener("gui:say",
    (sender, tag, data) -> {
        /// тут вы выполняете какие-либо действия с присланными данными
        Map<String, Object> map = (Map) data;
        map.put("data", "changedData");
        /// передаёте управление дальше
        pluginProxy.sendMessage("DeskChan:say#gui:say", map);
    }
};

Как видите, прослушиватель просто вызывает то же самое сообщение "DeskChan:say", но дополнительно указывает, что он уже свою часть выполнил, управление будет передано следующему в цепи.

Яркий пример работы альтернатив можно увидеть в файле core/CorePlugin.java

Отладка

Для экспериментов возможно использовать пункты "Отправить сообщение" внутри вкладки "Основное" опций DeskChan. В верхнем поле ввода можно указать тег сообщения, а в нижнем - данные в формате JSON. При нажатии кнопки сообщение будет отправлено от имени плагина gui.

Clone this wiki locally