-
Notifications
You must be signed in to change notification settings - Fork 18
Принципы взаимодействия плагинов
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.
Подробный гайд по написанию плагина
Основное
- Сборка и запуск
- Как пользоваться
- Принципы взаимодействия плагинов
- События и команды
- Обработка речи
- Создание отдельных модулей
- Сценарии
- Информация о поддержке языков программирования
API
- Интерфейс взаимодействия с ядром
- API плагинов
- Элементы управления
- Остальные плагины
- Внешние плагины | DeskChan как сервер
Характер
А как...