Вызов функции каждую 1 секунду (точно) - PullRequest
0 голосов
/ 02 мая 2018

Я работаю над простой программой симуляции игры на C ++, есть функция update (), которая обновляет текущее состояние игры, она должна вызываться точно каждую 1 секунду. Если я использую цикл, как это:

while(//some condition) {
     update();
     Sleep(1000);
}

Тогда функция будет вызываться не каждую 1 секунду, а каждые (1 + время выполнения update ()). Я читал о различных решениях, таких как асинхронные функции, многопоточность или вычисление времени выполнения функции с использованием std :: chrono и вычитание его из параметра 1000 мс в спящий режим. Некоторые из них были слишком сложными для моего простого случая, а другие казались небезопасными, если я их не очень хорошо понимаю.

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

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Вместо того, чтобы спать в течение продолжительного времени, вам нужно спать до момента времени. Например, если ваше первое обновление точно в 2: 00: 00.000, ваши будущие обновления должны быть как можно ближе к 2: 00: 01.000, 2: 00: 02.000 и т. Д.

Для этого вы можете посвятить поток обновлению, и после обновления перейдет в режим сна до в следующий раз, чтобы выполнить запланированное обновление. chrono::system_clock::time_point и this_thread::sleep_until ваши инструменты для этого.

Например:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

class UpdateManager
{
public:
    explicit UpdateManager() = default;

private:
    static std::atomic<int> now_;
    static std::atomic<bool> stop_;

    struct update_thread
        : private std::thread
    {
        ~update_thread();
        update_thread(update_thread&&) = default;

        using std::thread::thread;
    };

public:
    static update_thread start();
};

void update();

// source

std::atomic<int>  UpdateManager::now_{0};
std::atomic<bool> UpdateManager::stop_{false};

UpdateManager::update_thread::~update_thread()
{
    if (joinable())
    {
        stop_ = true;
        join();
    }
}

UpdateManager::update_thread
UpdateManager::start()
{
    return update_thread{[]
                         {
                             using namespace std;
                             using namespace std::chrono;
                             auto next = system_clock::now() + 1s;
                             while (!stop_)
                             {
                                 update();
                                 this_thread::sleep_until(next);
                                 next += 1s;
                             }
                         }};
}

#include "date/date.h"

void
update()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;
    cerr << system_clock::now() << '\n';
}

// demo

int
main()
{
    auto t = UpdateManager::start();
    using namespace std;
    this_thread::sleep_for(10s);
}

Просто для демонстрационных целей (не обязательно для логики) я использую бесплатную библиотеку дат / времени Говарда Хиннанта с открытым исходным кодом для печати текущего времени (UTC) с точностью до микросекунды в порядке чтобы проиллюстрировать стабильность этой техники. Пример вывода этой программы:

2018-05-02 15:14:25.634809
2018-05-02 15:14:26.637934
2018-05-02 15:14:27.636629
2018-05-02 15:14:28.637947
2018-05-02 15:14:29.638413
2018-05-02 15:14:30.639437
2018-05-02 15:14:31.637217
2018-05-02 15:14:32.637895
2018-05-02 15:14:33.637749
2018-05-02 15:14:34.639084
0 голосов
/ 02 мая 2018

Смотрите этот псевдокод. Если каждый процесс (который может занимать более 1 с) выполняется в отдельных потоках асинхронно, вы можете спать ровно за 1 с до следующего цикла, если это необходимо.

void MainGameLoop(bool& running)
{
    while (running)
    {
        AnimateSpritesAsync();
        AnimateBulletsAsync();
        CheckCollisionAsync();
        CheckWinConditionsAsync();

        WaitPreciselyOneSecond();
    }
}

Например, ваша update функция будет выглядеть так:

void update(); //your update 

void UpdateAsync()  // the async update
{
   std::thread([]() { update(); }).detach();
}

Теперь вы можете:

void MainGameLoop(bool& running)
    {
        while (running)
        {
            UpdateAsync();    
            Sleep(1000); // i won't recommend Sleep tho
        }
    }
0 голосов
/ 02 мая 2018

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

#include <ctime>

while(//some condition) {

     std::clock_t start = std::clock();
     update();
     std::clock_t end   = std::clock();
     Sleep(1000 - (end - start)/CLOCKS_PER_SEC * 1000);
}
...