Home Delphi while loop causing program to stop responding
Reply: 2

Delphi while loop causing program to stop responding

DoYouEvenFish
1#
DoYouEvenFish Published in 2017-11-09 23:52:44Z

I'm using Delphi 7 and the program I am writing needs to continuously draw on the screen. While it currently doesn't draw anything important, this is a necessity in the program later on. However, when I put the procedure for drawing the screen in a while loop which can only be stopped by pressing any button the program stops responding completely. I don't understand why this is happening. Surely, as the while loop can be exited, the program should continue to run fine. Here is the source code:

unit DD04f1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TeCanvas, ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Button1: TButton;
    procedure Image1OnCreate();
    procedure ScreenRender();
    procedure OnCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  IsDone : Boolean;

implementation

{$R *.dfm}
procedure TForm1.OnCreate(Sender: TObject);
begin
  IsDone := False;
end;

procedure TForm1.Image1OnCreate ();
var
  Count:Integer;
begin
  image1.canvas.Create();
  image1.canvas.Pen.Color:=clBlack;
  image1.canvas.rectangle(0,0,640,480);
  image1.canvas.Pen.Color:=$ed630e; //bgr instead of rgb

  Count:=0;
  While (Count <> 640) do
  begin
    image1.Canvas.moveto(Count,0);
    image1.Canvas.LineTo(Count,480);
    Count:=Count+1;

  end;
end;

procedure TForm1.ScreenRender();
var
  Count : Integer;
begin
  Count:=0;
  While(Count<>640) do
  begin
    image1.Canvas.moveto(Count,0);
    image1.Canvas.LineTo(Count,480);
    Count:=Count+1;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    Image1OnCreate();
    Button1.Visible := False;
    While(IsDone = False) do
    begin
      ScreenRender();
    end;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  IsDone := True;
end;

end.
Arioch 'The
2#
Arioch 'The Reply to 2017-11-10 13:15:25Z
procedure TForm1.OnCreate(Sender: TObject);
begin
  IsDone := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    Image1OnCreate();
    Button1.Visible := False;
    While(IsDone = False) do
    begin
      ScreenRender();
    end;
end;

Assuming IsDone is always False (because otherwise we would not enter the loop), this loop can not terminate. It is infinite.

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  IsDone := True;
end;

You do not call this procedure from inside TForm1.Button1Click loop, hence it can never be called after you entered that loop. Since you never exit the TForm1.Button1Click procedure you do not allow any outside agent (like messages dispatch loop in VCL) to get executed and call that procedure either. To sum it up as soon as you entered the loop there is no any executable code that can change IsDone value. So, it is not changed.

Event handlers are supposed to be very short procedures, executing almost immediately, and giving up "execution flow control" back to VCL internals. Every long (more so infinite) processing leads to the program becomes irresponsive. No matter how many news Windows might want to tell the program - the program never asks for them.

  • https://en.wikipedia.org/wiki/Event-driven_programming
  • https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx
  • https://msdn.microsoft.com/en-us/library/windows/desktop/ms632593.aspx

It was once told that Windows windows (GDI objects) are living in the center of the "messages storm" that they have to work out in timely matter. Hundreds of those messages are incoming every second and a Window Procedure (built inside the VCL classes for Delphi 7 forms) should receive, dispatch, and process every one of them before it's too late.

As soon as you blocked that process by making one of event handlers long or even endless - you broke the basic contract between the OS and the application.

You have to do "inversion of control", to break your continuous work into small short chunks and make Windows call those chunks when it sees appropriate. Try to use TTimer for example.

PS. A VERY remote problem you can look at:

  • How to use Pipeline pattern in Delphi
  • How to Stop all Pipeline tasks correctly

Skip all the multithreading stuff there, for your case it only is important that other threads create those "chunks of work" that we have to paint onto our forms when Windows asks us to do so at some reasonable framerate (not too fast and not too slow). Your work chunks are fundamentally different, so all the threading stuff unrelated to you.

And the rendering is made inside TTimer events. So the "framework" of setting up the timer, turning it on and off might be of some interest to you. However the work you are going to do inside the .OnTimer event would be significantly different (just painting something, or even merely invalidating some part of the form and waiting for the Windows to trigger OnPaint event.).

nil
3#
nil Reply to 2017-11-10 14:21:41Z

You already got an excellent answer why your current code does not work and in your comments you are mentioning you want to do ray casting and drawing from a players perspective, so I assume some kind of game background.

I'm not sure the VCL is the best basis for a game. Different philosophies and needs. As Arioch 'The explained Delphi's VCL is event driven. Things happen in response to windows messages, even painting. If nothing causes a need to repaint, nothing will be painted anew.

This is very different from how I understand game engines (I'm by no means an expert). Even if nothing happens, they will continuously draw frame after frame to present as fluid as possible. Each frame might include an update to underlying structures based on game rules, physics, player input, animation, but even when they remain the same a new frame will be drawn. Basically three steps happen in a simplified 'game loop'

  • Input
  • Update
  • Presentation

All this happens for every frame. There might be no input, no update of the game's structures or even no presentation is desired. But all three steps belong together, the input causing an update that is later presented happened in the exact same frame as the resulting drawing.

This is something I find hard to fit into the VCL. As a solution must be based on the existing VCL loop and windows messages. You basically attempted to create such a game loop in VCL.

A way to solve your immediate issue - that you want to present something based on a calculation - could be just using the principle of the VCL. You want to have something drawn. VCL controls normally communicate their desire to be drawn by Invalidate, causing their BoundsRect to be invalidated. You could do that after you have done your calculations. In the following example I'll just use a timer to simulate your calculations are done. Just be aware that Invalidate will cause WM_PAINT messages to be generated for the control, but will not cause immediate repainting. There might be messages queued before the WM_PAINT is processed. I'm using a TPaintBox's OnPaint to actually do the painting work, you might want to have your own control for that in the future when your project progresses.

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

type
  TFormMain = class(TForm)

    procedure FormCreate(Sender: TObject);
  private
    Timer1: TTimer;
    PaintBox1: TPaintBox;
    { Private declarations }
    procedure PaintBox1Paint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

procedure TFormMain.FormCreate(Sender: TObject);
begin
  PaintBox1 := TPaintBox.Create(Self);
  PaintBox1.Parent := Self;
  PaintBox1.Align := alClient;
  PaintBox1.OnPaint := PaintBox1Paint;

  Timer1 := TTimer.Create(Self);
  Timer1.Interval := 100;
  Timer1.OnTimer := Timer1Timer;
  Randomize;
end;

procedure TFormMain.PaintBox1Paint(Sender: TObject);
var
  AColor: TColor;
  I: Integer;
begin
  for I := 0 to PaintBox1.ClientWidth - 1 do
  begin
    AColor := RGB(Random(256), Random(256), Random(256));
    PaintBox1.Canvas.Pen.Color := AColor;
    PaintBox1.Canvas.MoveTo(I, 0);
    PaintBox1.Canvas.LineTo(I, PaintBox1.ClientHeight);
  end;
end;

procedure TFormMain.Timer1Timer(Sender: TObject);
begin
  PaintBox1.Invalidate;
end;

end.
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.304165 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO