Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "WinAPI";
Текущий архив: 2004.11.21;
Скачать: [xml.tar.bz2];

Вниз

Таймер   Найти похожие ветки 

 
Sphinx   (2004-10-09 23:09) [0]

Вопрос собственно, как сделать таймер с гарантийе срабатывания через заданный промежуток времени, плюс НЕБОЛЬШОЕ отставание.

TTimer не подходит, по некоторым данным дискредность его выполнения в Win9x - 55 ms Win2k/XP - 10 ms, то есть разная для разных систем.

Мультимедийный таймер, вычитано в книге, из библиотеки winmm.dll то же не подходит, во-первых под Win2k на работе такой библиотеки не оказалось (?!!!) а во-вторых встретился с проблемой, о которой чуть ниже.

SetTimer, KillTimer - нашел в Windows SDK - работают лучше, но та же проблема. События таймера не всегда выполняются!

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

Пытался сделать еще и отдельным потоком, примерно так:
TTimerThread.Execute;
begin
 while ThrdRun do
   begin
     if GetTicCount-LastTicCount>Delay then
       begin
         PostMessage(TimerWinHandle, WM_USEr_TIMER);
         LastTicCount:=GetTicCount;
       end;
     Sleep(Descrite;);
   end;
end;
while
писал тут, возможны ошибки, но суть такая была. Работало все хорошо, если бы не одно НО. Тормозило до жути.

Подскажите как ПРАВИЛЬНО организовать таймер, события которого не пропускались бы при прочих сообщениях окну.


 
DiamondShark ©   (2004-10-10 12:20) [1]

Попробуйте рассказать, зачем понадобился такой таймер.
Может найдём решение не "в лоб".


 
Nick Denry ©   (2004-10-10 12:44) [2]

DiamondShark ©   (10.10.04 12:20) [1]

- Хотите об этом поговорить? (с) реклама

:)))


 
Sphinx   (2004-10-10 17:08) [3]

Все очень просто - игрушка :)

нужно пересчитывать движение юнитов через равные промежутки времени, а так как при интенсивный движениях мышью события таймера не выполняются - то получается что юнит двигается рывками. Если мышь не трогать - все плавно.

Или я что-то непонял...или мне нужен какой-то хитрый таймер :)


 
Nick Denry ©   (2004-10-10 19:41) [4]

ДЛя начала, насколько я понял, надо использовать DirectInput, - тогда ни какие движения мышью не будут столь "интенсивными".

Далее, самый быстрый таймер в виндовс - мультимедийный татаймер...

uses ...,MMsystem
....
var
TimerID: uint;
....
///Процедура обрабатывающая тик таймера:
procedure TimerProc(uTimerId, uMessage, UNIT; dwUser,dw1,dw2, : DWORD); stdcall;
begin
//Здесь ты осуществляешь все что тебе нужно...
end;

//Запуск таймера:
TimerID := timeSetEvent(2,0,@TimeProc,0,TIME_PERIODIC);
// здесь перввый параметр не стоит приравнивать к 0...

//Завенршение таймера...
timeKillEvent(TimerID);
...


а вообще, я посоветовал бы тебе использовать Application.Idle или он же цикл обработки сообщений:

вот пример цикла обработки:

...
var  
TEst_RUN         : Boolean = false;
....

While  TEst_RUN do
begin
 if  PeekMessage(Mesg,MAinWnd,0,0,PM_REMOVE) then begin
                                 TranslateMessage(Mesg);
                                 DispatchMessage(Mesg);
                                 end;
 if FActive then
   begin
   Render; //В этой процедуре должен находится весь код воспрооизведения и рассчетов...
   ReadImmediateData; //Это напрямую относится к DirectInput
   {Inc(Frames);
   ThisTickCount := GetTickCount;
      if ThisTickCount - LastTickCount > 1 then
               begin
               FPSS := "FPS = " + IntToStr(trunc(Frames*1000/(ThisTickCount - LastTickCount)));
               SetWindowText(MainWnd,PChar(FPSS));
               Frames := 0;
               LastTickCount := GetTickCount;
               End;}
   end;
end;


Код конечно не "оптимайз", но хоть что - то, если используешь VCL, то вышеприведенный цикл тебе, как я уже сказал, заменит Application.Idle

procedure TfrmD3D.ApplicationEvents1Idle(Sender: TObject;
 var Done: Boolean);
var
 hRet : HRESULT;
begin
 if FActive then begin
    hRet := Render;
    if FAILED(hRet) then begin
       FActive := False;
       ErrorOut ("Render", hRet);
       Exit;
    end;

    ThisTickCount := GetTickCount;
    if ThisTickCount - LastTickCount > 30 then begin

       Angle := Angle + 0.05;
       if Angle > 2 * Pi then Angle := Angle - 2 * Pi;

       LastTickCount := GetTickCount;
    end;
 end;

 Done := False;
end;


Рекомендуемая литература:

1. М. Краснов. OpenGL графика в проектах Delphi, BHV, 2004
2. М. Краснов. DirectX графика в проектах Delphi, BHV, 2004 - оптимайзена под игры...

Если еще чего - спрашивай..


 
VMcL ©   (2004-10-10 20:14) [5]

>>Sphinx  (10.10.04 17:08) [3]

Еще не прочитав [3], я уже догадался. В играх такие вещи делаются не через таймер.

В, так сказать, "нормальных" играх кадры отрисовываются постоянно (например, в Application.OnIdle), а позиции объектов рассчитываются исходя из промежутка времени, который прошел с момента начала игры или отрисовки предыдущего кадра. Этот промежуток определяется посредством функции QueryPerformanceCounter() или какой-либо другой, дающей достаточно точное значение.


 
Sphinx   (2004-10-10 20:50) [6]

> ДЛя начала, насколько я понял, надо использовать DirectInput,

Он и используется.

> Далее, самый быстрый таймер в виндовс - мультимедийный татаймер...

прочтите внимательно первый пост, там я про него писал.

> 2. М. Краснов. DirectX графика в проектах Delphi, BHV, 2004

с ней и разбираюсь :) только издание 2002 года

> если используешь VCL

используется WinAPI.

> В, так сказать, "нормальных" играх кадры отрисовываются
> постоянно

читайте внимательно! цитирую самого себя:
> нужно пересчитывать движение юнитов через равные промежутки
> времени

перерисовка экрана и так происходит в реальном времени.


 
Nick Denry ©   (2004-10-10 20:50) [7]

2>VMcL ©   (10.10.04 20:14) [5]

В, так сказать, "нормальных" играх


ИМХО, точнее сказать: в сложных и больших играх. Опять таки ИМХО, для мальнькой игры без разницы.

Для большой еще можно сказать, должно быть исключено использование VCL, применяться асеемблерные вставки оптимизация и др. А новичкам, для начала надо следить за использованием того же DirectInput"a....


 
Nick Denry ©   (2004-10-10 21:06) [8]

2Sphinx   (10.10.04 20:50) [6]

Сорри, что читал невнимательно, но тогда тебе дествительно надо делать как говорит VMcL ©   (10.10.04 20:14) [5]. Т.е. код воспроизведения должен быть размещен в цикле обработки сообщений, как показанно у меня, и рассчет и отрисовка должны проводиться в функции render (предлагается не конкретное решение, а сам принцип).

нужно пересчитывать движение юнитов через равные промежутки времени

Что ты под э
тим подразумеваешь? Если смену кадров спрайтов, то тебе на стр. 173, там  все подробно написанно, если движение кадров, то см. дальше...


 
Sphinx   (2004-10-10 21:08) [9]

Видно без кода не обойтись.

за коментарии не пинать, писал для себя :)

основной цыкл:
 while appRun do
   begin
     GetMessage(aMSG, 0, 0, 0);
     TranslateMessage(aMsg);
     DispatchMessage(aMsg);
     DrawScreen;
   end;

процедура перерисовки:
procedure DrawScreen;
begin
 case ScreenCount of
   1: begin
        if appActive then
          begin
            DrawTitleSelect;
            if ReDrawTitle=DD_OK then
              FlipSurf;
          end;
      end;
   2: begin
      if appActive then
        begin
          (* скроллировани экрана *)
          if mouseY<5 then
            begin
              if GroundY>0 then
                GroundY:=GroundY-2;
            end;
          if mouseY>(SCREEN_HEIGHT-40) then
            begin
              if GroundY<(GameFlur.Height-screen_height) then
                GroundY:=GroundY+2;
            end;
          if mouseX<5 then
            begin
              if GroundX>0 then
                GroundX:=GroundX-2;
            end;
          if mouseX>(SCREEN_WIDTH-40) then
            begin
              if GroundX<(GameFlur.Width-screen_width) then
                GroundX:=GroundX+2;
            end;
               if ReDrawGameScreen=DD_OK then
                 FlipSurf;
               end;
        end;
   3: begin
      if appActive then
        begin
          if ReDrawInfoScreen=DD_OK then
            FlipSurf;
          end;
        end;
      end;
     KeyboardReadData;  // DInput
     MouseReadData;     // DInput
     Inc(FPSCount);
     ThisTickCount:=GetTickCount;
     if ThisTickCount-LastTickCount > 600 then
       begin
         FPS:=FPSCount;
         FPSCount:=0;
         LastTickCount:=GetTickCount;
       end;
end;

инициализация таймера:
 uTimerID:=SetTimer(mainHandle, uEventID+1, AniSpeed, @ProcTime);

 (* Процедура обратного вызова таймера.

 *** Параметры ***
  *)
procedure ProcTime(WHandle: HWND; wMesg, TimerID: UINT; dwTime: DWORD); stdcall;
begin
 if appActive then
   if TimerID=uTimerID then
     if ScreenCount=2 then
       Hero.DoMove;
end;

(* Обработка сообщений, поступивших приложению
  так как используется WinAPI то обработка выполняется
  самостоятельно.

******** Входные величины ********
iHandle:  указатель на окно приложения
aMSG:     собственно сообщение
wParam:   первый параметр сообщения
lParam:   второй параметр сообщения *)

function WindowProc(iHandle: THandle; aMSG: Cardinal;
 wParam: Cardinal; lParam: Integer): Integer; stdcall;
begin
(* определение посленного сообщения *)
 case aMSG of
   // активация\деактивация окна приложения
   WM_ACTIVATE:  begin
                   // приложение не активно
                   if LOWORD(wParam)=WA_INACTIVE
                   then
                     Activate(False)
                   // иначе приложение активно
                   else
                     Activate(True);
                 end;
   // скрыть курсор мыши в полноэкранном режиме
   WM_SETCURSOR: begin
                   SetCursor(0);
                   Result:=1;
                   Exit;
                 end;
   // закрываем приложение
   WM_QUIT:      begin
                   appRun:=False;
                   Result:=0;
                   Exit;
                 end;
   // приложение уничтожается
   WM_DESTROY:   begin
                   KillTimer(mainHandle, uEventID);
                   KillTimer(mainHandle, uEventID+1);
                   CloseFile(LogFile);
                   DDFreeObj;
                   IniFile.Free;
                   PostQuitMessage(0);
                   Result:=0;
                   Exit;
                 end;
 end;
 // передаем сообщение следующему приложению
 Result := DefWindowProc(iHandle, aMSG, wParam, lParam);
end;

так вот...если таймер не задавать, карта не скроллируется если не двигать мышкой! Если задан таймер, то карта скролируется, когда мышка подведена к краю, но если ей при этом еще и подвигать - герой замирает.


 
DiamondShark ©   (2004-10-10 21:15) [10]


> Sphinx   (10.10.04 20:50) [6]
> > Далее, самый быстрый таймер в виндовс - мультимедийный
> татаймер...
> прочтите внимательно первый пост, там я про него писал.

Значит у тебя что-то нехорошее с виндой. Эта библиотека должна быть. Что-то её некорректно снесло.


> читайте внимательно! цитирую самого себя:
> > нужно пересчитывать движение юнитов через равные промежутки
> > времени

Ну так это не правильная постановка. В такой постановке задача в рамках не реал-тайм ОС нерешаема. Расчитывать надо не через равные промежутки времени, а как функцию от прошедшего времени.
Другое дело, что брать в качестве источника времени: мультимедиа таймер, пефоманс каунтер или системный таймер.
В твоём случае подойдёт даже GetTickCount, вполне достаточная для игрушки точность.


 
Sphinx   (2004-10-10 21:21) [11]

> DiamondShark ©   (10.10.04 21:15) [10]

А я и не требую абсолютной точности мне бы от "замораживания" избавиться.

> Значит у тебя что-то нехорошее с виндой. Эта библиотека
> должна быть. Что-то её некорректно снесло.

На компьютере много народу бывает, он не мой лично, поэтому может быть, не спорю :)


 
Erazzer   (2004-10-10 21:44) [12]

Забудь про сообщения.
В новом потоке сделай следующее
//в методе execute

while no terminated do
begin
 sleep(10);
 synchronize(твоя процедура);
end;


 
DiamondShark ©   (2004-10-10 22:14) [13]


> так вот...если таймер не задавать, карта не скроллируется
> если не двигать мышкой!

Потому что у тебя GetMessage, которая не возвращается, пока нет сообщений в очереди.


 
Nick Denry ©   (2004-10-10 22:54) [14]

2DiamondShark ©   (10.10.04 22:14) [13]

Таким образом код возвращается к PeekMessage, о котором я говорил ранее..

Кстати, в нем же о GetTickCount, здесь рассчитывается колчество FPS за 1 секунду. Аналогично можно рассчитывать что угодно, за любой промежуток времени.

Я прав?

...
var  
TEst_RUN         : Boolean = false;
....

While  TEst_RUN do
begin
if  PeekMessage(Mesg,MAinWnd,0,0,PM_REMOVE) then begin
                                TranslateMessage(Mesg);
                                DispatchMessage(Mesg);
                                end;
if FActive then
  begin
  Render; //В этой процедуре должен находится весь код воспрооизведения и рассчетов...
  ReadImmediateData; //Это напрямую относится к DirectInput
  {Inc(Frames);
  ThisTickCount := GetTickCount;
     if ThisTickCount - LastTickCount > 1 then
              begin
              FPSS := "FPS = " + IntToStr(trunc(Frames*1000/(ThisTickCount - LastTickCount)));
              SetWindowText(MainWnd,PChar(FPSS));
              Frames := 0;
              LastTickCount := GetTickCount;
              End;}
  end;
end;


Далее по коду появилось несколько вопросов.

1. Зачем нужен exit в каждом сообщении WM_ ???? Сколько писал, писал только там, где явно необходимо было возвращать значения.. Это не правильно?

2. В "процедуре перерисвоки", если не ошибаюсь можно сначала проверить условие if appActive, а затем весь остальной код, тем самым уменьшить время, отводимое на эти проерки...

3. Объясните мне плиз, что хотел сказать [12]...


 
Sphinx   (2004-10-11 00:14) [15]

> Я прав?

Да :) Просто я хотел сделать изменяющуюся скорость игры, ну и
еще пару вещей, а у того же Краснова, к примеру, говориться что это лучше делать это через таймер.

> Зачем нужен exit в каждом сообщении WM_ ????

Не в каждом, может я не прав, но в WM_EXIT и WM_DESTROY должно вернуться "0". Из хелпа: If an application processes this message, it should return zero.

> В "процедуре перерисвоки", если не ошибаюсь можно сначала
> проверить условие if appActive,

Будет сделано :) просто оптимизацией еще не занимался, дописывая отдельные ветви, по мере из появления.

> 3. Объясните мне плиз, что хотел сказать [12]...

Не знаю...все равно результат один будет...


 
Nick Denry ©   (2004-10-11 00:46) [16]

Да :) Просто я хотел сделать изменяющуюся скорость игры

ИМХО, лучше это сделать используя счетчик шагов/движений персоонажа или спрайтов. Т.е. воспроизводится все непрерывно, а меняется значение переменной, отвечающей за сдвиг объектов. Ты наверное Direct3D не смотрел еще, краснов там так поступает в большинстве случаев...

Не в каждом, может я не прав, но в WM_EXIT и WM_DESTROY должно вернуться "0". Из хелпа: If an application processes this message, it should return zero.

Про справку -то оно верно, но в по опыту погу сказать дельфи сама подставляет в откомпилированный код необходимые значения. Использовать result:=Что-то и exit; мне приходилось только в сообщениях WM_CTLCOLORSTATIC и им подобных, т.е. там где

Return Values

(для WM_CTLCOLORSTATIC: If an application processes this message, the return value is the handle of a brush that Windows uses to paint the background of the static control. ) как раз отличны от нуля. Т.е. 0 можно не подставлять, по крайней мере проблем с этим не было...

Еще про подстановку кода дельфей в исполняемый код: поверить в это трудно :), но это факт. Если помнишь Краснов пишет о "неизвестной ошибке", появляющейся при попытке вызова метода _Release любого интерфейса. объяснить это он отказывается, ссылаясь на неоотлаженность заголовков DirectX для Delphi. Так вот, дело в том, что при инициализации каждого конкретного интерфейса Дельфи сама добавляет код его освобождения при завершении программы.


> В "процедуре перерисвоки", если не ошибаюсь можно сначала
> проверить условие if appActive,

Будет сделано :) просто оптимизацией еще не занимался, дописывая отдельные ветви, по мере из появления.


Ты знаешь, одним из главных советов по разработке больших программ на любом языке является рекомендация оптимизировать все после написания основных функции программы и ее отладки. Так что советую тебе просто пока запомнить/записать это, потом доработаешь. Иначе можно очень крепко запутаться....

Вот, советую почитать, http://www.wasm.ru/article.php?article=biprjasm , мне в свое время очень помогло...


 
XProger ©   (2004-10-11 01:48) [17]


function GetTimer: integer;
var
T, F : LARGE_INTEGER;
begin
QueryPerformanceFrequency(int64(F));
QueryPerformanceCounter(int64(T));
Result := trunc(1000 * T.QuadPart/F.QuadPart);
end;
...
Time_Old := GetTimer;
while not Engine_isQuit do
begin
while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
 begin
 TranslateMessage(msg);
 DispatchMessage(msg);
 end;

Time       := GetTimer;
Time_Delta := Time - Time_Old;

flag := false;
// Engine_GetUPS - кол-во обновлений игры в секунду
for i := 1 to Time_Delta div (1000 div Engine_GetUPS) do
 begin
 Engine_Update; //Обновление
 flag := true;
 end;

if flag = true then
 Time_Old := Time - (Time_Delta mod (1000 div Engine_GetUPS));
Engine_Draw;
end;


 
SammIk ©   (2004-10-11 04:48) [18]

Я тут где-то писал уже, про получение ко-ва тиков процессора
со времени включения компа.
Точность опупительная, и зависит лишь от частоты процессора.
Чем выше, тем более короткии промежуток можно отследить.
Работать будет всегда, но реализовывать придется самому.
Я лишь написал как можно реализовать его, с кодом на MASM.
Там реализован только счетчик тиков, а таимер сам делаи и в секунды сам переводи.


 
Nick Denry ©   (2004-10-11 10:47) [19]

2XProger ©   (11.10.04 01:48) [17]

Отлично, надо взять на заметку...



Страницы: 1 вся ветка

Форум: "WinAPI";
Текущий архив: 2004.11.21;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.55 MB
Время: 0.301 c
1-1099640917
Maxim____
2004-11-05 10:48
2004.11.21
tthread замучил


11-1082730937
Max003
2004-04-23 18:35
2004.11.21
Как сделать форму, перетаскивуемую за любое место.


6-1095185263
-MS-
2004-09-14 22:07
2004.11.21
Delphi+CGI+ServerSocket


1-1099554968
star.ru
2004-11-04 10:56
2004.11.21
типа Graphics32


1-1100108231
JustmE
2004-11-10 20:37
2004.11.21
DWORD/WORD -> STRING





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский