Creating a Pex Plugin within a modality app

  !   Данная информация предназначена только только для IT-специалистов по системной интеграции модулей БИОСОФТ-М. (см. Руководства пользователя к программным продуктам)
Пакеты

Packages/PexLink остается как и раньше заниматься добавлением и просмотром измерений в обследованиях данной модальности.

В отдельный пакет Packages/PexPlugin выносится вся логика, расширяющая функциональность Pex, специфическими функциями, требуемыми для данной модальности.

Пример

См. Emix/Packages/PexPlugin где демонстрируются два типа плугина:

  • анализирующий данные обследований (EmixIndividualNorm вычисляющий демонстрационную индивидуальную норму для пациента)
  • дополняющий карточку кнопкой для специальных вычислений по полям (EmixOptimum заносящий в поле карточки значение демонстрационной "оптимальной нагрузки" вычисленное им по значениям других полей)

Emix для обоих демо показывает полученые вычисленных данных из карточки. В виде экспозированного кеша в первом случае, и простого скалярного значения во втором.

Две стороны, возможно в двух разных процессах

Плугины в Emix/Packages/PexPlugin структурированы так чтобы четко отделить логику, выполняемую в рамках процесса БД Pex и в рамках процесса самплера. Только в частном случае Pili оба процесса могут совпадать. Вся логика плугинов должна работать в любом варианте подключения к БД. Коммуникация между плугинной и самплерной стороной возможна только через официальные интерфейсы PexContact/PexInsert/Pi.

По этому плугины разделены на два Iface класса видимых приложению

  • CXxxPluginIface - на стороне БД
  • CXxxReceiverIface - на стороне самплера

"Receiver" может стать слишком узким термином если сторона на PexContact будет не только получать что то из карточки, но пока это по сути. "Receiver" просто инкапсулирует получение полей из карточки. Он относится к PexContact, общается с пакетом PexLink но вынесен из него т.к. не занимается регистрацией измерений.

Инициализация

Плугины подключаются к Pex при инициализации PexInsert для данной модальности. Инициализация оного остается как и раньше в пакете PexLink и вызывает CXxxPluginIface из пакета PexPlugin:

void CEmixPexInsertLinkImpl::OnInitEmixPexInsertLink(
        ref<CPexInsertConnectionIfaceGp> rPexInsertConnection)
{
    if (rPexInsertConnection->IsPexInsertConnectionAvailable())
    {
        ...
 
        x_rPexInsertConnection->
            ConnectInsertedModule(...
 
//VL: 2014-06-04 -U2
        // Install Picom callback
        m_rEmixIndividualNormPlugin->
            InitEmixIndividualNormPlugin(
                x_rPexInsertConnection);
 
        // Predefine card fields
        m_rEmixOptimumPlugin->
            InitEmixOptimumPlugin(
                x_rPexInsertConnection);
//VL.
    }
}

Как видим инициализаторы плугинов получат все необходимые им связи из rPexInsertConnection.

Реакция на события внутри БД

Плугин EmixIndividualNorm демонстрирует отслеживание добавления новых измерений и запуск новых обследований своим прибором для пересчета своих данных.

Для установки своего колбака на события в Picom используется RegisterPicomUpdateCallback(колбак, очередность):

void CEmixIndividualNormPluginImpl::OnInitEmixIndividualNormPlugin(
        ref<CPexInsertConnectionIfaceGp> rPexInsertConnection)
{
    rASSERT(...
 
    // register our callback
    m_rEmixPicomUpdate->_x_pEmixIndividualNormPlugin = this;
    rPexInsertConnection->
        RegisterPicomUpdateCallback(
            m_rEmixPicomUpdate,
            CPicomUpdateIfaceGp::E_PicomUpdateOrder_CEmixPicomUpdate);
}

См. CEmixPicomUpdate где происходит отработка соответствующих событий. Данный объект колбака может быть связан с EmixIndividualNormPluginImpl указателем ptr. Через этот указатель он делегирует выполнение всех вычислений объекту сессии плугина CEmixIndividualNormPluginImpl. Обращаю особое внимание на возможность такого указателя в данном сценарии работы.

Broadcast callbacks с множеством получателей (CMulticallGp) на котором построен PicomUpdateтребует декларации enum константы для каждого класса обработчика. Соответственно нужно добавить такую константу в CPicomUpdateIfaceGp::EPicomUpdateOrder. Не забывать напоминать при интеграции нового плугина!

Захват поля карточки

Другой тип связи с Pex демонстрирует плугин EmixOptimumPlugin. Он не занимается отработкой событий Picom а создает себе особое поле карточки, обвешивая его контекстными колбаками:

void CEmixOptimumPluginImpl::OnInitEmixOptimumPlugin(
            ref<CPexInsertConnectionIfaceGp> rPexInsertConnection)
{
    rASSERT(...
 
    PrepareOptimumButtonCardField(
        rPexInsertConnection->
            x_rPiCardConfig);
}

Здесь мы:

  • создаем поле
  • настраиваем егоза
  • меняем его обработчик UI (на кнопку)
  • сохраняем колбак, вызываемый по этой кнопке

Подробнее:

  • поле создается/обновляется вызовом Pi API DefinePatientFieldByPlugin()
  • параметры поля задаются через CPiFieldConfigIfaceGpUI поля карточки заменяется установкой своего колбака через SetPluginAdditionalFieldConfigCallbacks() со специальным ключем CPiFieldConfigIfaceGp::C_keyFieldCallbackForMainFieldInputUiв качесиве UI поля карточки выбирается встроенный в Picom особый тип поля "с кнопкой" CPicomFieldInputButtonIfaceGpтакая кнопка способна при ее нажатии загрузить DLL нужной модальности, восстановить ее колбак (ExposeIn) из настроек карточки и передать управлению плугину модальности для выполнения требуемых действий при нажатии кнопкипри инициализации такого поля задаютсяx_sPreloadFieldButtonModule = имя модуля плугинаx_keyFieldButtonCallbackCookie = ключ колбака модуля плугина в настройке поля карточкипараметры UI самой кнопкиотдельно с помошью еше одного вызова SetPluginAdditionalFieldConfigCallbacks() инициализатор добавляет к полю свой полиморфный колбак.
void CEmixOptimumPluginImpl::PrepareOptimumButtonCardField(
        ref<CPiCardConfigIfaceGp> rPiCardConfig)
{
    // define our main card field
    str sFieldKey = CEmixOptimumCallback::C_sFieldKeyForEmixOptimum;
    if not_null(
        ref<CPiFieldConfigIfaceGp> rPiFieldConfig =
            rPiCardConfig->
                DefinePatientFieldByPlugin(
                    sFieldKey))
    {
        TESTLOG("", "Defining field " + sFieldKey + "\n");
 
        // initialize options
        rPiFieldConfig->
            SetPiFieldLabelForUi(
                Enru_VL(
                    "Emix Optimum",
                    "Îïòèìóì Emix"));
 
        // hide it?
        rPiFieldConfig->
            x_bHiddenPiField = false;
 
        // install the callback
        {
            // "Button"-field will use this key to lookup card config looking for click-handler
            key keyClickHandlerCookie =
                CEmixOptimumCallback::C_keyFieldButtonCallbackCookieForEmixOptimum;
 
            //
            // install special "Button" field customizer plugin from Picom
            //
 
            ref<CPicomFieldInputButtonIfaceGp> rPicomFieldInputButton;
 
            // we aren't loaded automatically with Pex,
            //   so the button needs to preload us before we can handle anything
            rPicomFieldInputButton->
                x_sPreloadFieldButtonModule =
                    CProject::GGetGlobalProjectLogo();
 
            // our click handler address in the card field config
            rPicomFieldInputButton->
                x_keyFieldButtonCallbackCookie =
                    keyClickHandlerCookie;
 
            // UI
            {
                rPicomFieldInputButton->
                    x_slocFieldButtonText =
                        Enru_VL(
                            "Input or Recalculate",
                            "Ââîä èëè ïåðåñ÷åò") +
                            " //TODO: rename!";
 
                rPicomFieldInputButton->
                    x_slocFieldButtonTip =
                        //Enru(
                            "TODO: Emix button tooltip";
                            //"Input or Recalculate",
                            //"Ââîä èëè ïåðåñ÷åò");
            }
 
            // save field customizer in the card config
            rPiFieldConfig->
                SetPluginAdditionalFieldConfigCallbacks(
                    CPiFieldConfigIfaceGp::C_keyFieldCallbackForMainFieldInputUi,
                    rPicomFieldInputButton);
 
            //
            // install our cookie object for the button handler
            //
 
            // save our callback for field customizer installed above
            rPiFieldConfig->
                SetPluginAdditionalFieldConfigCallbacks(
                    keyClickHandlerCookie,
                    m_rEmixOptimumCallback);
        }
    }
}

См. CEmixOptimumCallback где происходит отработка нажатия вставленной в поле карточи кнопки. Обрашаю внимание что данных колбак сохраняется и восстанавливается экспозицией, вися в мапе конфига карточки. У него нет возможности после восстановления получить ptr на объект CEmixOptimumPluginImpl. Поэтому объект сессии CEmixOptimumPluginImpl выполняет только инициализацию и потом больше не используется в данном примере.

Данный плугин должен выполнять всю свою работу не располагая никаким глобальным контекстом а только ссылкой ref<CPiFieldValueIfaceGp> на значение карточки для данного пациента для которого нажата кнопка. И через Pi API из него можно выудить всю нужную информацию о пациенте.

    CEmixIndividualNormPluginImpl::CalcEmixIndividualNorm(
            ref<CPiPatientIfaceGp> rPiPatient)

Обращаю внимание что данные измерений доступны плугину только для чтения. Категорически запрещается пытаться их изменять даже если через запреты SEMIPUBLIC(_picom_) просочились Set-методы!

Сохранение данных в полях карточки

С полем карточки можно ассоциировать два значения.

  • простой скаляр как обычно
  • структурный экспозируемый кеш с результатами вычислений (SetPluginAdditionalFieldValueCache)
Декларация поля

делается DefinePatientFieldByPlugin() (см. примеры в Emix)

Задание значения полю

демонстрируется с использованием SetPiFieldValueAsStr()

Сохранение экспозированного кеша

см. SetPluginAdditionalFieldValueCache()

Последовательность добавления плугина к проекту
  • Убедится чтоинициализация PexInsertобновлена для поддержки соединения с Picom-PexInsert
  • Скопировать пакет PexPlugin из Emix
    • заменить слова Emix на свой проект
    • выбрать какая из заготовок ближе задаче и удалить другую если не нужна
      • CEmixIndividualNorm*... + CEmixPicomUpdate.* вычисляют по сохраненным измерениям и сохраняют структуру в кеше поля карточки
      • CEmixOptimum*... + CEmixOptimumCallback.* добавляют поле с кнопкой, вычисляющее свое значение по другим полям
  • Добавить вызов инициализации плугиновв OnInit....PexInsertLink (см. выше и код в CEmixPexInsertLinkImpl::OnInitEmixPexInsertLink())
  • Вызвать функции из CEmixIndividualNormReceiverIface/CEmixOptimumReceiverIface получающие результаты из карточки для проведения текущего обследования
  • Если значения этих полей нужны в самплере то в PexLink добавляются по аналогии с Emix
    • rp<CPiAddFieldValueIfaceGp> LookupCurrentPatientPluginFieldValueCache()
    • str LookupCurrentPatientFieldValueOr()
  • Убедится что работает и с Bica и c Pili перевычисляя и при добавлении измерений и/или по кнопке появившейся в карточке и все это вычитывается правильно из Receiver функций
  • Исправить на свою прикладную логику