C++ - выполнять функцию каждые X миллисекунд
Я не могу найти хороший ответ на это:
Я делаю игру, и я хочу, чтобы логический цикл был отделен от графического цикла. Другими словами, я хочу, чтобы игра проходила цикл каждые X миллисекунд независимо от того, сколько кадров / секунду она отображает.
Очевидно, что они оба будут совместно использовать много переменных, поэтому у меня не может быть потока / таймера, передающего одну переменную вперед-назад... Я просто ищу способ иметь таймер в фоновом режиме, который каждые X миллисекунд отправляет флаг для выполнения логического цикла, независимо от того, где находится графический цикл.
Я открыт для любых предложений. Кажется, что лучший вариант - это иметь 2 потока, но я не уверен, что лучший способ обмена данными между ними, без постоянной синхронизации больших объемов данных.
4 ответа
Вы можете очень хорошо выполнять многопоточность, обмениваясь своим "взглядом на мир" каждый тик. Вот как это работает:
- Ваше текущее мировоззрение указано одним умным указателем и доступно только для чтения, поэтому блокировка не требуется.
- Ваша логика создает ваше (первое) мировоззрение, публикует его и составляет расписание рендера.
- Ваш рендерер берет копию указателя на ваше мировоззрение и отображает его (помните, только для чтения)
- Между тем, ваша логика создает новое, немного другое мировоззрение.
- Когда это сделано, он обменивается указателем на текущее мировоззрение, публикуя его как текущее.
- Даже если рендерер все еще занят просмотром старого мира, блокировка не требуется.
- В конце концов рендер завершает рендеринг (старого) мира. Он захватывает новое мировоззрение и начинает еще один забег.
- А пока... (перейдите к шагу 4)
Единственная блокировка, в которой вы нуждаетесь, - это время, когда вы публикуете или захватываете указатель на мир. В качестве альтернативы вы можете сделать атомный обмен, но затем вы должны убедиться, что вы используете умные указатели, которые могут это сделать.
Большинство наборов инструментов имеют цикл обработки событий (построенный поверх некоторого системного вызова мультиплексирования, такого как poll(2) или устаревший select
-...), например, GTK имеет g_application_run (который выше:) gtk_main, который построен над главным циклом событий Glib (который фактически делает poll
или что-то подобное). Аналогично, Qt имеет QApplication и его методы exec.
Очень часто вы можете зарегистрировать таймеры в цикле событий. Для GTK используйте GTimer s, g_timeout_add и т. Д. Для Qt узнайте о его таймерах.
Очень часто вы также можете зарегистрировать некоторую простаивающую или фоновую обработку, которая является одной из ваших функций, которая запускается циклом событий после обработки других событий и тайм-аутов. Ожидается, что ваша бездействующая функция будет выполняться быстро (обычно она выполняет небольшой шаг вычислений за несколько миллисекунд, чтобы обеспечить отзывчивость графического интерфейса). Для GTK используйте g_idle_add и т. Д. IIRC, в Qt вы можете использовать таймер с задержкой 0.
Таким образом, вы можете кодировать даже (концептуально) однопоточное приложение, используя тайм-ауты и обработку в режиме ожидания.
Конечно, вы можете использовать многопоточность: обычно основной поток выполняет цикл обработки событий, а другие потоки могут делать другие вещи. У вас есть проблемы с синхронизацией. В системах POSIX хорошим трюком синхронизации может быть использование самого себя pipe(7): вы устанавливаете канал перед запуском цикла обработки событий, и ваши вычислительные потоки могут записать на него несколько байтов, в то время как основной цикл обработки событий имеет вид " прослушивание "на нем" (с помощью GTK, с использованием g_source_add_poll или async IO или GUnixInputStream и т. д., с Qt, с использованием QSocketNotifier и т. д....). Затем в обработчике ввода, выполняемом в главном цикле для этого канала, вы можете получить доступ к традиционным глобальным данным с мьютексами и т. Д.
Концептуально читайте про продолжения. Это актуальное понятие.
Вы можете использовать метод Draw и Update для всех ваших игровых компонентов. Таким образом, вы можете установить его так, чтобы во время игры игра вызывала обновление и ничья игнорировалась, или любая комбинация из них. Он также имеет преимущество в том, что логика и графика полностью отделены друг от друга
Не могли бы вы просто создать метод рисования для каждого объекта, который нужно нарисовать, и сделать его глобальным. Затем просто запустите ваш поток рендеринга с задержкой сна. Пока ваш поток рендеринга не записывает какую-либо информацию в глобальные переменные, все будет в порядке. Посмотрите sfml, чтобы увидеть пример этого в действии.
Если вы работаете в системе Unix, вы можете использовать usleep()
однако, это не доступно на окнах, так что вы можете искать здесь альтернативы.