Лучший способ остановить основную игру l oop из отдельного потока - PullRequest
0 голосов
/ 05 апреля 2020

Я работаю над игрой на C ++ с использованием SDL. Я начал следовать учебникам Lazyfoo и собирал их в MacOS с использованием XCode. В эти выходные я смотрел, смогу ли я скомпилировать его на Windows в Visual Studio 2019. Я добился успеха, но не уверен насчет одного из изменений, которые мне пришлось сделать.

В игре используется ImageMagick (Magick ++) для рисования, которое затем сохраняется в виде текстуры в памяти видеокарты с использованием SDL (собственных функций рисования SDL недостаточно). Эта часть рисования очень медленная , но не должна происходить синхронно, поэтому я решил поместить этот процесс в свой собственный поток, который работал абсолютно нормально на моей Ma c.

Теперь у меня это работает на Windows, я видел случайные сбои, когда игра пыталась нарисовать нового персонажа. Я сузил это до SDL_CreateTextureFromSurface(), который вызывается из потока и создает новую текстуру, используя поверхность, созданную из образа Magick ++. Это происходило только тогда, когда я использовал многопоточность, если я отключил это (и убивал мою частоту кадров, когда что-то рисовал), это работало нормально.

Я смотрел на мьютекс, но все рекомендации рекомендовали использовать join в потоке, который заблокировал бы main() на весь период розыгрыша и сделал бы это бессмысленным. Я думаю, что есть очень хороший шанс, что я неправильно понял это!

Вместо этого я установил два bool в классе, который передается как ссылка на поток. Если для потока SDL_Thread_Waiting установлено значение true, игра l oop в main() временно останавливается и устанавливает SDL_Main_Paused в true, что говорит потоку, что он может безопасно обращаться к средству визуализации. Затем поток устанавливает SDL_Main_Paused на false после завершения, и игра l oop продолжается.

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

Пример кода ниже. На данный момент игра довольно масштабная, но это должно показать, что я делаю:

// class.hpp
Class Game {
    Public:
    bool is_running;
    bool SDL_Thread_Waiting = false;
    bool SDL_Main_Paused = false;
    // ...
}
// main.cpp
Game game;

int main(int argc, char* argv[]) {    
    game.is_running = true;

    while (game.is_running) {
        // Regular game stuff!

        if (game.SDL_Thread_Waiting) {
            game.SDL_Main_Paused = true;
            while (game.SDL_Main_Paused) {
                // do.. nothing?
            }
        }
    }
// Threaded bits
void generate_character() {
    std::thread th(make_texture, std::ref(game));
    th.detach();
}

SDL_Texture* make_texture(Game& game) {
    // Heavy lifting by ImageMagick goes here

    SDL_Thread_Waiting = true;
    while (!SDL_Main_Paused) {
        std::this_thread::sleep_for(std::chrono::milliseconds(25));
    }

    // SDL_CreateTextureFromSurface()

    SDL_Main_Paused = false;
    SDL_Thread_Waiting = false;
    return texture;
}

Является ли это подходящим способом на короткое время заблокировать основную игру l oop от отдельная тема? Я неправильно поняла mutex и это было бы лучше здесь?

Я очень новичок в C ++ и SDL, прошу прощения, если весь мой вопрос основан на дальнейших недоразумениях!

1 Ответ

0 голосов
/ 06 апреля 2020

То, что вы описали, это поведение мьютекса. Вы реализовали плохую спин-блокировку - которая может завершиться с ошибкой из-за плохой синхронизации c для переменной блокировки (один поток может установить переменную, но другой еще не видит это изменение), и тратит впустую циклы ЦП при блокировке, поскольку он продолжает вращаться в al oop. Используйте SDL_mutex или std::mutex, они, похоже, именно то, что вы хотели.

Что касается доступа к рендереру из нескольких потоков - ну, не очень хорошо. Документация SDL прямо заявляет, что вам действительно не следует (если вы не абсолютно уверены, какая реализация средства визуализации SDL используется и не знаете, как она работает). Попробуйте изменить свой подход - например, выполнить рендеринг в буфер / поверхность памяти в отдельном потоке, а затем сообщить о готовности, чтобы поток рендеринга мог взять буфер и преобразовать его в текстуру. Вы можете даже сделать двойную буферизацию здесь, чтобы минимизировать задержку.

Есть вещи, которые можно улучшить, но требующие дополнительной работы: создание потока не является бесплатным, почему бы не создать его один раз и подождать в mutex / semaphore / condvar для сигнал для рендеринга следующего кадра? SDL_CreateTextureFromSurface предполагает стати c текстуры, почему бы не создать потоковую текстуру один раз и обновить ее с помощью SDL_LockTexture && memcpy?

...