Comments 22
Первый вариант (с вызовом Invalidate в обработке WM_PAINT) — неверный. Вы сами объяснили почему (WM_TIMER).
Второй вариант с OnIdle у вас тоже не верный. Как минимум из-за того, что вам пришлось городить вот это:
А еще из-за того, что вы по прежнему не учитываете, что WM_PAINT может случится, и стандартная отрисовка может «мерцать» в вашем рендере.
А правильно делать так:
Рисовать сцену только в OnPaint, а в OnIdle делать только InvalidateRect. Тогда вы решите проблему со свернутыми окнами, заблокированными экраном по Win+L, да и вообще во всех потенциально возможных случаях + исчезнет проблема с мелькающей дефолтной отрисовкой на обработку WM_PAINT.
Но тут надо быть внимательным, ибо как вы дальше заметили — оно не работает с включенными темами в LCL. Очевидно, что это бага LCL.
Второй вариант с OnIdle у вас тоже не верный. Как минимум из-за того, что вам пришлось городить вот это:
if ( Application.Terminated ) // выполняется завершение работы приложения
or ( Application.ModalLevel > 0 ) // открыты модальные окна
or ( Self.WindowState = wsMinimized ) // окно программы свернуто
or ( { другие условия, при которых не нужно продолжать бесконечный цикл отрисовки } ) then
А еще из-за того, что вы по прежнему не учитываете, что WM_PAINT может случится, и стандартная отрисовка может «мерцать» в вашем рендере.
А правильно делать так:
Рисовать сцену только в OnPaint, а в OnIdle делать только InvalidateRect. Тогда вы решите проблему со свернутыми окнами, заблокированными экраном по Win+L, да и вообще во всех потенциально возможных случаях + исчезнет проблема с мелькающей дефолтной отрисовкой на обработку WM_PAINT.
Но тут надо быть внимательным, ибо как вы дальше заметили — оно не работает с включенными темами в LCL. Очевидно, что это бага LCL.
Спасибо за комментарий!
Первый вариант (OnPaint + InvalidateRect) я тоже считаю неверным.
Но я не мог о нём не упомянуть, поскольку он является самым тривиальным…
Может быть, я недостаточно акцентировал внимание на неудачности этого способа?
В общем-то вариант с использованием OnIdle() у меня работает именно так :)
А Вы пробовали в программе-примере закомментировать то, что «пришлось городить» и запустить под отладчиком?
Я, когда запускаю под отладчиком, сворачиваю окно программы и ставлю брейкпоинт в обработчике OnPaint() — то он, этот обработчик не вызывается, но программа всё равно «кушает» процессор.
Почему? Потому что цикл OnIdle() продолжает работать безо всяких пауз и остановок.
То условие, которое Вы процитировали, отвечает в моём коде за (временное) прекращение обработки цикла OnIdle():
Если параметр Done после исполнения обработчика события будет равено TRUE, то выполнение программы будет приостановлено до тех пор, пока не появятся новые оконные сообщения, а если FALSE — то в случае отсутствия новых оконных сообщений будет повторное срабатывание события OnIdle().
Не могу понять, кто из нас с Вами в этом вопросе кого недопонял — то ли я неправильно прочитал Ваш комментарий, то ли Вы недостаточно внимательно просмотрели текст публикации и исходники примера…
P.S.:
по поводу заблокированного экрана — спасибо за мысль, сам я не догадался это проверить — но попробую сегодня вечером
Первый вариант (OnPaint + InvalidateRect) я тоже считаю неверным.
Но я не мог о нём не упомянуть, поскольку он является самым тривиальным…
Может быть, я недостаточно акцентировал внимание на неудачности этого способа?
А правильно делать так:
Рисовать сцену только в OnPaint, а в OnIdle делать только InvalidateRect.
В общем-то вариант с использованием OnIdle() у меня работает именно так :)
Тогда вы решите проблему со свернутыми окнами, заблокированными экраном по Win+L, да и вообще во всех потенциально возможных случаях + исчезнет проблема с мелькающей дефолтной отрисовкой на обработку WM_PAINT.
А Вы пробовали в программе-примере закомментировать то, что «пришлось городить» и запустить под отладчиком?
Я, когда запускаю под отладчиком, сворачиваю окно программы и ставлю брейкпоинт в обработчике OnPaint() — то он, этот обработчик не вызывается, но программа всё равно «кушает» процессор.
Почему? Потому что цикл OnIdle() продолжает работать безо всяких пауз и остановок.
То условие, которое Вы процитировали, отвечает в моём коде за (временное) прекращение обработки цикла OnIdle():
procedure TFormMain.OnApplicationIdle(Sender: TObject; var Done: Boolean);
Если параметр Done после исполнения обработчика события будет равено TRUE, то выполнение программы будет приостановлено до тех пор, пока не появятся новые оконные сообщения, а если FALSE — то в случае отсутствия новых оконных сообщений будет повторное срабатывание события OnIdle().
Не могу понять, кто из нас с Вами в этом вопросе кого недопонял — то ли я неправильно прочитал Ваш комментарий, то ли Вы недостаточно внимательно просмотрели текст публикации и исходники примера…
P.S.:
по поводу заблокированного экрана — спасибо за мысль, сам я не догадался это проверить — но попробую сегодня вечером
Каюсь, я прочитал вскользь. Подумал что вы отрисовку делаете прямо в OnIdle.
p.s. Я кстати Done := True (на OnIdle) в своих проектах не использую. Подобное делаю только беглонаписанных тестах/семплах. Зачем бесполезно гонять электроэнергию и греть воздух? Если кадр не менялся, то там и нечего перерисовывать. Если есть какая-то анимация и она в кадре — то класс, играющий анимацию сам вызывает Invalidate.
p.s. Я кстати Done := True (на OnIdle) в своих проектах не использую. Подобное делаю только беглонаписанных тестах/семплах. Зачем бесполезно гонять электроэнергию и греть воздух? Если кадр не менялся, то там и нечего перерисовывать. Если есть какая-то анимация и она в кадре — то класс, играющий анимацию сам вызывает Invalidate.
Мне кажется, автор не рассмотрел еще один, очевидный способ реализации: нам нужен «бесконечный цикл»? repeat until false вполне подходит; нужно обрабатывать оконные сообщения — Application.ProcessMessages в цикле с этим справится; корректно завершать программу — на FormClose сбрасываем флаг. Разумеется цикл запускать будем не прямо в FormCreate, я использую таймер 1 раз для «передачи управления». Все эти OnPaint, OnIdle и прочее оставим VCL — они имеют отношение к его главному циклу, но не к нашему.
В своём примере я использую OpenGL + VCL, причем TPanel расположила прямо поверх OpenGL контекста. Исходники примера + exe тут:
В примере выключен VSYNC, это сделано для теста FPS, отрисовка будет идти максимально часто, соответственно CPU нагружен. Включаем VSYNC — отрисовка будет идти 60 раз в секунду.
Текст
procedure TForm1.FormCreate(Sender: TObject);
begin
…
BClose:=False;
Timer1.Enabled:=True;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BClose:=True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled:=False;
repeat // main loop
Application.ProcessMessages;
DoRender;
until BClose;
end;
begin
…
BClose:=False;
Timer1.Enabled:=True;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BClose:=True;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled:=False;
repeat // main loop
Application.ProcessMessages;
DoRender;
until BClose;
end;
В своём примере я использую OpenGL + VCL, причем TPanel расположила прямо поверх OpenGL контекста. Исходники примера + exe тут:
В примере выключен VSYNC, это сделано для теста FPS, отрисовка будет идти максимально часто, соответственно CPU нагружен. Включаем VSYNC — отрисовка будет идти 60 раз в секунду.
Исходники примера + exe тут: yadi.sk/d/-GSPdSvUgVUM2
Цикл с постоянной перерисовкой кадра вообще нужен только для плавной анимации движения и изменения объектов с частотой обновления дисплея. Если отображаете статическую сцену, чертеж, схему и т.п. лучше отрисовку выполнять по событиям. При плавном скроллинге или зуме анимацию создаст повторяющееся событие (мышь, скролл, клавиатура).
Спасибо за комментарии.
Я загрузил Ваши исходники и посмотрел в Delphi XE7.
Работает неплохо, но это не более удачный подход, чем раздел «OnPaint + InvalidateRect» из текста публикации.
У меня в Вашем примере работают корректно, вопреки некоторым из утверждений MrShoor:
— хинты
— PostQuitMessage()
— PostMessage(Form1.Handle, WM_CLOSE...)
— PostMessage(Form1.Handle, WM_QUIT...)
— синхронизация через TThread.Synchronize (не уверен, но по крайней мере мне не удалось создать проблемный вызов этой функции)
Сломаны следующие моменты:
— Application.OnIdle
— Application.Terminate() (правда, это легко исправляется, буквально одной строчкой кода)
— ресайз формы (если в системе включена опция «Отображать содержимое окна при перетаскивании», то изменение размеров у обычных окон выполняется плавно по мере движения мыши и со мгновенной перерисовкой; Ваш же пример не хочет перерисовывать окно до тех пор, пока пользователь не отпустит правую кнопку мыши)
В целом Ваш подход с Application.ProcessMessages() нетипичен для программирования под многозадачные ОС, типа Windows/Linux, и др…
Что ни говори, а корректная обработка очереди сообщений всё-таки нарушается (Application.ProcessMessages — это тот инструмент, который надо использовать с осторожностью, да и бесконечные циклы принято организовывать другими способами). А мне при подготовке публикации хотелось оставаться в рамках концепций программирования под платформу Win32, поэтому в тексте публикации использованы лишь те способы, которые там сейчас есть.
Признавайтесь: в своё время Вы много программировали под MS-DOS? :)
Я загрузил Ваши исходники и посмотрел в Delphi XE7.
Работает неплохо, но это не более удачный подход, чем раздел «OnPaint + InvalidateRect» из текста публикации.
У меня в Вашем примере работают корректно, вопреки некоторым из утверждений MrShoor:
— хинты
— PostQuitMessage()
— PostMessage(Form1.Handle, WM_CLOSE...)
— PostMessage(Form1.Handle, WM_QUIT...)
— синхронизация через TThread.Synchronize (не уверен, но по крайней мере мне не удалось создать проблемный вызов этой функции)
Сломаны следующие моменты:
— Application.OnIdle
— Application.Terminate() (правда, это легко исправляется, буквально одной строчкой кода)
— ресайз формы (если в системе включена опция «Отображать содержимое окна при перетаскивании», то изменение размеров у обычных окон выполняется плавно по мере движения мыши и со мгновенной перерисовкой; Ваш же пример не хочет перерисовывать окно до тех пор, пока пользователь не отпустит правую кнопку мыши)
В целом Ваш подход с Application.ProcessMessages() нетипичен для программирования под многозадачные ОС, типа Windows/Linux, и др…
Что ни говори, а корректная обработка очереди сообщений всё-таки нарушается (Application.ProcessMessages — это тот инструмент, который надо использовать с осторожностью, да и бесконечные циклы принято организовывать другими способами). А мне при подготовке публикации хотелось оставаться в рамках концепций программирования под платформу Win32, поэтому в тексте публикации использованы лишь те способы, которые там сейчас есть.
Признавайтесь: в своё время Вы много программировали под MS-DOS? :)
мда, с PostQuitMessage() и PostMessage(WM_CLOSE) я немного ошибся, они тоже поломаны.
хотя и будут работать, если в цикл добавить строку с проверкой Application.Terminated:
к сожалению, не смог отредактировать свой предыдущий комментарий
хотя и будут работать, если в цикл добавить строку с проверкой Application.Terminated:
repeat // main loop
Application.ProcessMessages;
if CheckBox1.Checked then ObjMove;
DoRender;
Label1.Caption:=format('NF %d ',[NFrame]);
inc(NFrame);
// добавленная строка
BClose := ( BClose ) or ( Application.Terminated );
until BClose;
к сожалению, не смог отредактировать свой предыдущий комментарий
Привет.
Я тут нечаянно поддерживала Вашу тему в Ваше отсутствие, ничего?
Признаюсь: ваше ясновиденье остаётся лично Вашим, если я хоть слово скажу про что-то древнее WinXP это уже будет намек на возраст, а Вы такого намека не допускали, верно?
Я под Android сейчас пишу, свою статью только сегодня опубликовала: habrahabr.ru/post/258379
Возможно возня с GLES убила во мне чувство пиетета к VCL и я совсем забыла о священных устоях VCL-щиков (главный цикл VCL есть черный ящик, на познание его сути жизни не хватит). А тут взяла и выложила его — Repeat Until, все просто. Согласна, это жестоко с моей стороны.
Не стоит слишком серьёзно относится к моему примеру — он делает то что делает, шейдеры GLSL компилируются, шарики рисуются, ошибок нет. Кажется, на его написание я потратила времени меньше чем на это обсуждение.
Я всего лишь воспользовалась Вашим предложением и дополнила Ваш сборник рецептов своим «борщом», тем более что Вы намекали на нужность аналогичной демки с OpenGL. У меня аналогичная.
Я скажу даже больше — могу поднять исходники GLScene (с ними не спорят, авторитет Майка Лишке очень велик), там сделан тот же фокус с компонентом что и у Вас, прописаны все нюансы. То есть, я могу авторитетно подтвердить правильность ваших выводов.
Впрочем, ни на на шаг не отступаю, мой пример корректен, ничего там не сломано — каждый может его скачать и проверить. Бить нужно по тем рукам, которые вносят некорректные правки.
1) Включите VSYNC.
procedure TForm1.FormCreate(Sender: TObject);
wglSwapIntervalEXT(0); // VSYNC off — эту строчку нужно закомментировать.
С ней я гружу GPU и CPU по полной для теста FPS, какое там OnIdle — ядро CPU будет загружено на 100%.
VSYNC я отключила по просьбе одного форумного товарища, любителя оценивать скорость рендера по FPS.
А нужно было повесить это на опцию.
2) Application.Terminate? Не забудьте уведомить второй цикл о Вашем намерении, он же не в курсе про существование TApplication и его флаги. У второго цикла свой один флаг, до его сброса (я это делаю на OnClose) он не завершится.
3) Отрисовка при Resize формы делается так как она написана. Для «правильной» перерисовки её придется дописать.
Вот, вспомнила — моя «старая» демка: bionica-game.tumblr.com.
Как раз VCL форма, Resize прописан. Могу поднять исходники если надо.
Я тут нечаянно поддерживала Вашу тему в Ваше отсутствие, ничего?
Признаюсь: ваше ясновиденье остаётся лично Вашим, если я хоть слово скажу про что-то древнее WinXP это уже будет намек на возраст, а Вы такого намека не допускали, верно?
Я под Android сейчас пишу, свою статью только сегодня опубликовала: habrahabr.ru/post/258379
Возможно возня с GLES убила во мне чувство пиетета к VCL и я совсем забыла о священных устоях VCL-щиков (главный цикл VCL есть черный ящик, на познание его сути жизни не хватит). А тут взяла и выложила его — Repeat Until, все просто. Согласна, это жестоко с моей стороны.
Не стоит слишком серьёзно относится к моему примеру — он делает то что делает, шейдеры GLSL компилируются, шарики рисуются, ошибок нет. Кажется, на его написание я потратила времени меньше чем на это обсуждение.
Я всего лишь воспользовалась Вашим предложением и дополнила Ваш сборник рецептов своим «борщом», тем более что Вы намекали на нужность аналогичной демки с OpenGL. У меня аналогичная.
Я скажу даже больше — могу поднять исходники GLScene (с ними не спорят, авторитет Майка Лишке очень велик), там сделан тот же фокус с компонентом что и у Вас, прописаны все нюансы. То есть, я могу авторитетно подтвердить правильность ваших выводов.
Впрочем, ни на на шаг не отступаю, мой пример корректен, ничего там не сломано — каждый может его скачать и проверить. Бить нужно по тем рукам, которые вносят некорректные правки.
1) Включите VSYNC.
procedure TForm1.FormCreate(Sender: TObject);
wglSwapIntervalEXT(0); // VSYNC off — эту строчку нужно закомментировать.
С ней я гружу GPU и CPU по полной для теста FPS, какое там OnIdle — ядро CPU будет загружено на 100%.
VSYNC я отключила по просьбе одного форумного товарища, любителя оценивать скорость рендера по FPS.
А нужно было повесить это на опцию.
2) Application.Terminate? Не забудьте уведомить второй цикл о Вашем намерении, он же не в курсе про существование TApplication и его флаги. У второго цикла свой один флаг, до его сброса (я это делаю на OnClose) он не завершится.
3) Отрисовка при Resize формы делается так как она написана. Для «правильной» перерисовки её придется дописать.
Вот, вспомнила — моя «старая» демка: bionica-game.tumblr.com.
Как раз VCL форма, Resize прописан. Могу поднять исходники если надо.
Интересно у нас получается — уже аж два умных человека изобретают способы, как бы поломать работу моего примера посредством его модификации. Пока самый боевой вариант — сообщение послать мимо второго цикла, либо напрямую обратиться к TApplication, главное чтобы второй цикл не заметил (он только событие OnClose отслеживает, остальное не прописано). На мой взгляд, все эти способы «ломания» очевидны из текста программы.
Интересно, а вы сможете потом подменить мою демку на Яндекс.Диск? Чтобы рабочий пример сделать глючным и не рабочим?
Поступите проще — удалите любую строчку из написанных мною 7-ми строк и мой пример перестанет работать.
Или так — давайте я вам пример с потоками подкину — там больше способов поломать будет.
Я вот что думаю: если до ваших правок пример работал, то после ваших правок его работа уже на вашей совести. На каждый добавленный метод обхода второго цикла можно дописать метод учета этого обхода. Пропишите флаг, сообщите второму циклу о своих действиях — он и отработает.
Резюме: это мы «цветочки» обсуждали, на самом деле в сочетании VCL + OpenGL/DirectX есть куда более интересные подводные камни. Простое контекстное меню поверх области графики с статической картинкой уже создаст нам проблему. Поместить контекст графики на многостраничный компонент — отдельная история. Главное: вообще-то считается, что эти вопросы остались в прошлом веке. У FireMonkey контролы и область для графики в одном контексте.
Интересно, а вы сможете потом подменить мою демку на Яндекс.Диск? Чтобы рабочий пример сделать глючным и не рабочим?
Поступите проще — удалите любую строчку из написанных мною 7-ми строк и мой пример перестанет работать.
Или так — давайте я вам пример с потоками подкину — там больше способов поломать будет.
Я вот что думаю: если до ваших правок пример работал, то после ваших правок его работа уже на вашей совести. На каждый добавленный метод обхода второго цикла можно дописать метод учета этого обхода. Пропишите флаг, сообщите второму циклу о своих действиях — он и отработает.
Резюме: это мы «цветочки» обсуждали, на самом деле в сочетании VCL + OpenGL/DirectX есть куда более интересные подводные камни. Простое контекстное меню поверх области графики с статической картинкой уже создаст нам проблему. Поместить контекст графики на многостраничный компонент — отдельная история. Главное: вообще-то считается, что эти вопросы остались в прошлом веке. У FireMonkey контролы и область для графики в одном контексте.
Blackmorsha, спасибо за приподнятое настроение, Ваш сарказм очарователен ))
только вот просьба не принимать близко к сердцу наши ответы.
никто не спорит, что указанный Вами метод имеет право на жизнь, просто он имеет не меньше проблем, чем другие способы.
давайте на этом прекратим дискуссию насчет Вашего способа в организации цикла отрисовки. кому тема интересна, тот в любом случае прочитает комментарии и примет Вашу инфу к сведению… кому не нужно — тот и основной текст не осилит.
добавлять Ваш способ в текст публикации, извините, не буду — для Windows он не более удачен, чем OnPaint+InvalidateRect, по другим платформам — не знаю.
другое дело, если появится инфа о способах, не связанных с событием OnIdle(), но не менее корректных в плане обработки поступающей очереди сообщений — такое буду добавлять, если где чего узнаю.
P.S.:
с FireMonkey, признаться, не связывался. попробую, как-нибудь попозже покопать его.
а под Android только на Java программировал, и то немного… Жду продолжений Вашей публикации :)
только вот просьба не принимать близко к сердцу наши ответы.
никто не спорит, что указанный Вами метод имеет право на жизнь, просто он имеет не меньше проблем, чем другие способы.
давайте на этом прекратим дискуссию насчет Вашего способа в организации цикла отрисовки. кому тема интересна, тот в любом случае прочитает комментарии и примет Вашу инфу к сведению… кому не нужно — тот и основной текст не осилит.
добавлять Ваш способ в текст публикации, извините, не буду — для Windows он не более удачен, чем OnPaint+InvalidateRect, по другим платформам — не знаю.
другое дело, если появится инфа о способах, не связанных с событием OnIdle(), но не менее корректных в плане обработки поступающей очереди сообщений — такое буду добавлять, если где чего узнаю.
P.S.:
с FireMonkey, признаться, не связывался. попробую, как-нибудь попозже покопать его.
а под Android только на Java программировал, и то немного… Жду продолжений Вашей публикации :)
В FM (FireMonkey) под Win7 графика работает через DirectCanvas (DirectX), вместо VCL-контролов применяются их улучшенные аналоги, которые отрисовывают себя в контексте DX. Один контекст, один «главный цикл приложения», контролы и графика хорошо сочетаются. Можно кнопку установить на 3D панель, саму панель вращать в 3D, при желании скрутить в трубочку — кнопка продолжает работать. Красота. Но exe-файлы будут иметь непривычно большой объем, потому что содержат код всех применённых контролов (плюс аналогичный код на GDI+ для WinXP).
Жуть. Никогда не используйте Application.ProcessMessages; А если очень надо, то хорошо подумайте, и не используйте.
Ваш точно код ломает:
1. Завершение приложения по WM_QUIT
2. Работу Application.OnIdle
3. Синхронизацию через TThread.Synchronize
и бог знает что еще ломает. Не надо так делать.
Ваш точно код ломает:
1. Завершение приложения по WM_QUIT
2. Работу Application.OnIdle
3. Синхронизацию через TThread.Synchronize
и бог знает что еще ломает. Не надо так делать.
MrShoor
Вы мою демку не проверяли в работе?
Мне тоже, как видимо и автору статьи понадобилось недавно быстренько шейдеры потестить (из за одной «вкусной» статьи на Хабре), тут и возникла идея не бегать от VCL при работе с графикой, все-таки готовые контролы, и такой пример быстрее написать. Поэтому тему заявленную автором я признаю, реально такое нужно бывает.
Отвечаю по пунктам:
1. WM_QUIT в моём примере обрабатывается корректно, это проверяемо — сообщение WM_QUIT проходит при закрытии окна «крестиком», выключении PC; также сообщение можно послать из другой программы; все 3 случая — 100% правильно отрабатываются, вообще-то это следует из текста примера;
2. Application.OnIdle происходит своим чередом. В своём примере я его не использую (а зачем?) и пишу о том, что это OnIdle «главного цикла» VCL, а у нашей графики свой «главный цикл», который впрочем не мешает VCL, что и требуется по условиям задачи автора. Могу на OnIdle повесить что нибудь, например, проверку, не изменились ли текстовые файлы с шейдерами. Это будет работать.
3. У автора шла речь об одном потоке и простом примере, я тоже не стала преумножать число потоков приложения. Мой способ сводится к тому, как в одном потоке приложения сделать 2 или более «независимых циклов», конечно эти циклы не есть отдельные потоки, но они могут работать параллельно без семафоров. Синхронизацию через TThread.Synchronize мой пример никак не нарушает, проверено — я использую такой способ в своём многопоточном сервере, все ок.
Ничего мой пример не ломает. Это просто цикл который крутится сам по себе, у него нет магических свойств. Это обычный типовой приём из области практического программирования, не я его придумала.
Application.ProcessMessages использовать можно, об этом гласит Help и офф. примеры.
Постарайтесь быть доброжелательнее, во имя Хабра. Хотя бы потому, что Вам будет крайне сложно доказать неработоспособность рабочего примера. В примере-то всего 5 строчек и все работают правильно. Я бы никогда не взялась такое оспаривать.
Мир?
Вы мою демку не проверяли в работе?
Мне тоже, как видимо и автору статьи понадобилось недавно быстренько шейдеры потестить (из за одной «вкусной» статьи на Хабре), тут и возникла идея не бегать от VCL при работе с графикой, все-таки готовые контролы, и такой пример быстрее написать. Поэтому тему заявленную автором я признаю, реально такое нужно бывает.
Отвечаю по пунктам:
1. WM_QUIT в моём примере обрабатывается корректно, это проверяемо — сообщение WM_QUIT проходит при закрытии окна «крестиком», выключении PC; также сообщение можно послать из другой программы; все 3 случая — 100% правильно отрабатываются, вообще-то это следует из текста примера;
2. Application.OnIdle происходит своим чередом. В своём примере я его не использую (а зачем?) и пишу о том, что это OnIdle «главного цикла» VCL, а у нашей графики свой «главный цикл», который впрочем не мешает VCL, что и требуется по условиям задачи автора. Могу на OnIdle повесить что нибудь, например, проверку, не изменились ли текстовые файлы с шейдерами. Это будет работать.
3. У автора шла речь об одном потоке и простом примере, я тоже не стала преумножать число потоков приложения. Мой способ сводится к тому, как в одном потоке приложения сделать 2 или более «независимых циклов», конечно эти циклы не есть отдельные потоки, но они могут работать параллельно без семафоров. Синхронизацию через TThread.Synchronize мой пример никак не нарушает, проверено — я использую такой способ в своём многопоточном сервере, все ок.
Ничего мой пример не ломает. Это просто цикл который крутится сам по себе, у него нет магических свойств. Это обычный типовой приём из области практического программирования, не я его придумала.
Application.ProcessMessages использовать можно, об этом гласит Help и офф. примеры.
Постарайтесь быть доброжелательнее, во имя Хабра. Хотя бы потому, что Вам будет крайне сложно доказать неработоспособность рабочего примера. В примере-то всего 5 строчек и все работают правильно. Я бы никогда не взялась такое оспаривать.
Мир?
Вы мою демку не проверяли в работе?Я смотрю на код, который вы привели. Я знаю как работает Application.ProcessMessages и знаю как работает Application.Run. Этого достаточно.
1. WM_QUIT в моём примере обрабатывается корректно, это проверяемо — сообщение WM_QUIT проходит при закрытии окна «крестиком», выключении PC; также сообщение можно послать из другой программы; все 3 случая — 100% правильно отрабатываются, вообще-то это следует из текста примера;
WM_QUIT не обрабатывается. Вы в этом легко можете убедиться кинув на форму кнопку, и вызвав PostQuitMessage(0); в обработчкие OnClick.
2. Application.OnIdle происходит своим чередом. В своём примере я его не использую (а зачем?) и пишу о том, что это OnIdle «главного цикла» VCL, а у нашей графики свой «главный цикл», который впрочем не мешает VCL, что и требуется по условиям задачи автора. Могу на OnIdle повесить что нибудь, например, проверку, не изменились ли текстовые файлы с шейдерами. Это будет работать.Это не будет работать. Киньте на форму ApplicationEvents и убедитесь самостоятельно.
3. У автора шла речь об одном потоке и простом примере, я тоже не стала преумножать число потоков приложения. Мой способ сводится к тому, как в одном потоке приложения сделать 2 или более «независимых циклов», конечно эти циклы не есть отдельные потоки, но они могут работать параллельно без семафоров. Синхронизацию через TThread.Synchronize мой пример никак не нарушает, проверено — я использую такой способ в своём многопоточном сервере, все ок.Любой сторонний компонент/библиотека, использующий TThread зависнет при вызове Synchronize. Например компоненты Indy использующие серверные сокеты. Вы можете даже не знать, что эти компоненты создают потоки. Просто компоненты перестанут работать, втихую. А все благодаря вашему «циклу».
Ничего мой пример не ломает. Это просто цикл который крутится сам по себе, у него нет магических свойств. Это обычный типовой приём из области практического программирования, не я его придумала.Вы просто не знаете устройство VCL. VCL под капотом выполняет далеко не
repeat
Application.ProcessMessages;
until IsFinish;
и когда вы крутите цикл так, то код, который должен был выполнятся в Application.Run — перестает выполнятся.
Application.ProcessMessages использовать можно, об этом гласит Help и офф. примеры.Само собой, если функция в паблике — то использовать можно, но не нужно, потому что это очень плохой тон. Даже так: ОЧЕНЬ плохой тон. Вы пока еще новичек в мире программирования, поэтому просто поверьте моему опыту, хорошо?
Постарайтесь быть доброжелательнее, во имя Хабра. Хотя бы потому, что Вам будет крайне сложно доказать неработоспособность рабочего примера. В примере-то всего 5 строчек и все работают правильно. Я бы никогда не взялась такое оспаривать.Я с вами не воевал. Я просто против, когда учат плохому. Вы можете убедится в том, что ваши 5 строчек ломают чужой код. Как это сделать — я написал. Между прочим автор текущей статьи сделал все как раз таки правильно, и ваш рендер стоило бы переделать именно так.
Мир?
Кинула на форму кнопку, вызвала в её обработчике OnClick функцию Winapi.Window.PostQuitMessage(0), по нажатию кнопки приложение закрывается. На всякий случай привожу текст из Vcl.Forms.pas: TApplication.ProcessMessages с обработкой события WM_QUIT, TApplication.Run с завершением цикла по флагу Terminated, TApplication.HandleMessage с вызовом OnIdle.
Вот он, главный цикл VCL который у него «под капотом» в Vcl.Forms.pas TApplication.Run.
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
Выводы: при использовании графики (DX или OpenGL) в VCL приложениях не обязательно привязывать рендер к событиям VCL-компонентов, вы можете по прежнему использовать свой «главный цикл», также как в WinAPI программах. Только не забывайте «дать подышать» VCL при помощи Application.ProcessMessages, он все таки занимается обработкой сообщений.
Тест
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
Unicode: Boolean;
MsgExists: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
if Unicode then
MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(Msg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
if Unicode then
DispatchMessageW(Msg)
else
DispatchMessageA(Msg);
end;
end
else
begin
{$IF DEFINED(CLR)}
if Assigned(FOnShutDown) then FOnShutDown(self);
DoneApplication;
{$IFEND}
FTerminate := True;
end;
end;
end;
end;
procedure TApplication.ProcessMessages;
var
Msg: TMsg;
begin
while ProcessMessage(Msg) do {loop};
end;
procedure TApplication.Run;
begin
FRunning := True;
try
{$IF NOT DEFINED(CLR)}
AddExitProc(DoneApplication);
{$IFEND}
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE:
begin
FInitialMainFormState := wsMinimized;
FMainForm.FWindowState := wsMinimized;
end;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if (FMainForm.FWindowState = wsMinimized) or (FInitialMainFormState = wsMinimized) then
begin
Minimize;
if (FInitialMainFormState = wsMinimized) then
FMainForm.Show;
end else
FMainForm.Visible := True;
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
end;
finally
FRunning := False;
end;
end;
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;
var
Handled: Boolean;
Unicode: Boolean;
MsgExists: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
if Unicode then
MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(Msg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
if Unicode then
DispatchMessageW(Msg)
else
DispatchMessageA(Msg);
end;
end
else
begin
{$IF DEFINED(CLR)}
if Assigned(FOnShutDown) then FOnShutDown(self);
DoneApplication;
{$IFEND}
FTerminate := True;
end;
end;
end;
end;
procedure TApplication.ProcessMessages;
var
Msg: TMsg;
begin
while ProcessMessage(Msg) do {loop};
end;
procedure TApplication.Run;
begin
FRunning := True;
try
{$IF NOT DEFINED(CLR)}
AddExitProc(DoneApplication);
{$IFEND}
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE:
begin
FInitialMainFormState := wsMinimized;
FMainForm.FWindowState := wsMinimized;
end;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if (FMainForm.FWindowState = wsMinimized) or (FInitialMainFormState = wsMinimized) then
begin
Minimize;
if (FInitialMainFormState = wsMinimized) then
FMainForm.Show;
end else
FMainForm.Visible := True;
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
end;
finally
FRunning := False;
end;
end;
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;
Вот он, главный цикл VCL который у него «под капотом» в Vcl.Forms.pas TApplication.Run.
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
Выводы: при использовании графики (DX или OpenGL) в VCL приложениях не обязательно привязывать рендер к событиям VCL-компонентов, вы можете по прежнему использовать свой «главный цикл», также как в WinAPI программах. Только не забывайте «дать подышать» VCL при помощи Application.ProcessMessages, он все таки занимается обработкой сообщений.
Напористая вы. Я даже скачал ваш пример, чтобы убедится. Выводы все те же, все поломано.
Я кинул в вашем примере на кнопку с обработчиком:
Вывод: ваш цикл ломает обработку WM_QUIT
Теперь смотрим на TApplication.HandleMessage. Что он делает? Вызывает Idle если ProcessMessage вернул False. ProcessMessage это не ProcessMessages, не путать. А теперь посмотрим на ProcessMessages который вы так же привели:
Давайте посмотрим что у нас происходит в Idle:
Кинула на форму кнопку, вызвала в её обработчике OnClick функцию Winapi.Window.PostQuitMessage(0), по нажатию кнопки приложение закрывается.
Я кинул в вашем примере на кнопку с обработчиком:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostQuitMessage(0);
end;
Приложение не завершается. Но это не удивительно. PostQuitMessage отправляет только WM_QUIT. TApplication.ProcessMessage конечно же обрабатывает WM_QUIT, но он только выставляет флаг FTerminate := True;, который нужен для выхода из главного оконного цикла Application.Run, который вы привели. А кто выставит ваш флаг BClose, чтобы код вышел из вашего цикла? Никто. Это прекрасно видно по коду, который вы привели.Вывод: ваш цикл ломает обработку WM_QUIT
Теперь смотрим на TApplication.HandleMessage. Что он делает? Вызывает Idle если ProcessMessage вернул False. ProcessMessage это не ProcessMessages, не путать. А теперь посмотрим на ProcessMessages который вы так же привели:
procedure TApplication.ProcessMessages;
var
Msg: TMsg;
begin
while ProcessMessage(Msg) do
{ loop } ;
end;
Кто вызывает Idle? Никто. Итак у нас поломаный Idle. Я на всякий случай лично проверил это вот этим обработчиком добавив его в вашем примере:procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
var Done: Boolean);
begin
AllocConsole;
WriteLn(IntToStr(Random(1000)));
Done := False;
end;
Давайте посмотрим что у нас происходит в Idle:
procedure TApplication.Idle(const Msg: TMsg);
var
Control: TControl;
Done: Boolean;
begin
Control := DoMouseIdle;
if FShowHint and (FMouseControl = nil) then
CancelHint;
Application.Hint := GetLongHint(GetHint(Control));
Done := True;
try
if Assigned(FOnIdle) then FOnIdle(Self, Done);
if Done then
if FActionUpdateDelay <= 0 then
DoActionIdle
else
if IdleTimerHandle = 0 then
begin
// Constantly assigning to the IdleTimerDelegate causes a
// memory allocation, and alot of TFNTimerProc's appear in Gen0 because of this.
// Only assign the delgate once; that is all that is needed.
if not Assigned(IdleTimerDelegate) then
IdleTimerDelegate := @IdleTimerProc;
IdleTimerHandle := SetTimer(0, 0, FActionUpdateDelay, IdleTimerDelegate);
if IdleTimerHandle = 0 then
DoActionIdle
end;
except
HandleException(Self);
end;
if (GetCurrentThreadID = MainThreadID) and CheckSynchronize then
Done := False;
if Done then WaitMessage;
end;
У нас дополнительно отваливается Application.Hint, но это ерунда. Главное, что теперь у нас CheckSynchronize не вызывается. А CheckSynchronize — это обработчик всех TThread.Synchronize.Выводы: при использовании графики (DX или OpenGL) в VCL приложениях не обязательно привязывать рендер к событиям VCL-компонентов, вы можете по прежнему использовать свой «главный цикл», также как в WinAPI программах. Только не забывайте «дать подышать» VCL при помощи Application.ProcessMessages, он все таки занимается обработкой сообщений.Выводы: автор комментария выше ничего не проверял, или проверял не так. Код по прежнему поломан, что хорошо видно из исходников VCL, которые были приведены, а так же легко проверяется в примере автора.
Я бы сказала так:
Вариант 1 (традиционный). Для работы графики делаем WinAPI приложение, обрабатываем сообщения самостоятельно. Текста там много, но примеры есть, это работает у всех.
Вариант 2 (рассматриваем). Создать в VCL приложении OpenGL (или DirectX) контекст на форме. Это можно, но придется внимательно прописать все нюансы. Контексты GDI и OpenGL разные, рисованием занимаются разные драйвера. Задачка решаемая, например GLScene так и делает, реализация всех нюансов помещена в компонент. Автор топика (кстати где он?) пошел по этому пути, это нормально. Мы помним о том, что обработкой сообщений занимается VCL и не мешаем ему, используем его обработчики событий.
Вариант 3 (мой+Ваш). Вы скачали демку. Пробовали нажать на крестик? Окно закроется. Реакция на клавиатуру и мышь также в норме. Для дальнейших успехов, в тот момент когда вы посылаете сообщение WM_QUIT вы должны дополнительно: либо вызвать Form1.Close, либо напрямую сбросить мой флаг. Напоминаю, в моём примере 2 главных цикла, мой и VCL, оба крутятся в одном потоке, для корректного выхода из обоих циклов необходимо «уважить» каждый. Раз уж моя демка с VCL, я работаю через VCL и все в норме. Если мне потребуется самостоятельная посылка/обработка сообщений, то я не должна забывать о втором цикле и вовремя обеспечивать его информацией.
Вариант 1 (традиционный). Для работы графики делаем WinAPI приложение, обрабатываем сообщения самостоятельно. Текста там много, но примеры есть, это работает у всех.
Вариант 2 (рассматриваем). Создать в VCL приложении OpenGL (или DirectX) контекст на форме. Это можно, но придется внимательно прописать все нюансы. Контексты GDI и OpenGL разные, рисованием занимаются разные драйвера. Задачка решаемая, например GLScene так и делает, реализация всех нюансов помещена в компонент. Автор топика (кстати где он?) пошел по этому пути, это нормально. Мы помним о том, что обработкой сообщений занимается VCL и не мешаем ему, используем его обработчики событий.
Вариант 3 (мой+Ваш). Вы скачали демку. Пробовали нажать на крестик? Окно закроется. Реакция на клавиатуру и мышь также в норме. Для дальнейших успехов, в тот момент когда вы посылаете сообщение WM_QUIT вы должны дополнительно: либо вызвать Form1.Close, либо напрямую сбросить мой флаг. Напоминаю, в моём примере 2 главных цикла, мой и VCL, оба крутятся в одном потоке, для корректного выхода из обоих циклов необходимо «уважить» каждый. Раз уж моя демка с VCL, я работаю через VCL и все в норме. Если мне потребуется самостоятельная посылка/обработка сообщений, то я не должна забывать о втором цикле и вовремя обеспечивать его информацией.
Вариант 3 (мой+Ваш)Это не мой вариант. Это только ваш вариант, который ломает код. Мой вариант как в статье выше, и это правильный вариант.
Для дальнейших успехов, в тот момент когда вы посылаете сообщение WM_QUIT вы должны дополнительно: либо вызвать Form1.Close, либо напрямую сбросить мой флаг.Гениально. А если это библиотечный код, и он не знает ничего о Form1.BClose?
А что делать с OnIdle? А что делать с синхронизацией TThread.Synchronize?
Хм, я всегда в paintBox рисовал, и ничего не надо городить с очередями сообщений. Чем плох этот подход?
Sign up to leave a comment.
Использование Direct3D с высокоуровневыми библиотеками компонентов VCL/LCL