Текущий раздел:

Описания RDS

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

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

§2.14. Программное управление расчетом

§2.14.3. Сохранение и загрузка состояния блоков

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

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

### §2.14. Программное управление расчетом

#### §2.14.3. Сохранение и загрузка состояния блоков

Описывается механизм возврата блоков в запомненное состояние. Рассматривается пример, в котором запоминание состояния подсистемы и возврат к этому состоянию производятся по команде пользователя.

Рассмотренная в §2.14.2 сервисная функция rdsResetSystemState позволяет вернуть всю схему или отдельную подсистему в исходное состояние, то есть в состояние, предшествовавшее первому запуску расчета. В RDS есть еще один механизм, позволяющий «вернуть в прошлое» какой-либо блок или подсистему: вызовом сервисной функции rdsSaveSystemState можно сохранить в памяти текущее состояние одного или нескольких блоков, а затем, вызвав rdsLoadSystemState, вернуть эти блоки в запомненное состояние. Этот механизм используется значительно реже, чем программный сброс, однако, в некоторых случаях он тоже может быть полезен. В системе может быть запомнено произвольное число состояний блоков, каждое из которых получает уникальный целый идентификатор.

Функция rdsSaveSystemState принимает следующие параметры:

      int RDSCALL rdsSaveSystemState(
        RDS_BHANDLE Block,   // Блок или подсистема
        int num,             // Идентификатор сохраняемого состояния
        BOOL Recursive,      // Сохранять ли вложенные блоки
        RDS_BBhpB CallBack); // Указатель на функцию проверки
                             // необходимости сохранения или NULL

В параметре Block передается идентификатор блока или подсистемы, чье состояние необходимо сохранить. Если в этом параметре передан идентификатор подсистемы, а в параметре Recursive – значение TRUE, то будет сохранено не только состояние самой подсистемы, но и состояния всех ее внутренних блоков и подсистем. В параметре num передается идентификатор запомненного состояния, которое нужно заменить на сохраняемое, или −1, если необходимо запомнить новое состояние. Функция возвращает идентификатор запомненного состояния: если в параметре num был передан идентификатор существующего запомненного состояния, будет возвращено значение num, если же в num было передано значение −1 или любое другое целое число, не соответствующее ни одному из существующих идентификаторов, будет создан и возвращен новый уникальный идентификатор запомненного состояния.

В последнем параметре функции (CallBack) может быть передан указатель на функцию вида

      BOOL func(RDS_BHANDLE block,BOOL *pContinue);

Если этот параметр не равен NULL, указанная функция будет вызываться для каждого блока, состояние которого должно быть сохранено. Состояние блока block будет сохранено только в том случае, если функция вернет TRUE. При этом, если функция запишет по указателю, переданному в параметре pContinue, значение FALSE, перебор блоков и сохранение их состояний будет остановлено. Таким образом при сохранении состояния подсистем с вложенными блоками можно гибко управлять тем, какие именно блоки должны сохраниться.

Для загрузки ранее запомненного состояния служит функция rdsLoadSystemState:

      BOOL RDSCALL rdsLoadSystemState(
        int num,             // Идентификатор загружаемого состояния
        RDS_BBhpB CallBack); // Указатель на функцию проверки
                             // необходимости загрузки или NULL

В параметре num передается уникальный идентификатор загружаемого состояния. Идентификатор подсистемы или блока, состояние которого будет загружено, не передается: загружаются состояния тех блоков, которые были запомнены с идентификатором num. Если после сохранения часть блоков была удалена, ничего страшного не произойдет – будут загружены состояния только тех блоков, которые остались в схеме. В параметре CallBack, если необходимо, передается указатель на функцию обратного вызова того же формата, который использовалась в rdsSaveSystemState, только теперь она определяет необходимость загрузки состояния конкретного блока.

При сохранении состояния блока запоминаются значения всех его статических переменных. Затем, поскольку RDS не может работать с личной областью данных блока, а в ней могут находиться какие-то параметры, связанные с состоянием этого блока, модель блока вызывается в режиме RDS_BFM_SAVESTATE. Внутри этого вызова модель может сохранить необходимые параметры при помощи уже знакомой нам функции записи в двоичном виде rdsWriteBlockData. Сохранение состояния блока в текстовом формате не предусмотрено поскольку проблемы с совместимостью форматов здесь вряд ли возникнут: запомненное состояние хранится только в памяти и теряется при выгрузке схемы или при смене модели блока. При загрузке состояния блока значения статических переменных восстанавливаются, а затем модель блока вызывается в режиме RDS_BFM_LOADSTATE для загрузки параметров личной области данных, если это необходимо, при помощи функции rdsReadBlockData. Таким образом, следует помнить, что блоки, текущие параметры которых хранятся не только в статических переменных, должны поддерживать загрузку и сохранение этих параметров в режимах RDS_BFM_SAVESTATE/RDS_BFM_LOADSTATE.

В отличие от многих других вспомогательных объектов, создаваемых блоками, сохраненное состояние никак не привязано к блоку, вызвавшему функцию rdsSaveSystemState, и не будет удалено из памяти при его удалении. При выгрузке всей схемы оно, разумеется, будет удалено, чтобы не вызывать утечек памяти, но до этого момента оно будет оставаться в памяти и может быть в любой момент загружено любым блоком схемы, которому известен целый идентификатор этого состояния (сохранивший блок может передать его другому блоку по связи или через вызов функции блока). Если сохраненное состояние больше не нужно, оно может быть принудительно удалено из памяти сервисной функцией rdsDeleteSystemState, в которую передается идентификатор удаляемого состояния.

В качестве примера рассмотрим простой блок, который будет сохранять и загружать состояние своей родительской подсистемы по щелчку мыши. Обработку щелчков мыши мы возьмем из модели блока, увеличивающего и уменьшающего значение выхода (см. §2.12.1) – будем сохранять состояние при щелчке левой кнопкой мыши по верхней части изображения блока и загружать его при щелчке по нижней части. Кроме того, дадим пользователю возможность нарисовать кнопки сохранения и загрузки в редакторе картинки блока: если у блока есть картинка, будем определять идентификатор ее элемента под курсором мыши, для положительных идентификаторов будем сохранять состояния, для отрицательных – загружать.

Нам потребуется где-то хранить целый идентификатор сохраненного состояния, чтобы его можно было потом загрузить. Статические переменные блока для этого не подходят – они изменяются при загрузке состояния родительской подсистемы, и мы потеряем этот идентификатор. Будем хранить его в личной обрасти данных блока, которая будет представлять собой единственное число типа int. Статические переменные нашему блоку не нужны (разумеется, у него будут два обязательных сигнала «Start» и «Ready», но мы не будем ими пользоваться). Модель блока будет достаточно простой:

      // Запись и загрузка состояния
      extern "C" __declspec(dllexport)
          int RDSCALL SaveLoadState(int CallMode,
                              RDS_PBLOCKDATA BlockData,
                              LPVOID ExtParam)
      { RDS_PMOUSEDATA mouse;
        // Указатель на личную обрасть данных блока (int)
        int *pSaveId=(int*)(BlockData->BlockData);

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создание личной области данных (одно число int)
              BlockData->BlockData=pSaveId=new int;
              // Исходное значение идентификатора: -1 (то есть нет)
              *pSaveId=-1;
              break;

            // Очистка
            case RDS_BFM_CLEANUP:
              // Удаление сохраненного состояния (если есть)
              rdsDeleteSystemState(*pSaveId);
              // Уничтожение личной области
              delete pSaveId;
              break;

            // Нажатие кнопки мыши
            case RDS_BFM_MOUSEDOWN:
              mouse=(RDS_PMOUSEDATA)ExtParam;
              if(mouse->Button==RDS_MLEFTBUTTON) // Левая кнопка
                { RDS_BLOCKDESCRIPTION descr;
                  descr.servSize=sizeof(descr);
                  BOOL save=FALSE,load=FALSE;
                  // Есть ли у блока картинка?
                  rdsGetBlockDescription(BlockData->Block,&descr);
                  if(descr.Flags & RDS_BDF_HASPICTURE)
                    { // Картинка есть – смотрим идентификатор элемента под курсором
                      int pic_id=rdsGetMouseObjectId(mouse);
                      if(pic_id>0)
                        save=TRUE;
                      else if(pic_id<0)
                        load=TRUE;
                    }
                  else if(mouse->y<mouse->Top+mouse->Height/2)
                    save=TRUE; // Картинки нет, верх блока
                  else
                    load=TRUE; // Картинки нет, низ блока
                  if(save) // Сохраняем состояние
                    *pSaveId=rdsSaveSystemState(BlockData->Parent,
                                 *pSaveId,TRUE,NULL);
                  if(load) // Загружаем состояние
                    { rdsLoadSystemState(*pSaveId,NULL);
                      // Необходимо обновить окно подсистемы
                      rdsRefreshBlockWindows(BlockData->Parent,TRUE);
                    }
                }
              break;
          }
        return RDS_BFR_DONE;
      }
      //=========================================

При инициализации модели мы создаем личную область данных блока размером в одно целое число оператором «new int», а затем записываем в нее число −1, которое означает, что у нас пока нет сохраненного состояния. При очистке модели мы удаляем сохраненное состояние, идентификатор которого содержится в личной области и на которое указывает переменная pSaveId, при помощи функции rdsDeleteSystemState. Если мы ни разу не сохраняли состояние, и там осталось значение −1, ничего страшного не произойдет: если число, переданное в функцию rdsDeleteSystemState, не соответствует никакому идентификатору сохраненного состояния, функция не выполнит никаких действий. Затем оператором delete удаляется сама личная область данных.

Загрузка и сохранение состояний выполняется в реакции на нажатие кнопки мыши RDS_BFM_MOUSEDOWN. Прежде всего, нажатая кнопка сравнивается с константой RDS_MLEFTBUTTON, обозначающей левую кнопку мыши. Если они совпали, мы проверяем, есть ли у блока картинка. Для блоков с картинкой считывается идентификатор элемента под курсором мыши и, в зависимости от его идентификатора, значение TRUE присваивается вспомогательной переменной load или save. Для блоков без картинки значение TRUE присваивается переменной save при попадании в верхнюю часть блока, и переменной load – при попадании в нижнюю. Таким образом, после всех этих действий, переменная save будет истинной, если мы должны сохранить состояние подсистемы, а переменная load – если мы должны загрузить его. Если значение save истинно, вызывается функция rdsSaveSystemState для сохранения состояния родительской подсистемы этого блока (BlockData->Parent) со всеми вложенными блоками (в параметре Recursive передается TRUE). В качестве идентификатора сохраняемого состояния передается значение, хранящееся в личной области данных блока (*pSaveId), туда же записывается результат возврата функции. Таким образом, при самом первом сохранении состояния, когда в личной области данных находится значение −1, функция добавит состояние к набору уже сохраненных (если они есть) и создаст для него новый идентификатор, который запишется в личную область блока. При всех последующих сохранениях функция будет заменять сохраненное состояние с этим идентификатором на новое.

Если истинно значение load, вызывается rdsLoadSystemState для загрузки состояния с идентификатором *pSaveId. Попытка загрузить состояние, не записывая его (при этом в личной области данных блока будет находиться −1) не приведет к ошибкам, функция просто не выполнит никаких действий. После загрузки состояния вызывается функция rdsRefreshBlockWindows для принудительного обновления окна подсистемы, поскольку параметры ее блоков изменились, и это могло отразиться на их внешнем виде.

Для проверки работы созданной модели следует создать новый блок и подключить к нему эту модель, разрешив ей реагировать на действия пользователя мышью. Внешним видом блока можно сделать прямоугольник со словами «записать» и «загрузить», расположенными друг под другом. Рядом с ним следует собрать схему, подобную изображенной на рис. 104. В этой схеме генератор синусоидальных колебаний подключен к двум графикам: в верхней части схемы расположен созданный нами ранее блок-график, в нижней – стандартный график из библиотеки блоков RDS. Если запустить расчет, щелкнуть по верхней части созданного блока, а затем, подождав некоторое время, щелкнуть по его нижней части, можно будет увидеть, как блоки в подсистеме вернутся в состояние на момент первого щелчка.

Рис. 104. Схема для проверки работы блока сохранения и загрузки состояния подсистемы

На самом деле, в это состояние вернутся все блоки, кроме верхнего, созданного нами, графика – он просто перестанет рисоваться. Дело в том, что все текущие параметры этого блока, включая массивы отсчетов, хранятся в личной области данных. Когда мы писали модель этого блока, мы не включили в нее поддержку режимов RDS_BFM_SAVESTATE и RDS_BFM_LOADSTATE, поэтому блок не реагирует на запись и загрузку своего состояния. Сейчас мы исправим эту ошибку.

Прежде всего, добавим в класс личной области данных блока-графика описания двух новый функций для записи и загрузки состояния – мы будем вызывать их из функции модели блока:

      //=========================================
      // Простой график – личная область данных
      //=========================================
      class TSimplePlotData
      {
          // …

          void Draw(RDS_PDRAWDATA DrawData); // Функция рисования

          void SaveState(void);  // Функция записи состояния
          void LoadState(void);  // Функция загрузки состояния
          TSimplePlotData(void); // Конструктор класса
          ~TSimplePlotData();    // Деструктор класса
      };
      //=========================================

Эти функции будут устроены очень просто: при сохранении состояния мы должны записать все изменяющиеся параметры блока, включая массивы отсчетов, при помощи функции rdsWriteBlockData, а при загрузке – загрузить их при помощи rdsReadBlockData (при этом нужно будет заново отвести массивы отсчетов перед их непосредственной загрузкой, поскольку их размер мог измениться). Функция записи будет выглядеть так:

      // Запись состояния блока
      void TSimplePlotData::SaveState(void)
      {
        // Макрос для записи одной переменной
        #define WRITEBLOCKDATAVAR(v) rdsWriteBlockData(&v,sizeof(v))

        WRITEBLOCKDATAVAR(TimeStep);
        WRITEBLOCKDATAVAR(Xmin);
        WRITEBLOCKDATAVAR(Xmax);
        WRITEBLOCKDATAVAR(XGridStep);

        // Запись массивов отсчетов
        rdsWriteBlockData(Times,Count*sizeof(double));
        rdsWriteBlockData(Values,Count*sizeof(double));

        WRITEBLOCKDATAVAR(NextIndex);
        WRITEBLOCKDATAVAR(NextTime);

        // Отмена макроса
        #undef WRITEBLOCKDATAVAR
      }
      //=========================================

В этой функции, чтобы для каждой переменной A не писать конструкцию вида

      rdsWriteBlockData(&A,sizeof(A));

мы вводим макрос WRITEBLOCKDATAVAR. Сначала мы записываем параметры горизонтальной оси графика TimeStep, Xmin и Xmax, которые однозначно определяют размер массивов отсчетов Count. Затем, на всякий случай, сохраняется шаг сетки горизонтальной оси XGridStep – вдруг пользователь изменил его после записи состояния. Параметры вертикальной оси мы не записываем, хотя можно было бы записать и их: будем считать, что изменения, внесенные пользователем в настройки этой оси, отменять при загрузке состояния не нужно. После этого мы записываем оба массива отсчетов Times и Values, с которыми работает график, и переменные NextIndex и NextTime, определяющие текущую точку записи в эти массивы. На этом работа функции завершается – мы записали все важные или изменяющиеся со временем переменные из личной области данных блока.

Функция загрузки состояния будет очень похожа на функцию записи:

      // Загрузка состояния блока
      void TSimplePlotData::LoadState(void)
      {
        // Макрос для загрузки одной переменной
        #define READBLOCKDATAVAR(v) rdsReadBlockData(&v,sizeof(v))

        READBLOCKDATAVAR(TimeStep);
        READBLOCKDATAVAR(Xmin);
        READBLOCKDATAVAR(Xmax);
        READBLOCKDATAVAR(XGridStep);

        // Отведение массивов перед загрузкой
        AllocateArrays();
        rdsReadBlockData(Times,Count*sizeof(double));
        rdsReadBlockData(Values,Count*sizeof(double));

        READBLOCKDATAVAR(NextIndex);
        READBLOCKDATAVAR(NextTime);

        // Отмена макроса
        #undef READBLOCKDATAVAR

        // Сброс параметров оптимизации рисования
        OldZoom=-1.0;
        LastDrawnIndex=-1;
      }
      //=========================================

В этой функции мы тоже вводим макрос, упрощающий загрузку одной переменной, а затем начинаем загружать переменные в том же порядке, в котором мы их записывали. Перед загрузкой массивов отсчетов вызывается функция AllocateArrays, которая отведет эти массивы заново согласно новым загруженным параметрам горизонтальной оси графика, и вычислит значение Count. В самом конце функции необходимо сбросить переменные, которые мы используем для ускорения работы функции рисования графика (см. §2.10.2) – параметры графика изменились, и прежние значения вспомогательных переменных рисования теперь не годятся, их нужно будет вычислить заново.

В оператор switch(CallMode) внутри функции модели SimplePlot необходимо внести два новых оператора case для вызова функций загрузки и записи состояния:

            // Запись состояния блока
            case RDS_BFM_SAVESTATE:
              data->SaveState();
              break;

            // Загрузка состояния блока
            case RDS_BFM_LOADSTATE:
              data->LoadState();
              break;

Это все изменения, которые нужно внести в модель блока-графика. Теперь при загрузке состояния подсистемы в схеме на рис. 104 в запомненное состояние будут возвращаться оба графика.

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