其他分享
首页 > 其他分享> > c – 使键盘事件队列既响应又不占用整个CPU功率的方法

c – 使键盘事件队列既响应又不占用整个CPU功率的方法

作者:互联网

我正在做一个Sdl游戏,它是2D射手.我正在使用SDL导入曲面和OpenGL在屏幕上绘制它们(这样做是因为它比SDL更快地工作).我有两个线程正在运行,一个用于处理东西和渲染,另一个用于输入.基本上,处理一个占我CPU的1-2%,而输入循环需要25%(在四核上,所以它是1个全核).我在每次尝试之前都尝试过SDL_Delay(1)(SDL_PollEvent(& keyevent))并且它有效!整个过程将CPU负载降低至3%.然而,这是一个令人讨厌的副作用.整个程序输入是残障的:它没有检测到所有按下的键,例如,为了使角色移动,它有时需要长达3秒的抨击键盘才能做出反应.

我也尝试过使用SDL_PeepEvent()和SDL_WaitEvent()解决它,然而,它导致相同(非常长!)的延迟.

事件循环代码:

void GameWorld::Movement()
{
    SDL_Event keyevent;
    bool x1, x2, y1, y2, z1, z2, z3, m;         // Booleans to determine the
    x1 = x2 = y1 = y2 = z1 = z2 = z3 = m = 0;   // movement direction
    SDL_EnableKeyRepeat(0, 0);
    while (1)
    {
        while (SDL_PollEvent(&keyevent))
        {
            switch(keyevent.type)
            {
            case SDL_KEYDOWN:
                switch(keyevent.key.keysym.sym)
                {
                case SDLK_LEFT:
                    x1 = 1;
                    x2 = 0;
                    break;
                case SDLK_RIGHT:
                    x1 = 0;
                    x2 = 1;
                    break;
                case SDLK_UP:
                    y1 = 1;
                    y2 = 0;
                    break;
                case SDLK_DOWN:
                    y1 = 0;
                    y2 = 1;
                    break;
                default:
                    break;
                }
                break;
            case SDL_KEYUP:
                switch(keyevent.key.keysym.sym)
                {
                case SDLK_LEFT:
                    x1 = x2 = 0;
                    break;
                case SDLK_RIGHT:
                    x1 = x2 = 0;
                    break;
                case SDLK_UP:
                    y1 = y2 = 0;
                    break;
                case SDLK_DOWN:
                    y1 = y2 = 0;
                    break;
                default:
                    break;
                }
                break;
            case SDL_QUIT:
                PrintToFile("The game was closed manually.\n");
                CleanUp();
                return;
                break;
            default:
                break;
            }
        }
        m = x1 || x2 || y1 || y2;
        if (m)   // if any button is pushed down, calculate the movement
        {        // direction and assign it to the player
            z1 = (x1 || x2) && (y1 || y2);
            z2 = !x1 && (x2 || y2);
            z3 = (!y1 && x1) || (!y2 && x2);
            MainSurvivor->SetMovementDirection(4 * z1 + 2 * z2 + z3);
        }
        else    // if no button is pushed down, reset the direction
            MainSurvivor->SetMovementDirection(-1);
    }
}

计算/渲染循环代码:

void GameWorld::GenerateCycles()
{
    int Iterator = 0;
    time_t start;   
    SDL_Event event;

    Render();
    _beginthread(MovementThread, 0, this);
    while (1)
    {
            // I know I check this in input loop, but if I comment
        SDL_PollEvent(&event);   // out it from here, that loop cannot
        if (event.type == SDL_QUIT)  // see any of the events (???)!
        {
            PrintToFile("The game was closed manually.\n");
            CleanUp();
        }                            // It never closes through here though

        start = clock();
        Iterator++;
        if (Iterator >= 232792560)
            Iterator %= 232792560;
        MainSurvivor->MyTurn(Iterator);
        for (unsigned int i = 0; i < Survivors.size(); i++)
        {
            Survivors[i]->MyTurn(Iterator);
            if (Survivors[i]->GetDiedAt() != 0 && Survivors[i]->GetDiedAt() + 25 < clock())
            {
                delete Survivors[i];
                Survivors.erase(Survivors.begin() + 5);
            }
        }
        if (Survivors.size() == 0)
            SpawnSurvivors();

        for (int i = 0; i < int(Zombies.size()); i++)
        {
            Zombies[i]->MyTurn(Iterator);
            if (Zombies[i]->GetType() == 3 && Zombies[i]->GetDiedAt() + 25 < Iterator)
            {
                delete Zombies[i];
                Zombies.erase(Zombies.begin() + i);
                i--;
            }
        }
        if (Zombies.size() < 3)
            SpawnZombies();

            // No need to render every cycle, gameplay is slow
        if (Iterator % 2 == 0)
            Render();

        if (Interval - clock() + start > 0)
            SDL_Delay(Interval - clock() + int(start));
    }
}

有没有人有任何想法?

解决方法:

我对SDL或游戏编程并不熟悉,但这里有一些随意的想法:

应对状态变化

你的代码:

while (1)
{
    while (SDL_PollEvent(&keyevent))
    {
        switch(keyevent.type)
        {
            // code to set keyboard state
        }
    }

    // code to calculate movement according to keyboard state
    // then act on that movement
}

这意味着无论键盘上是否发生任何事实,您都在计算和设置数据.

如果设置数据很昂贵(提示:同步数据),那么它将花费更多.

SDL_WaitEvent:测量状态

您必须等待事件发生,而不是您写的旋转导致100%使用一个处理器.

这是我在家里为测试写的事件循环的变体:

while(true)
{
    // message processing loop
    ::SDL_Event event ;

    ::SDL_WaitEvent(&event) ; // THIS IS WHAT IS MISSING IN YOUR CODE

    do
    {
        switch (event.type)
        {
            // etc.
        }
    }
    while(::SDL_PollEvent(&event)) ;

    // re-draw the internal buffer
    if(this->m_isRedrawingRequired || this->m_isRedrawingForcedRequired)
    {
        // redrawing code
    }

    this->m_isRedrawingRequired = false ;
    this->m_isRedrawingForcedRequired = false ;
}

注意:这是单线程的.我稍后会谈论线程.

注2:关于两个“m_isRedrawing …”布尔值的观点是当其中一个布尔值为真时,以及当计时器询问问题时强制重绘.通常,没有重绘.

我的代码和你的代码之间的区别在于,你不会让线程“等待”.

键盘事件

我想,在处理键盘事件时会出现问题.

你的代码:

        case SDL_KEYDOWN:
            switch(keyevent.key.keysym.sym)
            {
            case SDLK_LEFT:
                x1 = 1;
                x2 = 0;
                break;
            case SDLK_RIGHT:
                x1 = 0;
                x2 = 1;
                break;
            // etc.
            }
        case SDL_KEYUP:
            switch(keyevent.key.keysym.sym)
            {
            case SDLK_LEFT:
                x1 = x2 = 0;
                break;
            case SDLK_RIGHT:
                x1 = x2 = 0;
                break;
            // etc.
            }

假设您按下LEFT,然后按RIGHT,然后按下LEFT.我期待的是:

>按左键:角色向左走
>按RIGHT:角色停止(按下LEFT和RIGHT)
> unpress LEFT:角色向右,因为RIGHT仍被按下

在您的情况下,您有:

>按左键:角色向左走
>按RIGHT:字符向右移动(因为现在忽略LEFT,x1 = 0)
> unpress LEFT:角色停止(因为你取消了x1和x2两个.),尽管RIGHT仍被按下

你做错了,因为:

>你对一个事件做出了不可思议的反应,而不是每隔n毫秒用一个计时器来对一个情况做出反应
>你在一起混合事件.

我稍后会找到该链接,但你应该做的是为按键设置一个布尔状态数组.就像是:

// C++ specialized vector<bool> is silly, but...
std::vector<bool> m_aKeyIsPressed ;

您可以使用可用键的大小初始化它:

m_aKeyIsPressed(SDLK_LAST, false)

然后,在关键事件上:

void MyContext::onKeyUp(const SDL_KeyboardEvent & p_oEvent)
{
    this->m_aKeyIsPressed[p_oEvent.keysym.sym] = false ;
}

关键时刻:

void MyContext::onKeyDown(const SDL_KeyboardEvent & p_oEvent)
{
    this->m_aKeyIsPressed[p_oEvent.keysym.sym] = true ;
}

这样,当您定期检查(以及检查部件的时间很重要)时,您就知道键盘的确切瞬时状态,并且您可以对其做出反应.

主题

线程很酷但是,你必须确切知道你正在处理什么.

例如,事件循环线程调用以下方法:

MainSurvivor->SetMovementDirection

分辨率(渲染)线程调用以下方法:

MainSurvivor->MyTurn(Iterator);

说真的,你在两个不同的线程之间共享数据吗?

如果你(我知道你是),那么你有:

>如果您没有同步访问,则由于处理器缓存而导致数据一致性问题.简单地说,不能保证一个线程中的一个数据集在合理的时间内被另一个线程视为“已更改”.
>如果您确实同步了访问(使用互斥锁,原子变量等),那么您的性能就会受到影响,因为您(例如)每个循环迭代每个线程至少锁定/解锁互斥锁一次.

我要做的是将更改从一个线程传递到另一个线程(例如,通过消息传递给同步队列).

无论如何,线程是一个很重要的问题,因此在将它与SDL和OpenGL混合之前,您应该熟悉这个概念. Herb Sutter’s blog是关于线程的精彩文章集合.

你应该做的是:

>尝试使用事件,发布的消息和计时器在一个线程中编写事物.
>如果发现性能问题,请将事件或绘图线程移到其他位置,但继续使用事件,发布的消息和计时器进行通信

P.S.:你的布尔什么怎么了?

您显然使用C(例如void GameWorld :: Movement()),因此使用1或0而不是true或false将不会使您的代码更清晰或更快.

标签:event-loop,c,windows,sdl
来源: https://codeday.me/bug/20190826/1730116.html