
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.191.181.186] |
![]() |
|
Сообщ.
#1
,
|
|
|
Пишу игру на Паскале, все главные процедуры готовы. Но возникла проблема: не получается сделать задержку между ними, она должна быть разной для каждой процедуры. Так как процедуры довольно массивные получается такая ситуация: всё идёт гладко, пока (поcредством нескольких if`ов) в основном цикле не появляются дополнительные циклы (из этих самых, массивных, процедур). Delay не помогает; такой механизм как:
![]() ![]() ... Counter := 0; Repeat If Counter Mod 100 = 0 Then {выполняется одна процедура} If Counter Mod 200 = 0 Then {выполняется другая процедура} ... Inc(Counter); If Counter > 65534 Then Counter := 0; Until Key = #27; ... тоже не даёт нужного результата. За этим и обращаюсь в этот форум. |
![]() |
Сообщ.
#2
,
|
|
Идея про задержки не ясна
![]() |
Сообщ.
#3
,
|
|
|
Постоянную малую задержку можно получить, считывая значение таймера, т.е. тебе вместо твоего каунтера нужно считать таймер.
|
Сообщ.
#4
,
|
|
|
Попробуй взглянуть на проблему с другой стороны:
1. Если ты знаешь ООП, то в твоей игре все спрайты (и свои, и враги) описаны как объекты. 2. Каждый объект вызывает функцию Idle (которая делает 1 шаг за единицу времени) 3. Для всех объектов (они в массиве) функция Idle вызывается только 1 раз (или определённое количество раз) Тогда делаешь вложенный цикл. Внутренний проверяет количество шагов за единицу времени, внешний - делает собственно задержку. Примерно так: ![]() ![]() repeat TimeCount:=GetTime; for i:=1 to SpritesCount do repeat Sprites[i].Idle; until Sprites[i].EndStep; until TimeCount+1>=GetTime; Функция EndStep должна возвращать True, если количество вызовов Idle для данного юнита за этот ход уже исчерпано. (Типа, скорость - если, конечно, у каждого юнита своя скорость) |
Сообщ.
#5
,
|
|
|
"В общем, перепиши-ка ты игру заново..." ;D А если серьёзно и по существу,
?MemL[Seg0040: $6C] возвращает количество тиков системного таймера и увеличивается на единицу каждые 55 миллисекунд. ?Можно сделать задержку по int 15h (ah=86h, cx:dx-задержка в микросекундах), но под NT это не работает. ?Для Pentium и старше есть команда rdtsc, которая в eax:edx возвращает кол-во тиков тактового генератора с момента пуска (т.е. нужна калибровка). ?Delay использовать нельзя (по крайней мере стандартный из модуля CRT) -- вроде все уже знают, почему ;) |
Сообщ.
#6
,
|
|
|
Цитата sector, 20.08.03, 19:57:02 MemL[Seg0040: $6C] возвращает количество тиков системного таймера и увеличивается на единицу каждые 55 миллисекунд. Т.е. 18.2 раза в секунду. Используя это можно сделать 18 кадров в секунду (сделать задержку которая ждет изменения числа тиков). Этот метод подойдет, если все процедуры рисования достаточно быстры. Кстати, в SWAG-ах есть примеры разгона таймера |
Сообщ.
#7
,
|
|
|
Разгон таймера - вещь, но под 98 винды и выше пахать не будет. Кроме того, нужно будет вешаться на обработчик 08-го прерывания, чтобы системное время не ушло вперед. Я не думаю, что это именно то, что автору топика нужно.
|
Сообщ.
#8
,
|
|
|
“Мысли о Паскале”
Автор Демьянишин Владислав (E-mail: nitromanit@mail.ru www: http://amonit.boom.ru) Снятие временных характеристик программ Бывает чрезвычайно полезно провести оценку сравнительного быстродействия частей программы. Это может иметь большое значение для достижения приемлемой производительности программы и выявления неоптимальных участков кода. Таким образом, программисту необходим инструмент для измерения интервала времени, затрачиваемого на выполнение некоторой задачи определённым участком кода составляемой программы. Первый метод измерения интервалов времени основан на чтении счётчика системного таймера, находящегося в области данных BIOS по адресу $0040:$006C и занимающего 4 байта памяти. Этот счётчик изменяет свои показания каждую 1/18.2 секунды, увеличиваясь на единицу. Необходимо описать переменную, расположенную в памяти по известному адресу ![]() ![]() var SystemTimer : longint absolute $40:$6c; Чтобы не сбить показания системных часов, не следует записывать в эту переменную что-либо. Нам лишь следует из неё читать показания системного таймера, и для этого опишем процедуру чтения этой переменной. ![]() ![]() function ReadTimer : longint; begin ReadTimer := SystemTimer; end; А вот пример использования этой процедуры: ![]() ![]() var start, finish, j : longint; begin start := ReadTimer; {процесс, исследуемый на производительность} for j := 0 to … do begin … end; finish := ReadTimer; writeln('Время : ',(finish-start)/18.2:5:2,' сек.'); end. Главное достоинство такого способа замера времени есть его простота, а его недостаток состоит в том, что точность замера интервалов времени ограничена 1/18.2 секунды, т.е. около 55 мсек. Таким образом, если исследуемый процесс выполнится быстрее, чем за 55 мсек, то получим нулевой интервал времени. Чтобы избежать этого, можно задать цикл for с небольшим количеством повторений исследуемого процесса, чтобы выявить ничтожно малую величину времени, затраченного на выполнение данного процесса. При использовании этого способа переменной типа longint может хватить на измерение интервала продолжительностью до 32776 часов, а это около 4 лет. У кого хватит терпения ;O) Если возникает необходимость осуществить паузу, то первое, что приходит на ум, это функция delay модуля CRT.PAS, которая осуществляет задержку выполнения программы на заданное количество миллисекунд. Но каково было моё удивление, когда я смог добиться полусекундной задержки строкой delay(50000), хотя с таким параметром должна была получиться пауза в 50 секунд. А всё потому, что на процессорах, начиная с Celeron, код этой функции работает не так как на старых процессорах. Поэтому, хочу предложить универсальный способ осуществления задержки, который будет работать независимо от процессора x86. Вот код необходимой процедуры задержки: ![]() ![]() procedure Pause( p : longint ); var T : longint; begin T := ReadTimer + p; repeat until T <= ReadTimer; end; При её применении паузу следует задавать в 18-х долях секунды, т.е. Pause(1) – 55 мсек, Pause(18) – одна секунда, Pause(1092) – одна минута. Второй метод заключается в чтении счётчика канала №0 микросхемы системного таймера, который изменяется с частотой 1193180 Гц (т.е. 1193180 раз в секунду) и позволяет добиться точности в 0.838 мксек. Это реализуется простой функцией: ![]() ![]() function ReadTimerChipCount : word; var frec : word; begin frec := port[$40]; frec := frec or (port[$40] shl 8); ReadTimerChipCount := frec; end; т.е. читаем из порта $40 сначала младший байт, а затем старший байт двухбайтного счётчика. Но полученное таким образом значение непригодно для использования без дополнительной обработки так, как этот счётчик непрерывно уменьшается на единицу, и варьируется в пределах 0..65535 из-за того, что BIOS при загрузке компьютера устанавливает коэффициент пересчёта счётчика (регистр задвижки) данного канала в 65535. А нам необходимо нарастающее число. Следовательно, чтобы получить нарастающее число, нужно использовать выражение 65535-ReadTimerChipCount. Помимо этого необходимо ещё к полученному значению добавить количество 1/18.2 долей секунды, умноженных на коэффициент пересчёта, чтобы получить правильное значение времени. Вот функция, обеспечивающая всё необходимое: ![]() ![]() function ReadOscelator : longint; begin ReadOscelator := ((ReadTimer and $7fff)*$10000) or (65535-ReadTimerChipCount); end; Бесспорным достоинством этого метода является его высокая точность. А недостаток заключается в том, что переменной типа longint для хранения измеренного интервала времени может хватить на 30 минут. Хотя на практике приходится замерять интервалы времени, исчисляемые несколькими секундами. Хочу снова вернуться к проблеме, связанной со стандартной функцией delay. А что, если попытаться создать аналог этой функции. Такую процедуру можно назвать этим же именем, но чтобы при использовании модуля CRT.PAS не возникало проблем, назовём её так: ![]() ![]() procedure NewDelay( ms : word ); const k = 1193180/1000; var T : longint; begin T := ReadOscelator + trunc( ms*k ); repeat until T <= ReadOscelator; end; Константа k содержит число тактов микросхемы системного таймера, проходящих за одну миллисекунду. Затем этот коэффициент умножаем на количество заданных миллисекунд, и добавляем результат к общему числу прошедших тактов микросхемы системного таймера, с тем, чтобы потом ожидать нужного нам такта. Таким образом, формируется задержка в заданное количество миллисекунд. Определение частоты центрального процессора Иногда возникает необходимость определить частоту процессора. В числе машинных команд имеется команда RDTSC, которая возвращает в 32-разрядных регистрах EDX:EAX количество тактов процессора, произошедших с момента его сброса. Счётчик тактов процессора является 64-разрядным и его может хватить на 585 лет при частоте CPU 1 ГГц. При включении (сбросе) процессора счётчик тактов обнуляется. Чтобы из этого счётчика вычислить частоту процессора в МГц надо измерить несколько интервалов времени, например по системному таймеру (длительностью 1/18.2 c) и получить среднюю длительность в тактах процессора. Затем умножить эту величину на 18.2 (лучше умножить на 1193180 - частота таймера в Гц и разделить на 65536 - коэффициент пересчёта микросхемы таймера и тогда получим более точное умножение на 18.2). Результат нужно разделить на 1000000, чтобы из Гц получить МГц. Доступ к команде RDTSC контролируется флагом TSD в управляющем регистре CR4 процессора (если флаг сброшен, команда выполняется при любом уровне привилегий выполняемой программы, а если установлен - то только при уровне привилегий 0). Как показала практика, в задаче MS-DOS под Windows 98 такой метод работает нормально. Он так же работает и в реальном режиме центрального процессора, т.е если загрузить машину не Windows, а обычным MS-DOS (command prompt only). Чтобы получить значение счётчика тактов процессора придётся повозиться, так как компилятор Turbo Pascal не знает о существовании машинной команды RDTSC. Мало того, компилятор не в состоянии компилировать простые машинные команды, использующие 32-разрядные регистры. Поэтому, в моём представлении необходимая функция может выглядеть так: ![]() ![]() function GetCPUClock : longint; assembler; asm db 0fh,31h {команда RDTSC, теперь значение счётчика в EDX:EAX} mov bx,00fh db 66h,0c1h,0e3h,10h {shl ebx,16} mov bx,4240h {в EBX загружено число 1000000} db 66h,0f7h,0f3h {div ebx ;делим счётчик на миллион} db 66h,8bh,0d8h {mov ebx,eax} db 66h,0c1h,0e8h,10h {shr eax,16} db 66h,33h,0d2h {xor edx,edx} mov dx,ax db 66h,8bh,0c3h {mov eax,ebx} end; она возвращает количество миллионов тактов процессора, произошедших со времени включения компьютера. Ну вот, теперь осталось составить функцию окончательного определения частоты процессора ![]() ![]() function GetCPUFrec : word; var Start, Finish, T : longint; begin T := ReadTimer + 1; repeat until T <= ReadTimer; { ждём момента обновления системного счётчика, чтобы свести погрешность к минимуму } Start := GetCPUClock; T := ReadTimer + 18; repeat until T <= ReadTimer; {ждём в течении одной секунды} Finish := GetCPUClock; GetCPUFrec := Finish - Start; end; которая возвращает количество миллионов тактов процессора, произошедших за одну секунду, что и является искомой частотой процессора в МГц. Эту функцию можно применять для машин, включённых менее 24 часов подряд и для процессоров ниже 35 ГГц. Ну а до этого ещё далеко, так что можно быть спокойным. Хочу добавить, что машинная команда RDTSC доступна начиная с процессоров Pentium (5x86), во всяком случае, в руководствах по процессорам i386, i486 такая команда не упоминается. Все эти функции для удобства можно собрать в единый модуль и назвать его, например, profiler, как это сделал я. ( Продолжение следует ) Литература 1. Р. Джордейн. Справочник программиста персональных компьютеров типа IBM PC, XT и AT. – М.: Финансы и статистика, 1992. – 543 с. 2. Диалоговая справочная система Norton Guide. |