[Delphi] Программа Life

loks-po

Существует одна из версий "жизни", в которой писавший товарисч (Савченко Павел, МИФИ, 2004) немного изменил начальные условия классической формулировки (приведу на всякий случай)
В программе 3 разновидности юнитов: растения, травоядные и хищники.
// Хищники поедают только травоядных, травоядные питаются только
// растениями.
// В каждый момент объект может находится в одном из 5 состояний:
// 0 - не определено
// 1 - поиск пищи/охотится
// 2 - поиск партнера для размножения
// 3 - смерть
// 4 - убегает от хищника (только травоядные)
// Для растений доступны состояния 0,3.
// Для травоядных - 0,1,2,3,4
// Для хищников - 0,1,2,3
// Съеденное травоядным растение погибает, а количество жизней
// травоядного увеличивается на количество жизней растения.
// Аналогичное правило действует и для хищника, съевшего травоядного.
// С течением времени здоровье растений постепенно увеличивается
// (растут и рождаются новые растения. Здоровье травоядных
// и хищников, наоборот, уменьшается, поэтому они постоянно
// питаются/охотятся. Если здоровье превысит некоторое пороговое
// значение, животное ищет аналогичного партнера для размножения.
// Если такового не имеется, то оно ждет.
// Скорость передвижения хощников и травоядных напрямую зависит от
// количества единиц здоровья.
// После встречи двух особей рождается только одна новая, и
// здоровье родительских особей распределяется между всеми тремя
// (пропорционально исходному количеству жизней родителей, без
// потерь и добавок).
// Выбор необходимого растения/травоядного/хищника осуществляется
// поиском минимального расстояния до искомого (это самое медленное
// место в программе ( .
//
Прога работает отрисовывая всю эту живность кружочками разных цветов на форме.
События начинаются по OnIdle формы.
procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
var Done: Boolean);
begin
This:=GetTickCount;
if (This-Last)> 20 then
begin

(итерация жизни - несколько вложенных процедур, устанавливающих параметры живности)

Last:=GetTickCount;

end;
done:=false;
end;

В чем состоит вопрос: в коде я оставил только волнующий меня момент. Итерация жизни выполняется только если прошло 20 миллисекунд между двумя заходами в OnIdle.
Как можно оценить эту величину? ( Мне нужно написать нечто похожее) Как я понимаю она зависит от того, сколько кода должно выполняться внутри... но хотелось бы знать вообще что называется "порядок цифр".

gopnik1994

я не понял, какую величину ты хочешь оценить?

viktor954

Не совсем понял вопрос... Если нужно оценить время исполнения какой-то функции, то это вроде понятно как сделать...
Если нужно оценить величину задержи между итерациями, то это ИМХО всегда делается "на глаз"...

Dasar

Ты хочешь оценить эту величину заранее?
или во время хода программы?
Заранее оценить очень сложно - т.к. зоопарк техники довольно сильно отличается.
Если по ходу работы:
то замеряешь сколько времени у тебя работает функция Idle,
а также через сколько времени у тебя получает управление функция Idle.
На основе отношения этих двух чисел - выбираешь время паузы.

loks-po

Начнем с того, что это первая программа жизни, которую я увидел вместе с исходниками.
Мне сразу стало интересно как же она работает - ну не в бесконечный же цикл все засовывать? Я думал, что этим занимается отдельный поток с пониженным приоритетом.
Но здесь как я понял идет следующее:
Выполняются необходимые изменения во всех экземплярах объектов пищи, животных и растений (настолько быстро, насколько может процессор)
Если прошло менее двадцати миллисекунд, ничего не предпринимается
Т.е. получается, что все-все-все укладывается в этот промежуток. Это значение жестко зафиксировано в программе. Т.е. если компьютер будет слабее (т.е. выполнит код дольше). то получится ошибка?
Я почитал справку про OnIdle, но не понял когда точно оно наступает и есть ли альтернатива этому ходу при программировании жизни...

gopnik1994

есть альтернатива под названием TTimer.
OnIdle срабатывает "после каждого пука" в цикле обработке сообщений приложения (см. Applicatio.Run)
Есть мнение, что пользоваться OnIdle - признак плохого программирования.

Dasar

> Т.е. получается, что все-все-все укладывается в этот промежуток.
Не совсем.
Сценарий такой:
Пересчитывается изменения
делается пауза в 20 мс
Опять пересчитываются изменения
и т.д.
В данном случае - есть следующие потенциальные проблемы:
1. в секунду будет не больше 50 шагов, хотя может быть процессор может выполнить 1000 шагов за секунду.
2. Программа будет тормозить, если остальные действия программы не укладываются в паузу из 20 мс.
3. Будет эффект подвисания, если время пересчитывания изменений очень велико (например, несколько секунд).
> то получится ошибка?
будет эффект подвисания, т.е. действия пользователя программа будет выполнять с большой задержкой
> Я почитал справку про OnIdle, но не понял когда точно оно наступает
Он наступает, когда с точки зрения программы все остальные команды/действия выполнены.
> есть ли альтернатива этому ходу при программировании жизни...
Альтернатива - отдельный тред, но отдельный тред сложнее в программировании.

Dasar

> OnIdle срабатывает "после каждого пука"
OnIdle вызывается, если в очереди сообщений нет других сообщений.

loks-po

It is not called continuously unless the Done parameter is set to false
А вот это можно пояснить? Т.е. она НЕ вызывается повторно кроме случаев когда присваивается false.А в коде в конце процедуры - там true, как же она тогда дальше работает?

loks-po

Да, и еще интересный момент: в коде присутствует GetTickTime. Тип ее результата - longint.
Возникает вопрос: что будет возвращать эта функция через (MaxLongInt/1000/60/60/24) т.е. примерно через двадцать пять дней? Ясно, что система может проработать и двадцать пять дней и пятдесят...

Dasar

longint переполнится и будет возвращаться время опять начиная с нуля

rosali

А что, в delphi longint - 32 бита?
PS. А вообще какая нафиг разница, пусть переполняется. Если с ним только это
 
This-Last > 20
делать, то единственное, что должно беспокоить - чтобы между двумя OnIdle двадцать пять дней не прошло

gopnik1994

по хорошему надо бы контролировать, что оно > 0
потому что событие происходит примерно раз в 49 дней (при условии непрерывной работы ОС но все же может произойти, а прога рискует подвиснуть...

gopnik1994

там unsigned int32, который в Дельфе называетс Cardinal
И использовать надо его.

gopnik1994

2:
Читайте исходники VCL - многие вопросы отпадут. Лучшего хелпа просто не существует.
2, :
В Applicatio.Run бесконечный цикл, обрабатывающий приходящие сообщения.
1. При приходе сообщения оно обрабатывается,
2. после обработки срабатывает OnIdle и будет вызываться пока не вернет Done = True (попутно обрабатывая приходящие сообщения).
3. Как только OnIdle вернет Done = True, программа прекращает вызывать OnIdle пока снова не придет какое-нть сообщение.
4. Далее см. п.1.

Dasar

Запости, пожалуйста, код функции Run, где OnIdle вызывается.

bleyman

Что характерно, если изменить код вот так:

This:=GetTickCount;
if (This-Last)> 20 then
begin
Last:=GetTickCount; // Типа местами поменял

(итерация жизни - несколько вложенных процедур, устанавливающих параметры живности)

end;
То получится более правильный сценарий - то есть каждая итерация занимает не менее 20 мс, если времени не хватает, то ничего страшного не происходит, просто прога работает медленнее. Но тут уж ничем не поможешь, а всякие треды с таймерами только ухудшат ситуацию.
По поводу "раз в 49 дней" - правильно сказал - всё и так зашибись, ничего менять не нужно, разве что действительно проверить, что тип у переменных одинаковый.
По поводу OnIdle - исходников VCL у меня под рукой нету, равно как и желания в них смотреть. Но! Все нормальные люди конечно же понимают, что OnIdle вызывается когда сообщения _кончились_. То есть они все обрабатываются, обрабатываются, а вот когда PeekMessage вернёт false - вызовется OnIdle. Насчёт параметра - не знаю, но выглядит логично.
Хотя аффтары дельфей - известные долбы, они могли и извратить смысл OnIdle вызывая её после каждого сообщения. Хотя это уж совсем полными долбами надо быть.

gopnik1994

OnIdle по смыслу вызывается не постоянно если очередь сообщений пустая, а только тогда один раз, когда приложение очищает очередь. Для чего они туда прикрутили Done - не разбирался. Но логику работы я описал. Исходники запощу с работы..

gopnik1994

 procedure TApplication.Run;
begin
...
repeat
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;
...
end;

procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;

procedure TApplication.Idle(const Msg: TMsg);
begin
...
try
if Assigned(FOnIdle) then FOnIdle(Self, Done);
if Done then DoActionIdle;
except
HandleException(Self);
end;
if (GetCurrentThreadID = MainThreadID) and CheckSynchronize then
Done := False;
if Done then WaitMessage;
end;

CheckSynchronize в однопоточном приложении всегда False

Dasar

ProcessMessage и WaitMessage как выглядит?

gopnik1994

 
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
...
end;
end;

function WaitMessage; external user32 name 'WaitMessage';
The WaitMessage function yields control to other threads when a thread has no other messages in its message queue. The WaitMessage function suspends the thread and does not return until a new message is placed in the thread's message queue.
BOOL WaitMessage(VOID)
Parameters
This function has no parameters.
Return Values
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
BOOL PeekMessage(
LPMSG lpMsg, // pointer to structure for message
HWND hWnd, // handle to window
UINT wMsgFilterMin, // first message
UINT wMsgFilterMax, // last message
UINT wRemoveMsg // removal flags
);

...
Return Values
If a message is available, the return value is nonzero.
If no messages are available, the return value is zero.
Remarks
Unlike the GetMessage function, the PeekMessage function does not wait for a message to be placed in the queue before returning.
...

Dasar

Спасибо за цитату из исходников - это помогло понять, что Idle в Дельфи и Idle в других платформах отличаются.
ps
Да, согласен, с _Fj в Дельфи Idle реализован очень и очень странно.
Оставить комментарий
Имя или ник:
Комментарий: