Навигация:
<< >> Оглавление Указатель
Текст С++

Руководство программиста

Глава 2. Создание моделей блоков

§2.6. Динамические переменные

§2.6.4. Работа с несколькими динамическими переменными

Рассматриваются особенности работы модели блока с несколькими динамическими переменными одновременно. Приводится пример, в котором блок перемещается в окне подсистемы с заданной скоростью и в заданном направлении, при этом скорость и направление он берет из динамических переменных, созданных в подсистеме другим рассматриваемым в примере блоком, а время – из динамической переменной «DynTime» стандартного библиотечного блока-планировщика.

Во всех приведенных примерах (§2.6.2, §2.6.3) каждый блок работал только с одной динамической переменной. При работе с несколькими переменными одновременно необходимо запоминать указатель на структуру подписки для каждой из них, и здесь уже не обойтись без отведения дополнительной памяти под личную область данных блока.

Рассмотрим модель блока, изображение которого должно перемещаться в окне подсистемы с заданной скоростью в заданном направлении. Значения скорости и направления блок будет получать через динамические переменные, созданные в его родительской подсистеме другим блоком – блоком управления, который записывает в эти переменные значения своих входов. Для перемещения блока также необходимо значение времени, которое блок будет получать из динамической переменной «DynTime», обслуживаемой планировщиком динамического расчета из библиотеки «Common.dll» – без значения времени невозможно будет вычислить величину перемещения блока по его скорости. Можно было бы получать значение времени у операционной системы (например, с помощью функции Windows API GetTickCount), но, в данном случае, удобнее работать с блоком-планировщиком, поскольку в его параметрах можно настроить шаг изменения значения времени, а также ускорить или замедлить его, если это будет необходимо.

Для того, чтобы изображение блока перемещалось в окне подсистемы, необходимо связать его горизонтальную и вертикальную координаты с какими-нибудь статическими переменными, значения которых в режимах моделирования и расчета будут определять смещение блока от заданной в режиме редактирования позиции. Например, если блок установлен в точку (10,20), а переменные горизонтального и вертикального смещения равны 5 и 7 соответственно, в режиме редактирования блок будет по-прежнему изображаться в точке (10,20), а в режимах моделирования и расчета – в точке (15,27).

Будем считать, что нулевое значение угла направления соответствует движению блока вдоль горизонтальной оси вправо, а значение 90° – движению блока вдоль вертикальной оси вверх (увеличение угла направления будет соответствовать повороту блока против часовой стрелки). В окне подсистемы, как и в любом другом окне Windows, используется система координат с перевернутой вертикальной осью. Это означает, что увеличение вертикальной координаты соответствует перемещению блока вниз, а не вверх – нужно будет учитывать это при вычислении вертикального смещения блока.

Перемещение блока за промежуток времени Δ t

Рис. 42. Перемещение блока за
промежуток времени Δt

Если в какой-либо момент времени блок изображается в точке (x0,y0) и двигается со скоростью v в направлении α (рис. 42), его координаты через промежуток времени Δt можно вычислить следующим образом:

x1=x0+Δt v t cos α; y1=y0-Δt v t sin α

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

Начнем с создания модели блока управления, на вещественные входы «Speed» и «Angle» которого поступают значения скорости и направления движения соответственно, а он, в свою очередь, передает эти значения перемещающемуся блоку через динамические переменные подсистемы. Для простоты имена динамических переменных будут жестко заданы в моделях обоих блоков: переменная для передачи скорости будет называться «BlkMove_Speed», переменная для передачи направления – «BlkMove_Angle». Хотя жесткое задание имен переменных ограничивает функциональность модели, не позволяя поместить в одну подсистему несколько независимо управляемых перемещающихся блоков, для данного примера этот вариант вполне подходит. Можно было бы, как в предыдущем примере, хранить имена переменных в комментарии блока, однако теперь каждому блоку необходимо две переменных, и их имена либо пришлось бы извлекать из разных строк комментария, либо формировать динамически, добавляя к тексту комментария блока суффиксы «_Speed» и «_Angle». И то, и другое неоправданно усложнило бы пример, целью которого является демонстрация работы с несколькими динамическими переменными, а не операций со строками.

Блок управления будет иметь следующую структуру переменных:

Смещение Имя Тип Размер Вход/выход Пуск Начальное значение
0 Start Сигнал 1 Вход 1
1 Ready Сигнал 1 Выход 0
2 Speed double 8 Вход 0
10 Angle double 8 Вход 0

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

Модель блока управления будет в целом похожа на модель блока-передатчика из прошлого примера. Однако, блок-передатчик создавал единственную динамическую переменную, поэтому указатель на ее структуру подписки можно было запомнить в поле BlockData структуры RDS_PBLOCKDATA, предназначенном для хранения указателя на личную область данных блока. Новый же блок создает две переменных, поэтому ему необходимо хранить два указателя на структуры подписки. Проще всего будет описать структуру, содержащую два поля RDS_PDYNVARLINK для хранения этих указателей, и использовать такую структуру в качестве личной области данных, динамически отводя под нее память при инициализации и освобождая эту память при очистке данных блока.

Запишем модель блока управления вместе с описанием структуры личной области данных:

  // Структура личной области данных блока
  typedef struct
  { // Указатель на структуру подписки на скорость
     VLink;
    // Указатель на структуру подписки на направление
     ALink;
  } TestBlkMoveSetterData;

  // Модель блока управления
  extern "C" __declspec(dllexport)
    int  TestBlkMoveSetter(int CallMode,
                                   BlockData,
                                   ExtParam)
  {
  // 
  #define pStart  ((char *)(BlockData->VarTreeData))
  #define Start (*((char *)(pStart)))
  #define Ready (*((char *)(pStart+RDS_VSZ_S)))
  #define Speed (*((double *)(pStart+2*RDS_VSZ_S)))
  #define Angle (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
    // Вспомогательная переменная – указатель на личную область
    // данных блока, приведенный к правильному типу
    TestBlkMoveSetterData *privdata;

    switch(CallMode)
      { // Инициализация блока
        case :
          // Отведение памяти под личную область данных
          privdata=(TestBlkMoveSetterData*)
                    malloc(sizeof(TestBlkMoveSetterData));
          BlockData->BlockData=privdata;
          // Создание переменной для передачи скорости
          privdata->VLink=(
                            ,    // В родительской
                            "BlkMove_Speed", // Имя переменной
                            "D",             // Тип double
                            TRUE,            // Запрет удаления
                            NULL);           // Без нач.значения
          // Создание переменной для передачи направления
          privdata->ALink=(
                            ,
                            "BlkMove_Angle",
                            "D",
                            TRUE,
                            NULL);
          break;

        // Очистка данных блока
        case :
          // Приведение указателя на личную область данных к правильному типу
          privdata=(TestBlkMoveSetterData*)(BlockData->BlockData);
          // Удаление динамических переменных
          (privdata->VLink);
          (privdata->ALink);
          // Освобождение отведенной памяти
          free(privdata);
          break;

        // Проверка типа статических переменных
        case :
          if(strcmp((char*)ExtParam,"{SSDD}")==0)
            return ;
          return ;

        // Выполнение такта моделирования
        case :
          // Приведение указателя на личную область данных к правильному типу
          privdata=(TestBlkMoveSetterData*)(BlockData->BlockData);
          // Проверка существования переменной направления
          if(privdata->ALink!=NULL && privdata->ALink->Data!=NULL)
            { // Переменная существует – привести к типу double*
              double *pa=(double*)privdata->ALink->Data;
              if(*pa!=Angle) // Значение направления изменилось
                { // Записать значение в динамическую переменную
                  *pa=Angle;
                  // Уведомить всех подписчиков об изменении
                  (privdata->ALink);
                }
            }
          // Проверка существования переменной скорости
          if(privdata->VLink!=NULL && privdata->VLink->Data!=NULL)
            { // Переменная существует – привести к типу double*
              double *pv=(double*)privdata->VLink->Data;
              if(*pv!=Speed)
                { // Записать значение в динамическую переменную
                  *pv=Speed;
                  // Уведомить всех подписчиков об изменении
                  (privdata->VLink);
                }
            }
          break;
      }
    return ;
  // Отмена макроопределений
  #undef Angle
  #undef Speed
  #undef Ready
  #undef Start
  #undef pStart
  }
  //=========================================

Перед текстом модели блока описана структура TestBlkMoveSetterData, которая будет использоваться в качестве личной области данных блока. Структура содержит два поля типа – указатели на структуры подписки на динамические переменные скорости VLink и направления ALink. При вызове модели с параметром RDS_BFM_INIT под эту структуру динамически отводится память при помощи стандартной функции malloc (описанной в «stdlib.h»), в которую передается размер структуры, полученный при помощи оператора sizeof. Возвращенный функцией malloc указатель на отведенную область памяти записывается во вспомогательную переменную privdata и в поле BlockData структуры данных блока, в которой он будет храниться до тех пор, пока модель блока не освободит отведенную память.

В отличие от блока-передатчика из прошлого примера, в котором пользователь мог ввести новое имя переменной в комментарии блока, в этом примере пользователь не имеет возможности задавать имена динамических переменных. Это сильно упрощает написание модели, поскольку здесь не нужно проверять изменения текста комментария при запуске расчета и создавать новые переменные – их можно создавать один раз при инициализации блока. После того, как личная область данных создана, модель создает в родительской подсистеме динамические переменные «BlkMove_Speed» и «BlkMove_Angle» при помощи сервисной функции rdsCreateAndSubscribeDV и записывает полученные указатели на структуры подписки в поля VLink и ALink личной области данных.

При вызове модели с параметром RDS_BFM_CLEANUP модель приводит запомненный в BlockData->BlockData указатель на личную область данных к типу «TestBlkMoveSetterData*» и присваивает его вспомогательной переменной privdata, чтобы к личной области данных было удобнее обращаться. Затем при помощи сервисной функции rdsDeleteDVByLink модель удаляет обе динамические переменные и освобождает отведенную под личную область память стандартной функцией free.

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

Теперь рассмотрим модель блока, который будет перемещаться в окне подсистемы. В этом блоке должно быть по крайней мере две вещественных статических переменных («x» и «y»), чтобы было с чем связать координаты его изображения на вкладке «внешний вид» окна параметров блока (рис. 43). Для наглядности добавим еще одну вещественную переменную «a», с которой свяжем угол поворота изображения блока. В эту переменную модель блока будет записывать значение угла направления в радианах. Таким образом, если блок будет иметь векторную картинку со стрелкой, указывающей вправо (что соответствует нулевому углу направления), в режиме расчета эта стрелка будет указывать направление движения блока.

Привязка координат подвижного блока к переменным x , y и a

Рис. 43. Привязка координат подвижного блока к переменным «x», «y» и «a»

Координаты изображения блока «x» и «y» будут вычисляться по приведенным выше рекуррентным формулам для перемещения блока за интервал времени Δt (рис. 42). Из динамической переменной «DynTime» можно получить только текущее значение времени, поэтому интервал, прошедший с момента последнего вычисления координат, модель должна будет вычислять самостоятельно. Для этого придется добавить к статическим переменным блока дополнительную переменную «t0», в которой будет запоминаться предыдущее значение времени, а интервал Δt будет вычисляться как разность «DynTime» и «t0». Чтобы первый интервал времени был вычислен правильно, начальное значение переменной «t0» должно быть нулевым.

Таким образом, перемещающийся блок будет иметь следующую структуру переменных:

Смещение Имя Тип Размер Вход/выход Пуск Начальное значение
0 Start Сигнал 1 Вход 0
1 Ready Сигнал 1 Выход 0
2 x double 8 Внутренняя 0
10 y double 8 Внутренняя 0
18 a double 8 Внутренняя 0
26 t0 double 8 Внутренняя 0

Этот блок не будет реагировать на событие (все действия будут производиться в реакции на изменение динамических переменных ), поэтому в его параметрах имеет смысл выключить запуск каждый такт. В личной области данных блока необходимо запоминать три указателя на структуры подписки: для переменных «BlkMove_Speed», «BlkMove_Angle» и «DynTime». Кроме того, в структуре личной области данных будут два дополнительных вещественных поля SinA и CosA, в которых будут храниться значения синуса и косинуса угла «a». Наличие этих полей позволит вычислять синус и косинус не при каждом перемещении блока, а только при изменении направления движения, что уменьшит нагрузку на систему. В данном случае это уменьшение будет практически незаметным, но при сложных вычислениях часто имеет смысл хранить промежуточные результаты.

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

  // Структура личной области данных блока
  typedef struct
  { // Указатель на структуру подписки на скорость
     VLink;
    // Указатель на структуру подписки на направление
     ALink;
    // Указатель на структуру подписки на время
     Time;

    // Дополнительные поля для хранения значений синуса
    // и косинуса направления движения блока
    double SinA;
    double CosA;
  } TestBlkMoveObjectData;

  // Модель перемещающегося блока
  extern "C" __declspec(dllexport)
    int  TestBlkMoveObject(int CallMode,
                                   BlockData,
                                   ExtParam)
  {
  // 
  #define pStart  ((char *)(BlockData->VarTreeData))
  #define Start (*((char *)(pStart)))
  #define Ready (*((char *)(pStart+RDS_VSZ_S)))
  #define x (*((double *)(pStart+2*RDS_VSZ_S)))
  #define y (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
  #define a (*((double *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_D)))
  #define t0 (*((double *)(pStart+2*RDS_VSZ_S+3*RDS_VSZ_D)))
    // Вспомогательная переменная – указатель на личную область
    // данных блока, приведенный к правильному типу
    TestBlkMoveObjectData *privdata;
    // Вспомогательные переменные для скорости и времени
    double v,time;

    switch(CallMode)
      { // Инициализация блока
        case :
          // Отведение памяти под личную область данных
          privdata=(TestBlkMoveObjectData*)
                    malloc(sizeof(TestBlkMoveObjectData));
          BlockData->BlockData=privdata;
          // Подписка на переменную скорости
          privdata->VLink=(
                          ,    // В родительской
                          "BlkMove_Speed", // Имя переменной
                          "D",             // Тип double
                          FALSE);          // Без поиска
          // Подписка на переменную направления
          privdata->ALink=(
                          ,    // В родительской
                          "BlkMove_Angle", // Имя переменной
                          "D",             // Тип double
                          FALSE);          // Без поиска
          // Подписка на переменную времени
          privdata->Time=(
                          ,    // В родительской
                          "DynTime",       // Имя переменной
                          "D",             // Тип double
                          TRUE);           // Поиск в иерархии
          break;

        // Очистка данных блока
        case :
          // Приведение указателя на личную область данных к
          // правильному типу
          privdata=(TestBlkMoveObjectData*)(BlockData->BlockData);
          // Прекращение подписки на все динамические переменные
          (privdata->VLink);
          (privdata->ALink);
          (privdata->Time);
          // Освобождение отведенной памяти
          free(privdata);
          break;

        // Проверка типа статических переменных
        case :
          if(strcmp((char*)ExtParam,"{SSDDDD}")==0)
            return ;
          return ;

        // Реакция на изменение динамической переменной
        case :
          // Приведение указателя на личную область данных к
          // правильному типу
          privdata=(TestBlkMoveObjectData*)(BlockData->BlockData);
          // Проверка – удалось ли подписаться на все переменные
          // (если хотя бы один указатель – NULL, значит, не удалось)
          if(privdata->VLink==NULL || // Скорость
             privdata->ALink==NULL || // Направление
             privdata->Time==NULL)    // Время
            break;
          // Проверка – существуют ли все переменные
          // (если хотя бы один указатель – NULL, значит, не удалось)
          if(privdata->VLink->Data==NULL || // Скорость
             privdata->ALink->Data==NULL || // Направление
             privdata->Time->Data==NULL)    // Время
            break;
          // Если изменилась переменная направления, нужно вычислить
          // и запомнить новые значения синуса и косинуса угла
          if(ExtParam==(void*)(privdata->ALink))
            { // Изменилось направление – привести к типу double*
              double *pa=(double*)privdata->ALink->Data;
              // Значение угла в радианах
              a=(*pa)*M_PI/180.0;
              // Значения синуса и косинуса
              privdata->SinA=sin(a);
              privdata->CosA=cos(a);
            }
          // Записать во вспомогательные переменные v и time значения
          // скорости и времени
          v=*(double*)privdata->VLink->Data;
          time=*(double*)privdata->Time->Data;
          // Вычислить новое смещение изображения блока
          x+=v*(time-t0)*privdata->CosA;
          y-=v*(time-t0)*privdata->SinA;
          // Запомнить значение времени, для которого вычислены новые координаты
          t0=time;
          break;
      }
    return ;
  // Отмена макроопределений
  #undef t0
  #undef a
  #undef y
  #undef x
  #undef Ready
  #undef Start
  #undef pStart
  }
  //=========================================

Структура личной области данных блока TestBlkMoveObjectData содержит три указателя на структуры подписки (VLink для динамической переменной скорости, ALink для направления и Time для времени) и два вещественных поля для хранения значений синуса и косинуса направления. Как и в модели блока управления, при вызове этой модели с параметром под эту структуру отводится память функцией malloc, и полученный указатель записывается во вспомогательную переменную privdata и в поле BlockData->BlockData. Затем модель подписывается на три динамических переменные и записывает указатели на созданные структуры подписки в соответствующие поля личной области данных. Следует обратить внимание, что переменные «BlkMove_Speed» и «BlkMove_Angle» ищутся только в родительской подсистеме (параметр Search функции rdsSubscribeToDynamicVar равен FALSE), а переменная «DynTime» – во всех подсистемах вверх по иерархии, начиная с родительской. Это позволяет данному блоку получить доступ к переменной «DynTime», созданной ближайшим к нему планировщиком в цепочке родительских подсистем. При вызове модели с параметром подписка на все динамические переменные прекращается, и память, занятая личной областью данных блока, освобождается функцией free.

Как только блок управления изменит направление или скорость, или блок-планировщик изменит значение времени, функция модели данного блока будет вызвана с параметром RDS_BFM_DYNVARCHANGE, при этом в параметре ExtParam в нее будет передан указатель на структуру подписки изменившейся переменной. Сравнив ExtParam с полями VLink, ALink и Time личной области данных, можно понять, какая из динамических переменных изменилась. Но сначала модель должна проверить, есть ли у нее доступ к динамическим переменным. Если хотя бы один указатель на структуру подписки, хранящийся в личной области данных, равен NULL, значит, модели не удалось подписаться на одну из переменных. В данном случае это маловероятно, поскольку выбранные имена переменных отвечают требованиям RDS, и функция может вернуть значение NULL, сигнализирующее о невозможности подписки, только при каком-либо системном сбое (например, при нехватке памяти). Однако, проверить успешность подписки все-таки следует. Если один из указателей будет равен NULL, работа модели будет немедленно завершена – для работы этого блока необходимы все три переменные.

Если модели удалось подписаться на все переменные, это еще не значит, что у нее есть доступ к ним. Успешность подписки говорит только о том, что RDS теперь следит за тем, чтобы в поле Data структуры подписки находился указатель на область данных запрошенной переменной. Если такой переменной не существует, это поле будет иметь значение NULL. Поскольку блоку нужны все три динамические переменные, необходимо проверить все три указателя: VLink->Data, ALink->Data и Time->Data. Если хотя бы один из них равен NULL, работа модели будет немедленно завершена.

После того, как модель убедилась, что в данный момент у нее есть доступ ко всем трем динамическим переменным, она сравнивает указатель на структуру подписки изменившейся переменной, переданный в ExtParam, с указателем переменной направления ALink, предварительно приведенным к типу универсального указателя (void*). Если два этих указателя равны, значит, изменилось направление движения блока. В этом случае переданное через динамическую переменную значение направления в градусах переводится в радианы и присваивается статической переменной a, чтобы векторная картинка блока, угол поворота которой связан с этой переменной, повернулась в направлении движения блока. Затем вычисляются косинус и синус a и запоминаются в полях SinA и CosA личной области данных блока для дальнейшего вычисления перемещения.

Затем, уже независимо от того, какая из трех переменных изменилась, вычисляются новые значения горизонтального x и вертикального y смещений изображения блока по приведенным ранее формулам. Интервал времени Δt, прошедший с момента последнего расчета, вычисляется как разность значения времени из динамической переменной «DynTime» (присвоенного вспомогательной переменной time) и запомненного значения времени t0. После этого t0 присваивается текущее значение времени, чтобы при следующем изменении переменных использовать его как значение времени предыдущего расчета.

Управление подвижным блоком

Рис. 44. Управление подвижным блоком

Чтобы убедиться в работоспособности созданной модели, необходимо поместить в одну подсистему блок управления, перемещающийся блок и планировщик (планировщик может также находиться выше по иерархии, например, в родительской подсистеме данной подсистемы), задать для перемещающегося блока какую-нибудь векторную картинку и подключить поля ввода ко входам «Speed» и «Angle» блока управления (рис. 44). После запуска расчета и ввода каких-либо значений в поля ввода блок должен начать двигаться в заданном направлении с указанной (в точках экрана в секунду) скоростью.


<< >> Оглавление Указатель