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

Описания RDS

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

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

§2.10. Программное рисование внешнего вида блока

§2.10.2. Оптимизация рисования

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

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

### §2.10. Программное рисование внешнего вида блока

#### §2.10.2. Оптимизация рисования

Рассматриваются различные способы ускорения программного рисования внешнего вида блоков. Два из этих способов используются для улучшения работы графика из примера, описанного в §2.10.1.

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

Самый простой способ избежать ненужных обновлений окна – это информирование RDS о том, что в блоке ничего не изменилось. Каждый раз перед вызовом функции модели блока RDS взводит битовый флаг RDS_NEEDSDLLREDRAW в поле Flags структуры RDS_BLOCKDATA (указатель на нее передается в функцию модели при каждом ее вызове, см. §2.3). Если в результате работы функции модели не произошло никаких изменений, которые должны быть отражены на внешнем виде блока, функция может сбросить этот флаг, сообщив тем самым, что изображение данного конкретного блока обновлять не надо. Когда сработает таймер обновления окна подсистемы, RDS проверит состояние этого флага у каждого блока, изображение которого рисуется программно, и, если у всех блоков этот флаг окажется сброшен, и других изменений в подсистеме не было, обновление окна производиться не будет. Если же модель не сбросит этот флаг (сообщая тем самым о наличии изменений во внешнем виде блока, или просто потому что работа с этим флагом не заложена в модель программистом), все содержимое окна будет перерисовано. Следует отметить, что эффективность этого метода борьбы с задержками не очень высока – для его работы необходимо, чтобы все блоки подсистемы работали с флагом RDS_NEEDSDLLREDRAW. Достаточно хотя бы одного блока, модель которого не сбрасывает этот флаг, чтобы окно подсистемы обновлялось постоянно.

Другой способ уменьшить время, которое RDS будет тратить на обновление окна – это оптимизация самой модели блока. Из функции рисования, входящей в модель, необходимо вынести как можно больше операций, выполнять которые при каждом рисовании нет необходимости. Если посмотреть на функцию рисования из примера в §2.10.1, можно заметить, что в ней каждый раз заново вычисляются координаты области графика Gr_x1, Gr_x2, Gr_y1 и Gr_y2, которые зависят от размера блока, выбранного в его настройках шрифта и текущего масштаба окна подсистемы. Поскольку эти вычисления связаны с преобразованием вещественных чисел в строки и определением размеров этих строк, выведенных на экран текущим шрифтом, неплохо было бы выполнять их только при изменении масштаба или шрифта. Для этого координаты области графика нужно сделать полями класса TSimplePlotData и ввести в него еще одно дополнительное поле для отслеживания изменений масштаба. Таким образом, изменения, которые необходимо внести в описание класса, выглядят так (выделены цветом):

      //=========================================
      // Простой график – личная область данных
      //=========================================
      class TSimplePlotData
      { private:
          // Запомненные координаты поля графика
          int Gr_x1,Gr_x2,Gr_y1,Gr_y2;
          // Масштаб окна на момент последнего рисования
          double OldZoom;
          // Настроечные параметры графика (цвета, шаг и т.п.)
          double TimeStep;   // Шаг записи отсчетов
          // …
          // … далее без изменений …

В вещественное поле OldZoom при рисовании графика будет записываться текущий масштаб окна подсистемы. Если при очередном вызове функции Draw значение переданного в нее масштабного коэффициента DrawData->DoubleZoom не совпадет со значением OldZoom, значит, с момента последнего рисования масштаб окна подсистемы изменился, и координаты Gr_x1, Gr_x2, Gr_y1 и Gr_y2 нужно вычислить заново. При изменении параметров шрифта, которое тоже должно приводить к пересчету координат поля графика, будем записывать в OldZoom значение −1. Поскольку масштаб окна не может быть отрицательным, это значение никогда не будет совпадать с DrawData->DoubleZoom, поэтому координаты будут вычислены заново при следующем вызове Draw. Шрифт чисел на осях графика может измениться только в двух случаях: при задании нового шрифта пользователем в окне настроек или при загрузке параметров блока. Таким образом, присваивание OldZoom=-1 должно выполняться вместе с вызовами функций Setup и LoadText. Кроме того, его нужно выполнить в конструкторе класса, чтобы при самом первом вызове Draw координаты тоже вычислялись, а также при изменении размеров блока, поскольку размеры рабочего поля при этом тоже должны изменяться.

Чтобы оградить себя от возможных ошибок в программировании, мы внесли поле OldZoom в закрытую (private) область класса. Но нам нужно иметь возможность сбрасывать запомненное значение масштаба из функции модели, присваивая OldZoom значение −1. Для этого мы добавим в открытую область класса (public) функцию ResetCoords, которая будет этим заниматься:

    //=========================================
    // Простой график – личная область данных
    //=========================================
    class TSimplePlotData
    { private:
        // …
      public:
        // Функция сброса запомненного масштаба последнего рисования
        void ResetCoords(void){OldZoom=-1.0;};
        // Функция отведения массивов отсчетов
        void AllocateArrays(void);
        // …
        // … далее без изменений …

В функции рисования мы будем сравнивать текущий масштаб с запомненным, поэтому, вероятно, следует сделать небольшое замечание относительно сравнения вещественных чисел. Обычно, если эти числа являются результатом каких-либо вычислений, их сравнение производится с заданной точностью. Например, если необходимо сравнить результат какой-либо вещественной функции f(x) с нулем, обычно пишут следующий оператор: «if(fabs(f(x))<DELTA)?…», где DELTA – заданная погрешность. В нашем случае мы сравниваем вещественный масштабный коэффициент с его же прежним значением, которое не является результатом вычислений – это просто запомненное значение той же самой переменной. Таким образом, для отслеживания изменений переменной мы можем использовать оператор точного сравнения «==».

Внесем изменения в указанные выше функции класса. Начнем с конструктора:

      // Конструктор класса личной области данных графика
      TSimplePlotData::TSimplePlotData(void)
      { // Инициализация OldZoom: значение -1 приведет к принудительному
        // вычислению Gr_x1,Gr_x2,Gr_y1 и Gr_y2 в ближайшем вызове
        // функции Draw
        OldZoom=-1.0;
        // Присвоение начальных значений параметрам
        TimeStep=0.1;   // Шаг записи
        BorderColor=0;  // Цвет рамки вокруг блока
        // …
        // … далее без изменений …

Изменения в функции модели блока выглядят так:

            // …
            // Функция настройки параметров
            case RDS_BFM_SETUP:
              data->ResetCoords(); // Сброс запомненных значений
              return data->Setup();

            // Загрузка параметров в текстовом формате
            case RDS_BFM_LOADTXT:
              data->LoadText((char*)ExtParam);
              data->ResetCoords(); // Сброс запомненных значений
              break;

            // Изменение размеров блока
            case RDS_BFM_RESIZE:
              data->ResetCoords(); // Сброс запомненных значений
              break;
            // …

В реакциях модели на загрузку параметров блока и вызов окна настройки (то есть в реакциях, которые могут привести к изменению параметров шрифта и, следовательно, размеров поля графика) добавлен сброс запомненного масштаба. Технически, при вызове функции настройки пересчитывать координаты имеет смысл только тогда, когда пользователь закрыл окно кнопкой «OK», подтвердив изменения. Но, на самом деле, можно присваивать OldZoom значение −1 при любом вызове функции настройки, поскольку в этом случае, если пользователь закрыл окно кнопкой «Отмена», вычисление координат выполнится всего один лишний раз – ни RDS, ни, тем более, пользователь этого не заметят.

В модель также добавлена новая реакция на изменение размеров блока RDS_BFM_RESIZE, в которой тоже сбрасывается запомненный масштаб.

Теперь нужно сделать вычисление координат Gr_x1, Gr_x2, Gr_y1 и Gr_y2 в функции Draw условным – оно должно производиться только при неравенстве OldZoom и масштаба окна DrawData->DoubleZoom. Кроме того, после вычисления необходимо запоминать новое значение масштаба в OldZoom, чтобы заблокировать вычисления координат до его изменения. С этими изменениями функция будет выглядеть следующим образом:

      // Рисование внешнего вида блока
      void TSimplePlotData::Draw(RDS_PDRAWDATA DrawData)
      { // Вспомогательные переменные
        // int Gr_x1,Gr_x2,Gr_y1,Gr_y2; - эти переменные стали полями класса
        int x1,y1,x2,y2,textheight,w1,w2;
        char buf[80];

        // Рамка графика
        rdsXGSetPenStyle(0,PS_SOLID,1,BorderColor,R2_COPYPEN);
        rdsXGSetBrushStyle(0,RDS_GFS_SOLID,FillColor);
        rdsXGRectangle(DrawData->Left,DrawData->Top,
                       DrawData->Left+DrawData->Width,
                       DrawData->Top+DrawData->Height);

        // Необходимо вычислить координаты поля графика относительно
        // верхнего левого угла блока

        // Установка параметров шрифта с учетом масштаба
        rdsXGSetFontByParStr(&Font,DrawData->DoubleZoom);
        if(DrawData->DoubleZoom!=OldZoom) // Масштаб изменен
          { // Зазор сверху – половина высоты цифры + 1 точка
            rdsXGGetTextSize("0",NULL,&textheight);
            Gr_y1=textheight/2+1;
            // Зазор снизу – полная высота цифры + 1 точка
            Gr_y2=DrawData->Height-textheight-1;
            // Зазор слева – ширина самого длинного числа вертикальной
            // оси или половина ширины Xmin
            sprintf(buf," %.*lf ",YNumDecimal,Ymin);
            rdsXGGetTextSize(buf,&w1,NULL);	// Ширина Ymin
            sprintf(buf," %.*lf ",YNumDecimal,Ymax);
            rdsXGGetTextSize(buf,&w2,NULL);	// Ширина Ymax
            if(w2>w1) w1=w2;
            sprintf(buf," %.*lf ",XNumDecimal,Xmin);
            rdsXGGetTextSize(buf,&w2,NULL);	// Ширина Xmin
            w2/=2;
            if(w2>w1) w1=w2;
            Gr_x1=w1;
            // Зазор справа – половина ширины Xmax
            sprintf(buf," %.*lf ",XNumDecimal,Xmax);
            rdsXGGetTextSize(buf,&w2,NULL);	// Ширина Xmax
            w2/=2;
            Gr_x2=DrawData->Width-w2;

            // Запоминание нового масштаба
            OldZoom=DrawData->DoubleZoom;
          } // if(DrawData->DoubleZoom!=OldZoom)

        // Абсолютные (на рабочем поле) координаты поля графика
        x1=DrawData->Left+Gr_x1;
        x2=DrawData->Left+Gr_x2;
        y1=DrawData->Top+Gr_y1;
        y2=DrawData->Top+Gr_y2;
        // …
        // … далее без изменений …

Фактически, в функцию внесено всего три изменения. Во-первых, координаты Gr_x1, Gr_x2, Gr_y1 и Gr_y2 больше не являются локальными переменными функции, теперь они стали полями класса TSimplePlotData. Во-вторых, весь блок вычисления этих координат помещен внутрь условного оператора, который выполняется, только если запомненное значение масштаба отличается от текущего. И, в-третьих, после вычисления координат текущее значение масштаба запоминается в поле OldZoom, чтобы не повторять эти вычисления, пока масштаб не изменится.

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

Еще один способ повысить скорость работы функции рисования применяется довольно редко: если в окне подсистемы видна только часть блока, можно не рисовать его невидимые фрагменты. Это требует довольно существенного усложнения функции рисования, поэтому такой метод обычно применяют только для очень больших блоков, имеющих размер, существенно больший, чем видимая в окне часть рабочего поля подсистемы, и отображающих много слабо связанных между собой элементов. Например, он хорошо работает для блоков, показывающих внутри своего изображения различные географические карты или схемы. Координаты области рабочего поля, видимой в данный момент, передаются в модель блока в поле VisibleRect структуры RDS_DRAWDATA. Это поле имеет тип RECT*, то есть указатель на стандартную структуру Windows, описывающую прямоугольник. Если координаты какого-либо элемента изображения блока, который нужно нарисовать, не попадают в этот прямоугольник, элемент можно не рисовать целиком. Мы не будем приводить здесь пример блока с такого рода оптимизациями. Для рассмотренного блока-графика они не дадут прироста скорости (и даже, может быть, замедлят работу за счет большого количества дополнительных проверок), а блоки, в которых это имело бы смысл, слишком сложны сами по себе, чтобы использовать их в качестве примера.

Достаточно часто изображение блока состоит из неподвижной постоянной части (рамки, шкал, элементов оформления) и каких-либо элементов, изменяющихся и двигающихся в процессе работы блока (стрелок, индикаторов, линий и т.п.). Типичный пример такого блока – рассмотренный выше график, в котором в процессе работы схемы постепенно рисуется только сама линия графика, а рамка, числа на осях и сетка остаются неизменными. Было бы логично перерисовывать неподвижные части изображения только тогда, когда нужно обновить все окно, а в процессе расчета (при обновлении окон по таймеру) рисовать только изменившиеся элементы. Разумеется, это возможно только в том случае, если блоки не перемещаются по рабочему полю подсистемы, иначе у них просто не будет неподвижных частей – каждый раз все изображение блока нужно будет рисовать на новом месте. Кроме того, блоки в окне подсистемы не должны перекрываться и накладываться один на другой, иначе перекрытый блок может испортить изображение блока слоем выше, если нарисует что-нибудь на его фиксированной части.

RDS поддерживает специальный режим обновления окна подсистемы (он независимо включается для каждого окна), в котором модели блока сообщается, должна ли она нарисовать изображение блока полностью, или может отобразить только изменения, произошедшие с момента последнего рисования. За этот режим отвечает флаг «в системе только неподвижные не перекрывающиеся блоки» на вкладке «общие» окна параметров подсистемы (рис. 62). Если этот флаг включен, RDS в режиме расчета будет по-разному рисовать содержимое окна подсистемы, в зависимости от того, из-за чего возникла необходимость в этом рисовании. Если окно необходимо перерисовать из-за того, что оно было скрыто за другим окном, изменило размер, пользователь изменил что-либо в редакторе слоев и т.п., все содержимое окна (связи, блоки, сетка, обои окна) будут нарисованы полностью, при этом поле FullDraw структуры RDS_DRAWDATA, указатель на которую передается в модель блока при рисовании его внешнего вида, будет иметь значение TRUE. Если же окно должно быть перерисовано из-за срабатывания таймера обновления (при этом изображение, нарисованное в прошлый раз, не было ничем перекрыто), RDS будет рисовать только блоки, причем поле FullDraw вышеупомянутой структуры будет иметь значение FALSE.

Рис. 62. Вкладка «общие»окна параметров подсистемы

Таким образом, чтобы воспользоваться преимуществами, предоставляемыми этим режимом рисования, модель блока должна анализировать поле FullDraw. Если его значение – TRUE, модель должна нарисовать изображение блока полностью. Если же оно – FALSE, модель может «дорисовать» на изображении блока изменения, произошедшие с момента последнего рисования. Выигрыш в скорости при этом будет весьма значительным, ведь чем меньше изменений произошло с момента последнего рисования, тем меньше придется рисовать модели. Если изменений вообще не было, модель завершится немедленно, не потратив процессорного времени зря.

Разумеется, как было указано выше, этот режим не годится для подсистем, блоки которых перемещаются внутри окна или накладываются друг на друга. Его включение в таких подсистемах приведет к появлению «хвостов» за движущимися блоками и другим искажениям изображения.

Ускорим этим методом рисование рассмотренного выше графика. Для этого, прежде всего, необходимо запоминать последнюю построенную точку, чтобы при очередном рисовании продолжать линию графика начиная с нее. Кроме того, нужно ввести в функцию рисования анализ поля FullDraw переданной структуры RDS_DRAWDATA. Чтобы не загромождать уже написанную функцию рисования Draw, которая и так получилась достаточно длинной, мы не будем кардинально переделывать ее – она будет вызываться тогда, когда нужно нарисовать блок полностью. В дополнение к ней мы напишем новую функцию DrawFast, которая будет дорисовывать изменившиеся части блока. Изменения, которые нужно внести в описаний класса блока, выглядят так:

      //=========================================
      // Простой график – личная область данных
      //=========================================
      class TSimplePlotData
      { private:
          // Запомненные координаты поля графика
          int Gr_x1,Gr_x2,Gr_y1,Gr_y2;
          // Масштаб окна на момент последнего рисования
          double OldZoom;
          // Индекс последнего нарисованного отсчета
          int LastDrawnIndex;
          // Настроечные параметры графика (цвета, шаг и т.п.)
          double TimeStep;  // Шаг записи отсчетов

          // …

          void LoadText(char *text); // Функция загрузки параметров
          void Draw(RDS_PDRAWDATA DrawData); // Функция рисования
          // Функция быстрого рисования
          void DrawFast(RDS_PDRAWDATA DrawData);
          // …
          // … далее без изменений …

В поле LastDrawnIndex будет храниться индекс последнего отсчета в массивах Times и Values, нарисованного функциями Draw или DrawFast. Функция Draw должна, как и раньше, рисовать линию графика с начала массивов до индекса NextIndex-1, после чего она должна занести значение NextIndex-1 в поле LastDrawnIndex (это будет единственным изменением, которое нужно сделать в Draw). Функция DrawFast должна будет нарисовать отсчеты с LastDrawnIndex+1 по NextIndex-1, и тоже занести NextIndex-1 в LastDrawnIndex. Таким образом, при каждом вызове DrawFast она будет дорисовывать участок графика до последнего запомненного отсчета. В конструкторе класса желательно присвоить полю LastDrawnIndex начальное значение, сигнализирующее об отсутствии уже нарисованной части графика (будем использовать для этого значение –1):

      // Конструктор класса личной области данных графика
      TSimplePlotData::TSimplePlotData(void)
      { // Инициализация OldZoom: значение -1 приведет к принудительному
        // вычислению Gr_x1,Gr_x2,Gr_y1 и Gr_y2 в ближайшем вызове
        // функции Draw
        OldZoom=-1.0;
        // Инициализация LastDrawnIndex
        LastDrawnIndex=-1;
        // Присвоение начальных значений параметрам
        TimeStep=0.1;   // Шаг записи
        BorderColor=0;  // Цвет рамки вокруг блока
        // …
        // … далее без изменений …

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

Добавим в функцию Draw установку поля класса LastDrawnIndex после рисования:

      // Рисование внешнего вида блока
      void TSimplePlotData::Draw(RDS_PDRAWDATA DrawData)
      { // Вспомогательные переменные

        // …

            // Строим ломанную линию по отсчетам из массивов
            for(int i=0;i<NextIndex;i++)
              { // Преобразуем вещественные отсчеты в целочисленные
                // координаты на рабочем поле
                int ix=x1+(Times[i]-Xmin)*(x2-x1)/(Xmax-Xmin),
                    iy=y2-(Values[i]-Ymin)*(y2-y1)/(Ymax-Ymin);
                if(i) // Не первая точка – строим линию от предыдущей
                  rdsXGLineTo(ix,iy);
                else // Первая точка графика – делаем ее текущей
                  rdsXGMoveTo(ix,iy);
              }
            // Запоминаем последний нарисованный отсчет
            LastDrawnIndex=NextIndex-1;
            // Отмена отсечения
            rdsXGSetClipRect(NULL);
          }
      }
      //=========================================

Теперь осталось написать функцию DrawFast и обеспечить ее вызов вместо Draw, если модель блока вызвана RDS для рисования изменений. Вынесем эту проверку внутрь функции DrawFast: она сама будет анализировать поле FullDraw в переданной ей структуре и вызывать Draw, если значение поля истинно. В самой функции модели просто заменим вызов Draw на вызов DrawFast:

            // Рисование внешнего вида блока
            case RDS_BFM_DRAW:
              // data->Draw((RDS_PDRAWDATA)ExtParam);
              data->DrawFast((RDS_PDRAWDATA)ExtParam);
              break;

Наконец, напишем функцию DrawFast:

      // Рисование изменений
      void TSimplePlotData::DrawFast(RDS_PDRAWDATA DrawData)
      { int x1,y1,x2,y2;

        // Если RDS требует полного рисования, или полное рисование
        // еще не проводилось, вызывается старая функция Draw
        if(DrawData->FullDraw || DrawData->DoubleZoom!=OldZoom)
          { Draw(DrawData);
            return;
          }

        // Вычисление абсолютных координат поля графика
        x1=DrawData->Left+Gr_x1;
        x2=DrawData->Left+Gr_x2;
        y1=DrawData->Top+Gr_y1;
        y2=DrawData->Top+Gr_y2;

        if(x1>=x2 || y1>=y2) // Негде рисовать
          return;

        // Если массивы не пустые – рисовать график, начиная
        // с последней уже нарисованной точки
        if(Count)
          { RECT r;
            // Установить область отсечения рисования по полю графика
            r.left=x1+1;
            r.top=y1+1;
            r.right=x2-1;
            r.bottom=y2-1;
            rdsXGSetClipRect(&r);

            // Установить сплошной стиль линии, заданный для
            // графика цвет и толщину линии с учетом масштаба
            rdsXGSetPenStyle(0,PS_SOLID,
              LineWidth*DrawData->DoubleZoom,
              LineColor,R2_COPYPEN);

            // Проверка допустимости LastDrawnIndex – на всякий случай
            if(LastDrawnIndex<0) LastDrawnIndex=0;

            // Строим ломанную линию по отсчетам из массивов,
            // начиная с LastDrawIndex
            for(int i=LastDrawnIndex;i<NextIndex;i++)
              { int ix=x1+(Times[i]-Xmin)*(x2-x1)/(Xmax-Xmin),
                    iy=y2-(Values[i]-Ymin)*(y2-y1)/(Ymax-Ymin);
                if(i!=LastDrawnIndex) rdsXGLineTo(ix,iy);
                else rdsXGMoveTo(ix,iy);
              }

            // Запоминаем последний нарисованный отсчет
            LastDrawnIndex=NextIndex-1;

            // Отмена отсечения
            rdsXGSetClipRect(NULL);
          }
      }
      //=========================================

Прежде всего функция выполняет проверку: требуется ли нарисовать изображение блока полностью, или можно ограничиться только отображением изменений. Если DrawData->FullDraw имеет значение TRUE, то есть требуется полное рисование, вызывается старая функция Draw, и работа функции DrawFast на этом завершается. То же самое происходит и в случае несоответствия масштаба на в момент предыдущего рисования (OldZoom) текущему масштабу (DrawData->DoubleZoom). В противном случае, точно так же, как и в Draw, вычисляются абсолютные координаты поля графика x1, y1, x2 и y2, после чего размеры поля проверяются на допустимость. Если с размерами все в порядке и в массивах есть отсчеты (значение Count не равно нулю), устанавливается область отсечения и стиль линии для рисования графика. Этот фрагмент функции в точности соответствует аналогичному в Draw, а дальше уже начинаются различия.

Если в функции Draw рисование линии графика начиналось с нулевого индекса в массиве отсчетов, в этой функции начинать нужно с индекса LastDrawIndex, поскольку вплоть до этого индекса линия уже нарисована прошлыми вызовами функций. В случае, если LastDrawIndex имеет отрицательное значение (в конструкторе мы присвоили ему −1), ему принудительно присваивается 0, чтобы рисование графика началось с самого первого отсчета. Далее следует цикл рисования, отличающийся от соответствующего цикла в Draw только тем, что в качестве начального индекса везде используется не 0, а LastDrawIndex. После цикла в LastDrawIndex записывается индекс последнего нарисованного отсчета, отключается область отсечения и функция завершается.

Изменения, внесенные в модель графика, позволяют ускорить его рисование в несколько раз, особенно в схемах, работающих синхронно с реальным временем, в которых обновление окон идет постоянно. При этом следует помнить о недопустимости перекрытия изображений блоков в этом режиме, в противном случае можно будет наблюдать эффект, подобный изображенному на рис. 63.

Рис. 63. Искажения изображения у перекрывающихся блоков

На рисунке видно, что линия графика блока, лежащего ниже, рисуется поверх изображения блока, лежащего выше, хотя она должна быть скрыта под ним. В таких случаях нужно отключать ускоренный режим рисования в окне параметров подсистемы (см. рис. 62). Вносить какие-либо изменения в модели блоков не нужно: RDS в этом случае будет каждый раз вызывать модель с требованием полного рисования изображения блока, что в ней должно быть предусмотрено в любом случае. В рассмотренном примере при каждом рисовании графика просто будет вызываться функция Draw, поскольку поле FullDraw структуры RDS_DRAWDATA никогда не будет иметь значение FALSE.