Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.6. Принципы создания автокомпилируемых моделей блоков
§3.6.13. Вызов функций блоков
§3.6.13.3. Вызов функции у одного блока
Описывается вызов функции у блока с известным идентификатором, рассматриваются способы определения этого идентификатора.
В §3.6.13.2 был рассмотрен вызов функции у всех блоков подсистемы. Если идентификатор блока известен, можно вызвать функцию только у этого конкретного блока. Основной вопрос здесь – как получить идентификатор этого блока. Как правило, мы хотим вызвать функцию у блока, обладающего какой-то особенностью. Например, нам может быть известно полное имя этого блока, или этот блок может быть соединен связью с вызывающим. Рассмотрим оба этих варианта, начав с самого простого – будем вызывать функцию у блока, имя которого нам известно.
Созданная в §3.6.13.2 модель управляющего блока позволяла увеличивать, уменьшать и сбрасывать выходы у всех управляемых блоков в одной подсистеме. Сделаем еще одну модель, которая будет сбрасывать выход только у одного управляемого блока, полное имя которого мы будем задавать в настройках. Напомним, что полное имя блока начинается с двоеточия, за которым следует последовательное перечисление через двоеточие всех имен подсистем на пути от корневой подсистемы до этого блока, которое завершается именем самого блока. Например, полное имя «:Sys1:Sys100:Block1» говорит о том, что блок с именем «Block1» находится в подсистеме «Sys100», которая, в свою очередь, находится в подсистеме «Sys1» корневой подсистемы.
Создадим в схеме новый блок с автокомпилируемой моделью, выберем на левой панели редактора вкладку «», нажмем в ее верхней части кнопку «» (см. рис. 344) и заполним окно добавления параметра согласно рис. 472.
Рис. 472. Добавление в модель блока строкового параметра
В поля этого окна введены следующие значения:
- на панели «»:
- «» – «RemoteName» (так будет называться наш настроечный параметр);
- «» – «rdsbcppString» (это специальный класс для хранения строк, создаваемый модулем автокомпиляции);
- «» – оставлено пустым;
- на панели «»:
- «» – установлен флажок;
- «» – «имя связанного блока»;
- «» – 100;
- «» – «ввод».
После нажатия кнопки «» в модель блока будет добавлен параметр с именем RemoteName, в который пользователь сможет вводить любую строку. Окно для ввода параметра и процедуры его загрузки и сохранения вместе со схемой будут добавлены в модель автоматически (см. §3.5.6).
Наш новый блок будет искать в схеме другой блок, имя которого содержится в параметре RemoteName, и вызывать у него функцию «UserManual.PictureClick.Cmd» с параметром 3, которая сбросит его выход (эту функцию мы придумали и создали в конце §3.6.13.2). Структура TPictureClickFuncParam, в которую записывается параметр функции, описана в файле «pictureclick.h», поэтому, как и в предыдущих моделях, нам нужно добавить в модель команду для его включения. Раскроем на вкладке «» левой панели редактора раздел «», дважды щелкнем на его подразделе «», и, в открывшейся справа вкладке «», введем эту команду:
#include "pictureclick.h"
Теперь нужно добавить в модель блока функцию «UserManual.PictureClick.Cmd» – сделаем это точно так же, как делали раньше, и, точно так же, переименуем для краткости создаваемый для функции объект в rdsfuncUMPC_Cmd.
Сбрасывать значение выхода блока, имя которого записано в параметре RemoteName (то есть вызывать его функцию), наш блок будет по щелчку пользователя. Добавим в нашу модель реакцию на нажатие кнопки мыши. Раскроем раздел «» на вкладке «» (см. рис. 451), дважды щелкнем на подразделе «», и на появившейся вкладке введем следующий текст:
if(MouseData->Button==RDS_MLEFTBUTTON) // Нажата левая { RDS_BHANDLE b; // Получаем идентификатор блока по полному имени b=rdsBlockByFullName(RemoteName.c_str(),NULL); if(b!=NULL) // Такой блок есть { TPictureClickFuncParam param; // Структура параметров param.servSize=sizeof(param); // Размер param.Command=3; // Команда (сброс) rdsfuncUMPC_Cmd.Call(b,¶m); // Вызов функции } } else // Нажата не левая Result=RDS_BFR_SHOWMENU; // Разрешаем контекстное меню
Как и в предыдущих моделях, все действия мы выполняем только тогда, когда нажата левая кнопка мыши, то есть если поле Button структуры MouseData равно константе RDS_MLEFTBUTTON. В этом случае мы вызываем функцию rdsBlockByFullName, которая ищет в схеме блок по его полному имени. Функция возвращает уникальный идентификатор блока, который имеет принятый в RDS тип RDS_BHANDLE: его мы записываем во вспомогательную переменную b. В первом параметре функции rdsBlockByFullName передается строка (char*) с полным именем блока. Поскольку RemoteName – это объект класса rdsbcppString, хранящего эту строку, для доступа к самой строке внутри этого объекта мы используем его функцию-член c_str без параметров. Во втором параметре rdsBlockByFullName может передаваться указатель на структуру описания, которую эта функция заполняет параметрами найденного блока. Нам это не нужно, поэтому мы передаем NULL.
Если блок с именем, записанным в RemoteName, существует в схеме, rdsBlockByFullName вернет ненулевой идентификатор, то есть значение b не будет равно NULL. В этом случае мы заполняем вспомогательную структуру param параметрами вызываемой функции (мы уже добавили в глобальные описания модели команду включения файла «pictureclick.h», в котором описан тип этой структуры TPictureClickFuncParam, поэтому мы имеем право использовать ее в модели). В поле servSize мы, как обычно, записываем размер самой структуры, а в поле команды Command – число 3, поскольку для созданных нами блоков это число считается командой сброса. Заполнив структуру параметров, мы вызываем функцию «UserManual.PictureClick.Cmd» у блока b, передавая в качестве ее параметра указатель на структуру param. Выглядит этот вызов так:
rdsfuncUMPC_Cmd.Call(b,¶m);
Здесь rdsfuncUMPC_Cmd – это объект, созданный модулем автокомпиляции для функции «UserManual.PictureClick.Cmd» (мы указывали имя объекта при добавлении функции в модель), а Call – одна из функций-членов этого объекта.
Если нажата не левая кнопка мыши, мы, как и раньше, присваиваем переменной Result значение RDS_BFR_SHOWMENU, чтобы не блокировать контекстное меню.
Разрешим для созданного нами блока реакцию на мышь (см. рис. 452), и введем в окне его настройки, вызываемом пунктом «» контекстного меню, полное имя какого-нибудь уже имеющегося в схеме блока, который увеличивает, уменьшает или сбрасывает свой выход по щелчкам мыши (рис. 473). Если отображение имен блоков запрещено, имя любого блока можно прочесть в строке состояния подсистемы, выделив его.
Рис. 473. Управляемый блок «Block23» (слева), сбрасывающий блок (в центре, выделен)
и окно его настройки (справа)
Теперь, если запустить расчет, щелчок на созданном нами блоке будет сбрасывать выход блока, имя которого введено в окне настроек. При этом, поскольку в настройках мы задаем полное имя связанного блока, наш новый блок можно разместить в любой подсистеме. В отличие от управляющего блока, созданного в §3.6.13.2, он не обязан находиться в одной подсистеме с управляемым, как на рис. 473.
Достаточно часто бывает нужно вызвать какую-либо функцию у блоков, соединенных связями с данным блоком. Часто такой вызов используют для организации передачи данных в режимах моделирования и редактирования, в которых связи не работают (такой пример будет рассмотрен ниже). В некоторых, более редких, случаях, связи, соединяющие блоки, вообще не используются для передачи данных – вместо этого они просто показывают блокам их соседей, а передача данных между ними производится только за счет вызовов функций. В §2.13.4 руководства программиста, например, рассматривается использование вызовов функций для поиска путей в графе, составленном из блоков и связей. Здесь мы не будем рассматривать подобные примеры из-за их сложности, вместо этого добавим в один из ранее созданных нами блоков возможность передачи данных по связям в режиме моделирования.
Рис. 474. Кольцо из блока
и поля ввода
С точки зрения пользователя, у созданного ранее блока, который по щелчкам мыши на элементах картинки увеличивает, уменьшает или сбрасывает в ноль значение своего выхода (см. §3.6.11), и который мы использовали в качестве примера начиная с §3.6.13.2, есть один, достаточно серьезный, недостаток: он не позволяет непосредственно ввести значение выхода с клавиатуры. Можно, конечно, организовать ввод числа с клавиатуры в модели этого блока, однако, это потребует слишком больших усилий. Гораздо проще будет добавить в переменные блока целый вход, значение которого без изменений передается на выход, и соединить его связью со стандартным полем ввода, в которое пользователь будет вводить число (рис. 474). Чтобы увеличение, уменьшение и сброс, выполняемые нашим блоком, отражались и на поле ввода, вход поля ввода нужно будет соединить с выходом нашего блока, замкнув их в кольцо. Таким образом, ввод числа в поле ввода будет приводить к тому, что значение с выхода этого поля будет по связи передаваться на вход нашего блока и далее, без изменения, на его выход. Щелчки на нашем блоке будут приводить к изменению значения на его выходе, которое будет по связи попадать в поле ввода и появляться в нем. В результате, выход поля ввода и нашего блока будут все время синхронизированы.
Однако, все это будет работать только в режиме расчета, то есть в единственном режиме RDS, в котором значения передаются по связям. Если остановить расчет, щелчки на нашем блоке будут изменять значение его выхода, но в поле ввода они попадать не будут – связь между ними не будет работать до запуска расчета. Чтобы заставить кольцо из нашего блока и поля ввода синхронно работать и в режиме моделирования, нужно использовать функцию «Common.ControlValueChanged», которую поддерживают все библиотечные блоки пользовательского интерфейса: поля ввода, ручки и т.п. Работает она так: при изменении значения в одном из таких блоков модель этого блока принудительно передает данные выходов по связям независимо от режима RDS (для этого используется специальная сервисная функция rdsActivateOutputConnections), а затем вызывает у всех соединенных с этими выходами блоков функцию «Common.ControlValueChanged». У этой функции нет параметров, блоки должны считать измененное пользователем значение со своих входов. Таким образом, чтобы синхронизировать наш блок с полем ввода в любом режиме, нам нужно, во-первых, при любом изменении выхода блока принудительно передавать новое значение по связям и вызывать у всех блоков, соединенных с этим выходом, функцию «Common.ControlValueChanged» (так выход нашего блока попадет в поле ввода), и, во-вторых, реагировать на вызов этой функции, передавая свой вход на выход (так значение из поля ввода попадет на выход нашего блока).
Здесь очень важно вовремя прервать цепочку вызовов. Если не предпринять никаких специальных мер, при вводе нового значения в поле ввода это поле вызовет наш блок, наш блок установит это значение на своем выходе и вызовет поле ввода, поле ввода примет это значение и снова вызовет наш блок, наш блок снова вызовет поле ввода и т.д. Это будет продолжаться до тех пор, пока стек приложения не переполнится и не произойдет аварийное завершение RDS. Чтобы избежать этого, проще всего ввести в данные блока специальный флаг, который мы будем взводить перед вызовом «Common.ControlValueChanged» у соединенных блоков и сбрасывать после него. Реагируя на вызов функции, модель будет проверять этот флаг: если он взведен, значит, данный блок сам уже вызвал у кого-то «Common.ControlValueChanged» – то есть, он уже принимает участие в цепочке вызовов, и снова вызывать эту функцию у соседей не нужно (подробнее о применении такого флага можно прочесть в §2.13.2 руководства программиста).
Таким образом, чтобы дать пользователю возможность объединять наш блок с полем ввода или какими-либо другими интерфейсными блоками, нам нужно внести в его модель следующие изменения:
- добавить в модель целый вход для подключения выходов других блоков и логический флаг для блокировки повторного вызова функции;
- для передачи нового значения выхода в соединенные блоки при любых изменениях выхода принудительно активировать выходные связи и вызывать у подключенных к ним блоков функцию «Common.ControlValueChanged»;
- для приема значения, измененного другими блоками, добавить реакцию на вызов «Common.ControlValueChanged», в которой значение входа будет передаваться на выход (при этом необходимо принудительно активировать выходные связи и выполнить все действия, описанные в предыдущем пункте);
- в реакции на такт расчета просто передавать значение входа на выход, чтобы блок работал в режиме расчета так же, как и все обычные блоки.
Самое сложное среди этих действий – поиск идентификаторов блоков, подключенных к выходу данного, для вызова у них «Common.ControlValueChanged». Это можно выполнить двумя способами:
- В цикле перебрать все связи, подключенные к выходу блока, при помощи функции rdsGetBlockLink. Для каждой из этих связей перебрать все ее точки и, при помощи функции rdsGetPointDescription, получить идентификатор блока, соединенного с точкой.
- Вызвать функцию rdsEnumConnectedBlocks, которая переберет все блоки, соединенные с данным, и для каждого из них вызовет функцию специального вида, указатель на которую мы передадим в параметрах.
Мы выберем второй путь. Хотя программа модели при этом может показаться более запутанной, она будет значительно короче, поскольку не нужно будет организовывать два вложенных цикла и анализировать точки связей и их параметры – это за нас сделает RDS.
Начнем изменять модель все того же блока с картинкой, по которой может щелкать пользователь – эту модель мы рассматриваем, начиная с §3.6.11. Прежде всего, добавим в блок две новых переменных: целый вход «x», к которому будет подключаться связь от поля ввода, и внутреннюю логическую переменную «NoCall», которая будет использоваться как флаг блокировки лишних вызовов «Common.ControlValueChanged», предохраняющий от бесконечной рекурсии этих вызовов. Новая структура переменных нашего блока будет выглядеть так:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 0 |
| Ready | Сигнал | Выход | 0 | |
| y | int | Выход | 0 | |
| x | int | Вход | ✓ | 0 |
| NoCall | Логический | Внутренняя | 0 |
Теперь добавим в модель функцию «Common.ControlValueChanged». Выберем на левой панели редактора вкладку «» – после последнего изменения модели, сделанного в §3.6.13.2, в списке на этой панели должна быть единственная функция «UserManual.PictureClick.Cmd» (см. рис. 471). Нажмем на вкладке кнопку «» и, в открывшемся окне, установим флажок «» и выберем в выпадающем списке справа от него «изменение присоединенного поля» (рис. 475). Поскольку мы добавляем стандартную функцию, поля «» и «» будут заблокированы – для стандартной функции их изменить нельзя. Можно только изменить имя объекта, с помощью которого мы будем вызывать функцию в программе. Сделаем это: для краткости переименуем объект в «rdsfuncCVC».
Рис. 475. Добавление в модель блока стандартной функции «Common.ControlValueChanged»
Вызывать «Common.ControlValueChanged» у присоединенных блоков нам предстоит в нескольких местах модели, поэтому этот вызов мы оформим в виде отдельной функции. Причем, поскольку нам нужен доступ к флагу NoCall, который будет блокировать лишние вызовы, нам нужно сделать эту функцию членом класса блока – только функции-члены класса блока имеют доступ к статическим переменным этого блока. Выберем на левой панели редактора вкладку «», раскроем на ней раздел «», а затем дважды щелкнем на его пункте «». На открывшейся справа пустой вкладке введем следующий текст:
// Вызов функции у всех присоединенных блоков
void InformNeighbours(void)
{ if(NoCall) // Вызов запрещен
return;
// Принудительно активируем выходные связи
rdsActivateOutputConnections(NULL,TRUE);
NoCall=1; // Взводим флаг блокировки
// Перебираем все соединенные блоки
rdsEnumConnectedBlocks(
NULL,
RDS_BEN_OUTPUTS | RDS_BEN_TRACELINKS,
ControlValChanged_Callback, // Функция обратного вызова
NULL);
NoCall=0; // Сбрасываем флаг блокировки
};
Здесь мы описываем функцию с именем InformNeighbours. Поскольку мы описали ее внутри класса блока, она станет членом этого класса. В этой функции мы, прежде всего, проверяем, не взведен ли флаг NoCall. Если он взведен, значит, этот блок уже вызвал «Common.ControlValueChanged» у своих соседей, и повторять этот вызов не нужно – мы немедленно завершаем работу функции (так мы прерываем рекурсию вызовов в кольце блоков). В противном случае мы принудительно передаем по связям данные всех выходов нашего блока (в данном случае, у нас единственный выход y) при помощи функции rdsActivateOutputConnections. Эта функция принимает два параметра: идентификатор блока, выходы которого нужно передать по связям (мы передаем NULL, что означает, что передаются выходы того блока, из модели которого вызвана функция), и логическое значение, разрешающее использование обычной логики передачи данных RDS (мы передаем TRUE, то есть разрешаем использование логики – нам нужна самая обычная передача данных по связям, аналогичная автоматически работающей в режиме расчета).
После принудительной передачи данных выхода на входы соединенных блоков мы взводим флаг NoCall (теперь повторный вызов InformNeighbours для данного блока будет временно запрещен) и вызываем функцию rdsEnumConnectedBlocks чтобы перебрать все блоки, соединенные связями с данным. Эта функция принимает четыре параметра:
- идентификатор блока, соседи по связям которого перебираются (мы передаем NULL – будут перебраны соседи того блока, из модели которого вызвана функция);
- флаги, управляющие перебором блоков (мы передаем объединенные битовым ИЛИ RDS_BEN_OUTPUTS и RDS_BEN_TRACELINKS – будут перебираться только блоки, соединенные с выходом данного, и связи будут прослеживаться внутрь и наружу подсистем);
- указатель на функцию обратного вызова (обычную функцию языка C, не «функцию блока» RDS), которая будет вызвана для каждого найденного блока (мы передаем ControlValChanged_Callback, нам еще предстоит написать функцию с таким именем);
- указатель на дополнительные данные, передаваемые в функцию обратного вызова (мы передаем NULL – у нас нет таких данных).
После того, как все соседи блока перебраны, мы сбрасываем флаг NoCall (теперь уведомление соседей блока об изменении его выхода снова разрешено). На этом работа InformNeighbours завершается.
Хотя мы создали функцию InformNeighbours для того, чтобы информировать блоки, соединенные связями с данным, об изменении его выхода при помощи вызова «Common.ControlValueChanged», можно заметить, что сам этот вызов внутри созданной функции отсутствует. Мы должны вставить его в функцию обратного вызова с именем ControlValChanged_Callback: функция InformNeighbours через сервисную функцию rdsEnumConnectedBlocks будет вызывать эту функцию обратного вызова для каждого блока, соединенного с выходом данного, а функция обратного вызова уже должна информировать конкретный переданный ей блок об изменении значения на его входе.
Функция обратного вызова, используемая в rdsEnumConnectedBlocks, должна иметь следующий вид:
BOOL RDSCALL имя_функции( RDS_PPOINTDESCRIPTION nearpoint, // Точка связи данного блока RDS_PPOINTDESCRIPTION farpoint, // Точка связи "соседа" LPVOID ptr); // Дополнительный параметр
В первом параметре этой функции (nearpoint) передается указатель на структуру RDS_POINTDESCRIPTION, описывающую точку связи, соединенную с выходом данного блока. Во втором параметре (farpoint) передается указатель на такую же структуру, описывающую точку, соединенную со входом другого блока. Анализируя поля этих структур, можно узнать, какая именно переменная данного блока соединена с другим блоком, имя соединенной переменной в другом блоке и идентификатор этого другого блока. Нам нужен будет только идентификатор блока из структуры farpoint – у него мы будем вызывать «Common.ControlValueChanged». В третьем параметре (ptr) передается указатель на дополнительные данные из последнего параметра rdsEnumConnectedBlocks – мы не передаем никаких дополнительных данных, поэтому этот параметр нас не волнует. Функция должна вернуть TRUE, если перебор блоков нужно продолжить, и FALSE, если его нужно прервать – мы будем перебирать все блоки и всегда возвращать TRUE.
Теперь нам нужно написать функцию такого вида с именем ControlValChanged_Callback, но прежде нужно разобраться с одной проблемой, возникающей при этом. По правилам языка C любой идентификатор, будь то переменная или функция, должен быть описан до места своего использования. Наша функция ControlValChanged_Callback используется в параметре rdsEnumConnectedBlocks внутри функции InformNeighbours, которая вставлена в описания внутри класса блока. Функция обратного вызова не может быть членом класса блока (она должна быть обычной функцией C), поэтому вставить ее в те же описания в классе перед InformNeighbours мы не можем. Таким образом, описать функцию обратного вызова мы должны до класса блока, а единственные доступные пользователю описания, которые можно вставить до этого класса – это глобальные описания. Однако, если мы запишем ControlValChanged_Callback в глобальных описаниях, она окажется перед тем местом в программе, в котором описываются объекты для вызова функций (они описываются там же, где и класс блока), поэтому мы не сможем использовать в ней объект rdsfuncCVC, необходимый для вызова «Common.ControlValueChanged».
Решить проблему просто: в глобальных описаниях мы запишем только прототип функции ControlValChanged_Callback, а ее тело, в котором будет использоваться объект rdsfuncCVC, разместим в описаниях после класса блока. Откроем раздел глобальных описаний (вкладка «» на левой панели редактора модели – раскрыть раздел «» – дважды щелкнуть на пункте «») и добавим туда к уже имеющейся там команде включения файла «pictureclick.h» прототип функции (изменения выделены цветом):
#include "pictureclick.h" // Прототип функции обратного вызова BOOL RDSCALL ControlValChanged_Callback( RDS_PPOINTDESCRIPTION,RDS_PPOINTDESCRIPTION,LPVOID);
Теперь добавим тело этой функции в описания после класса блока. На вкладке «» в разделе «» дважды щелкнем на пункте «» и на открывшейся вкладке введем текст:
// Функция обратного вызова для "Common.ControlValueChanged" BOOL RDSCALL ControlValChanged_Callback( RDS_PPOINTDESCRIPTION /*nearpoint*/, RDS_PPOINTDESCRIPTION farpoint, LPVOID /*ptr*/) { // Вызов функции "Common.ControlValueChanged" у блока на другом // конце связи rdsfuncCVC.Call(farpoint->Block); // Возвращаем TRUE – не останавливаем перебор блоков return TRUE; }
В параметре farpoint этой функции будет передан указатель на структуру, описывающую точку связи, соединенную с найденным блоком на другом конце этой связи от нашего. В поле Block этой структуры записан идентификатор этого блока – он нам и нужен для вызова. Функция «Common.ControlValueChanged» не имеет параметров, поэтому функция-член Call объекта rdsfuncCVC, созданного для вызова функции, будет иметь единственный параметр – идентификатор вызываемого блока, то есть farpoint->Block. Фактически, наша функция обратного вызова состоит только из вызова «Common.ControlValueChanged» через ее объект rdsfuncCVC для блока farpoint->Block. После этого вызова мы возвращаем TRUE, чтобы перебор блоков, соединенных связями с данным, продолжался.
Теперь нам нужно добавить вызов функции InformNeighbours при каждом изменении выхода блока. Сначала добавим его в уже имеющуюся реакцию на нажатие кнопки мыши (вкладка «» на левой панели редактора модели – раскрыть раздел «» – дважды щелкнуть на пункте «»). Добавленный текст выделен цветом:
if(MouseData->Button==RDS_MLEFTBUTTON) // Нажата левая { switch(rdsGetMouseObjectId(MouseData)) // Идентификатор { case 1: y--; break; // Красный квадрат case 2: y++; break; // Зеленый квадрат case 3: y=0; break; // Белый квадрат } // Взводим сигнал готовности для передачи выхода по связям Ready=1; // Информируем соседние блоки InformNeighbours(); } else // Нажата не левая Result=RDS_BFR_SHOWMENU; // Разрешаем контекстное меню
Теперь при щелчках на картинке блока в режиме моделирования будет вызываться функция InformNeighbours, принудительно передающая выход блока на входы блоков, соединенных с ним, и сообщающая им об этом вызовом «Common.ControlValueChanged».
В реакцию на вызов у блока функции «UserManual.PictureClick.Cmd», введенную в модель в конце §3.6.13.2, тоже вставим вызов InformNeighbours. На вкладке «» раскроем раздел «» и дважды щелкнем на его пункте «UserManual.PictureClick.Cmd». Изменим текст на открывшейся справа вкладке следующим образом (добавленные строки выделены цветом):
if(Param==NULL || Param->servSize<sizeof(TPictureClickFuncParam))
return; // Нет параметра или недостаточный размер структуры
// Параметр в порядке
switch(Param->Command)
{ case 1: y--; break;
case 2: y++; break;
case 3: y=0; break;
}
// Взводим сигнал готовности для передачи выхода по связям
Ready=1;
// Информируем соседние блоки
InformNeighbours();
Теперь и при внешних командах, отданных блоку через вызов «UserManual.PictureClick.Cmd», он будет принудительно передавать данные выхода в соседние блоки.
Добавим в модель реакцию на такт расчета (вкладка «» – раскрыть раздел «» – дважды щелкнуть на пункте «») – раньше, до появления у блока входа, эта реакция не была нужна:
if(y==x) // Выход равен входу – ничего не делаем
{ Ready=0;
return;
}
// Копируем вход в выход и уведомляем соседей
y=x;
InformNeighbours();
Здесь, если поступившее на вход x значение не отличается от y, мы сбрасываем сигнал готовности блока Ready, чтобы данные его выхода не передавались по связям, и завершаем модель. Это экономит процессорное время – на вход блока могут часто поступать одинаковые значения (например, если блок, передающий их, по каким-то причинам срабатывает каждый такт), а передавать их на выход повторно нет никакой необходимости. Если же выход отличается от входа, мы присваиваем выходу новое значение и вызываем InformNeighbours. Может возникнуть вопрос: зачем вызывать InformNeighbours для принудительной передачи данных соседним блокам, если в режиме расчета данные с выхода блока и так передаются по связям? Конечно, можно и не делать этого, но, поскольку мы предполагаем, что наш блок будет частью кольца соединенных блоков, лучше передать данные его выхода как можно быстрее – это позволит убрать колебания, которые могут возникнуть при подключении по кольцу. Представим себе, что на выходе нашего блока и на выходе соединенного с ним поля ввода почему-то оказались разные значения, и у обоих блоков взведен сигнал готовности. В этом случае в каждом такте расчета поле ввода будет передавать свое значение нашему блоку, а наш блок – другое значение полю ввода: блоки будут обмениваться значениями выходов и взводить сигнал готовности, и эти колебания будут продолжаться все время расчета Принудительная передача данных по связям «выравнивает» значения всех выходов: на момент конца такта, когда происходит обычная передача, значения на выходах блоков уже будут одинаковыми, а сигналы готовности – сброшенными (их сбрасывает rdsActivateOutputConnections после передачи).
Теперь нам нужно ввести в наш блок реакцию на вызов «Common.ControlValueChanged», чтобы он, как и стандартные блоки, мог получать значение по связи от другого блока (например, от поля ввода) вне режима расчета. На вкладке «» раскроем раздел «», дважды щелкнем на его пункте «Common.ControlValueChanged», и введем на открывшейся вкладке следующий текст:
if(y==x) // Выход равен входу
return;
if(NoCall) // Вызов блокирван
return;
y=x; // Копируем с входа
Ready=1; // Взводим готовность
// Информируем соседей
InformNeighbours();
Здесь мы проверяем, поступило ли на вход действительно новое значение и не заблокирован ли временно вызов функций. В обоих случаях мы немедленно завершаем реакцию – если вход не отличается от выхода, или если данный блок уже находится в цепочке последовательных вызовов функции в кольце блоков, модель не выполняет никаких действий. В противном случае мы копируем значение входа на выход, взводим сигнал готовности, разрешая тем самым передачу данных выхода по связям, и принудительно передаем данные связанным блокам, вызывая InformNeighbours.
Рис. 476. Список событий и
описаний блока
Для добавления в наш блок поддержки функции «Common.ControlValueChanged» нам потребовалось ввести в него достаточно много изменений и добавить в него несколько новых реакций и описаний (полный список фрагментов программы блока после всех изменений изображен на рис. 476). Зато теперь, если собрать схему, изображенную на рис. 474, то в режиме моделирования щелчки на нашем (цветном) блоке будут отражаться на значении поля ввода, а изменения, вносимые в поле, будут отражаться на выходе нашего блока (на индикаторе, подключенном к нему). Это стало возможным, поскольку все блоки на рисунке, включая индикатор, поддерживают функцию «Common.ControlValueChanged» и могут передавать и принимать данные с ее помощью, минуя стандартные механизмы связей RDS.
В нашем примере у блока был единственный выход. Если у блока несколько выходов, и мы хотим добавить в него столько же входов и ввести поддержку «Common.ControlValueChanged», следует обязательно каким-то образом узнавать, из-за изменения какого именно входа вызвана функция. Если не делать этого, можно потерять измененное значение одного или нескольких входов – причины и механизм этого явления подробно рассматриваются в §2.13.2 руководства программиста на примере модели блока, аналогичного двухкоординатной рукоятке из §3.6.11. Здесь мы не будем подробно рассматривать способы организации работы интерфейсных блоков с несколькими входами – скажем только, что проще всего в таких случаях привязать к каждому входу управляющий сигнал, автоматически взводящийся при срабатывании связи, подключенной к этому входу (см. §3.6.2.7), и игнорировать значения входов, сигналы которых не взведены.