Вверх ↑
Этот топик читают: Гость
Ответов: 167
Рейтинг: 7
#1: 2024-03-30 14:25:58 ЛС | профиль | цитата
Компоненты, реализующие паттерн Publisher-Subscriber в хи.
События идентифицируются именами, и хранятся в канале событий (EventChannel).
EventPublisher и EventSubscriber позволяют опубликовать/подписаться на событие в канале.
"Опубликовать" означает то, что при вызове точки doPublish будут вызваны все точки onPublish подписчиков на событие этого имени в одном канале. Возврат управления происходит когда все подписчики завершат работу.
Подписчикам передаются все данные из потока, опубликовавшего событие.



Детали реализации

Дабы не делать линейный поиск события по имени каждый раз как мы его публикуем, компоненты EventPublisher/EventSubscriber заранее находят массив подписчиков в списке, при их создании или вызове метода doSetName. EventPublisherEx - исключение, так как он и был создан с целью публикации по "любому" имени.

Реализована защита от рекурсии - в списке событий хранится не только массив подписчиков, но и флаг того, запущено-ли это событие уже. При попытке вызова появится MessageBox и публикация будет проигнорирована.
Не рекомендуется устанавливать имя события компонента EventSubscriber из его точки onPublish, так как это предотвращает удаление старого события из списка, если на него больше нет подписчиков и издателей (следствие того, что это событие уже будет запущено, и если оно было запущено из EventPublisherEx, и так как он он не увиличивает счётчик ссылок ноды - это бы привело к доступу к освобождённой памяти, что нехорошо)

Само собой всё это дело непотокобезопасно - больше одного потока не могут публиковать события в один и тот-же канал событий. Однако на каждый поток может быть свой канал событий - это должно работать без проблем, так как иного общего стейта нет.

Поддерживаются оба компилятора, Hiasm 4.05 build 186.

Ссылка на zip архив с примерами :
https://drive.google.com/file/d/1UTtXN_fUSDWyWvTg0vTckZvYMZPv0L8Z/view?usp=sharing

Точка doEventName позволяет установить имя события динамически, в обоих компонентах EventPublisher и EventSubscriber
Пустое имя события обрабатывается по особенному - подписка и отписка на событие с таким именем, а так-же публикация такового ничего не делает. Поэтому, это хороший способ отписаться от события (вызвав точку doEventName с пустой строкой).

Примеры, где эти компоненты могут быть полезны

- Вызов логики в динамических контейнерах. (Во всех разом, без необходимости делать цикл с ##select)
- Динамичность. Никто не запрещает менять имена событий в рантайме, добавлять к ним числовые префиксы/постфиксы. Можно свой интерпретатор сделать чёрт возьми, без кучи case/ifelse!
- Глобальный лог ошибок. И вообще любой конечный обработчик чего-либо, который используется во многих частях схемы
- Удобство разработки. Не нужно в больших схемах тащить линии через кучу контейнеров в другую кучу контейнеров - вызовите соответствующиее событие.
При неаккуратном ображении это может привести к обратьному результату, поэтому старайтесь именовать события чётко и ясно, минимизировать количество событий в пределах одного контейнера (там вы можете и так провести линию).

Редактировалось 12 раз(а), последний 2024-04-10 21:11:06
карма: 0
c, c++, lua
2
Голосовали:envoy_sky, Assasin
Ответов: 4630
Рейтинг: 749
#2: 2024-04-09 20:05:25 ЛС | профиль | цитата
Интересная вещь с точки зрения реализации. Хотя и нарушает визуализацию алгоритмов в HiAsm.

Компоненты - не смотрел.
Не совсем понятна роль 3-го компонента EventChannel. Предполагаю, компонент для обработки события EventSubscriber и сам занесет имя события в невидимый список в момент задания нового имени.

Потокобезопасность - да, надо внутри защитить работу со списком событий/подписчиков и флагом "уже выполняется".
Не знаю как реализована "защита от рекурсии", но нужно убедиться что при вызове второго события (из параллельного потока), пока выполняется первое, второе не потеряется. А для этого у каждого события должна быть критическая секция, чтобы вызвавшие становились в очередь. Можно добавить свойство события "что делать, если уже выполняется": пропускать или ждать.
Во втором случае могут быть dead lock на крит. секции при вызове из главного потока.

Можно добавить методы для включения/отключения события (что ещё нарушит визуализацию...)

Возможно, не очень удачный термин doPublish/onPublish выбран для вызова события. Вероятно, более понятно будет что-то типа doFireEvent, doCallEvent, onEvent.
карма: 26

0
Ответов: 167
Рейтинг: 7
#3: 2024-04-10 20:35:29 ЛС | профиль | цитата
Netspirit писал(а):
Не совсем понятна роль 3-го компонента EventChannel.

Он решает проблему глобального стейта. Я бы мог сделать как в GlobalVar и Mutex - общий список на компоненты, но это бы привело, как вы и сказали, к необходимости добавления критической секции. А так, EventCnannel - весь контекст который нужен компонентам, пока только один поток одновременно работает с одним каналом событий - всё будет работать очень даже стабильно и потокобезопасно (проверил).

+ таким образом можно разделять события на категории. Разные каналы событий не будут мешать друг-другу.
В примерах есть схема, где весь список имён событий из EventChannel из точки ANames добавляется в список, и по выбору элемента списка вызывается соответствующее событие. Если этот пример расширить - могут сосуществовать другие каналы событий, которые не будут в обычных условиях доступны из оригинального канала.
Netspirit писал(а):
Потокобезопасность - да, надо внутри защитить работу со списком событий/подписчиков и флагом "уже выполняется"

Проблема этого в производительности. Блокировки не бесплатные, но было-бы неплохо в будущем добавить свойство на опциональное их включение на время отладки схемы, например...
Netspirit писал(а):
Не знаю как реализована "защита от рекурсии", но нужно убедиться что при вызове второго события (из параллельного потока), пока выполняется первое, второе не потеряется.

Реализуется именно что ингорированием события, если оно уже обрабатывается
Есть пример, где на MT потоках и двух каналах событий сделана Очередь событий - по публикации, события добавляются в MT стек, по тику таймера, по одному, забираются со стека и публикуются по имени, с данными в MT потоке.
Netspirit писал(а):
Можно добавить свойство события "что делать, если уже выполняется": пропускать или ждать.

Хмм... Это хорошая идея. Вообще "вдохновление" на создание этих компонентов у меня вообще возникло именно тогда, когда я делал другие компоненты, у которых в компоненте-контексте была единая точка, вызывающаяся в случае ошибок из самого контекста и компонентов работы с ним.
Как будет время, обязательно добавлю точку event в контекст, вызывающееся при возникновении рекурсии, и точку на ситуацию, когда у события нет подписчиков.
Netspirit писал(а):
Можно добавить методы для включения/отключения события (что ещё нарушит визуализацию...)

Вы опоздали - это уже есть Если поменять имя события в компоненте Издателя/Подписчика на пустую строку (точка doEventName), то он отпишется от события (Пустая строка - единственное некорректное имя события... Стоило Это задокументировать... Упс)
ИМХО, При правильном применении эти компоненты - золото. Можно было-бы создать сто компонентов именованных процедур, очередей, а можно просто использовать эту тройку для реализации такого поведения)
Netspirit писал(а):
Возможно, не очень удачный термин doPublish/onPublish выбран для вызова события. Вероятно, более понятно будет что-то типа doFireEvent, doCallEvent, onEvent.

Увы, с этим я наврятли уже что-то сделаю - это сломает обратную совместимость со схемами, которые уже есть у меня (и не только) и активно используют эти компоненты.

Редактировалось 7 раз(а), последний 2024-04-10 20:53:00
карма: 0
c, c++, lua
0
Ответов: 4630
Рейтинг: 749
#4: 2024-04-10 21:33:19 ЛС | профиль | цитата
UtoECat писал(а):
таким образом можно разделять события на категории

По-моему, реализуется чисто соглашением по именованию, типа "CatX.EventY"
UtoECat писал(а):
Можно было-бы создать сто компонентов именованных процедур, очередей, а можно просто использовать эту тройку для реализации такого поведения

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

Редактировалось 1 раз(а), последний 2024-04-10 21:34:08
карма: 26

0
Ответов: 167
Рейтинг: 7
#5: 2024-04-10 21:41:39 ЛС | профиль | цитата
Netspirit писал(а):
По-моему, реализуется чисто соглашением по именованию, типа "CatX.EventY"

Но есть нюансы :
- большое количество событий в одном канале будет медленновато, потому как там линейный поиск. Именно на момент переподписки или изначальной подписки/динамической пукбликации.
- Строки вот так морочать не всегда удобно... Но это уже скорее субъективщина.
Netspirit писал(а):
Но вот добавлять незвестные события в процессе работы программы, которые будуть выполнять не предусмотренные разработчиком действия - я не представляю как

Динамические контейнеры + список строк в файле, например. Составлять события, которые будут по очереди вызывать более мелкие, опять-же, как свой скриптовик.. Но это сомнительно, хоть и возможно.
Netspirit писал(а):
только разработчик может предусмотреть реакцию на событие, а значит и сам перечень событий.

Это полезно, опять-же, для перенаправления событий в другой канал, без ручного создания кучи компонентов EventSubscriber или дублирования списка событий ручками для создания динамических контейнеров. Так просто удобнее. В процессе пользования компонентами я ни разу не пожалел о том, что дал такую возможность.

И да, этот массив из точки ANames только для чтения. Менять имена с его помощью или добавлять новые - нельзя.

Редактировалось 5 раз(а), последний 2024-04-10 21:49:45
карма: 0
c, c++, lua
0
5
Сообщение
...
Прикрепленные файлы
(файлы не залиты)