Link to home
Create AccountLog in
Avatar of passpics
passpics

asked on

Time critical app stops when any window is moved...

I am writing an application that needs an accurate and continous clock 'pulse' every 40mS or less: basically it's sort of like a MIDI music sequencer, but for another purpose. Everything works fine - I've done a custom component that fires off a NotifyEvent at nice, regular intervals, and I can display that as a time. Here's the beef of my timer component:

procedure TTimecode.RunClock;
var
     Interval : single;
     fq, ct : int64;
     MIDICount : integer;
begin
     Interval := FRameRate / 1000;
     MIDICount := 0;
     while Enabled do    //Keep looping
     begin
          QueryPerformanceFrequency(fq);     //Get the frequency
          Repeat
               QueryPerformanceCounter(ct);  //Get the current count
          until (ct - OldCounter) / fq >= Interval;  //Time in miliseconds since last loop
          OldCounter := ct;        //Keep this for next time around
          If assigned(OnMIDIFrame) then OnMIDIFrame(self);
          inc(MIDICount);
          If MIDICount = 5 then MIDICount := 1;
          If MIDICount = 1 then
             If assigned(OnTCFrame) then OnTCFrame(self);
          sleep(1);                //Reduces processor load
     end;
end;

In my main app code, this is used as follows:

procedure TfrmMain.MasterClockTCFrame(Sender: TObject);  //Called once every full frame
var
     n : integer;
begin
     //Update clocks for any timelines running on internal sync
     For n := 0 to length(TL) - 1 do
          If TL[n].Sync = syInt then TL[n].Time := TL[n].Time + mspf;
     frmTimeline.UpdateClock;   //Update the timeline display
end;

TL is a dynamically created array of records, with Time as a field. UpdateClock is a little procedure that simply shows the time in an edit control. mspf is the time to increment in miliseconds for each event (each item in the TL array can have a different time).

Now here's the problem: if I click on any child window's caption bar, the clock stops. I don't mean the form's paint does not update, I mean it's like the whole thread stops, until I release the mouse. TL[x].Time is not updated until I release the mouse. My app is time critical, so this is clearly not good! I tried setting my apps thread priority to REALTIME_PRIORITY_CLASS, but that did not help. Any ideas? I need the clock to always keep counting, even if the user moves a window around.

Avatar of flasht
flasht

You need to do everything in separated thread... everything stops because moving a form pauses vcl thread...
Avatar of passpics

ASKER

Thanks for the quick response.

And there's no way to prevent that?  I'm going to have a lot of interaction between the UI and the timed event processing, so putting all my timing / processing stuff in another thread will be a bit of a pain. I don't care if moving a window becomes jerky or otherwise not pretty - UI is less important than timing.
ASKER CERTIFIED SOLUTION
Avatar of flasht
flasht

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Ummm, OK. Seems sort of crazy. I wonder why it does this?

I Can't use the MM Timer, because I need more accuracy (like 33.333mS), and  QueryPerformanceCounter is more accurate - it measures in nanoseconds. Although of course this is all dependent of how accurate the actual processor clock / Windows is....

So I guess I'm into a whole new game with thread communication then. Expect more questions soon - I'm new to this sort of thing!

Anyway, thanks again. If I hear no other responses in the next few days that change my mind, you get the points.
OK, I've written the thread code, and that fixed the problem. Thanks again, and I'm awarding the points to you. As I say I'm new to threads, so does the following look OK to you?

type
  TMainClock = class(TThread)
  private
    { Private declarations }
     OldCounter : single;
     Interval : single;
    procedure SetInterval;
    procedure postmainmessage(msg:shortstring);
  protected
    procedure Execute; override;
...
procedure TMainClock.Execute;
var
     fq, ct : int64;
     MIDICount : integer;
begin
     {
     We basically loop around, checking on the highest performance clock
     available in the system, until we are >= Interval (= MIDIInterval in nanoseconds)
     }
     FreeOnTerminate := true;    //Frees thread when terminated.
     Synchronize(SetInterval);   //In case it's being used by someone else
     MIDICount := 0;
     while not(Terminated) do    //Keep looping until the thread gets terminated
     begin
          QueryPerformanceFrequency(fq);     //Get the frequency
          Repeat
               QueryPerformanceCounter(ct);  //Get the current count
          until (ct - OldCounter) / fq >= Interval;  //Time in miliseconds since last loop
          OldCounter := ct;        //Keep this for next time around
          postmainmessage('M');    //Send a MIDI type message to the main thread
          inc(MIDICount);          //Every 4th loop, we send a Frame message
          If MIDICount = 5 then MIDICount := 1;
          If MIDICount = 1 then postmainmessage('F'); //Send a Frame type message to main thread
          sleep(1);     //Reduces processor load
     end;
end;

procedure TMainClock.postmainmessage(msg:shortstring);   //Sends a message to the main thread
var msgstrptr:PShortstring;
begin
     new(msgstrptr);
     msgstrptr^:=msg;
     postmessage(frmMain.Handle,MyMsg,integer(msgstrptr),0);
end;

procedure TMainClock.SetInterval;
begin
     Interval := MIDIInterval / 1000;
end;
Almost... you should use synchronize when reffering to main thread (form1)... For this Message should be the global variable of thread and PostMainMessage shouldn't then take any arguments (it will take M from global variable). Then just replace postmainmessage('M'); to Synchronize(PostMainMessage);
That will avoid doing 2 things at once by main thread and unexpected behaviour or even crashes.
Oh well... maybe i'm not really right, the code seems to be ok since postmessage doesn't affect main thread directly, so leave it like this and it should be good.