Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C / C++ / MFC
  4. eliminating lag from a chess clock

eliminating lag from a chess clock

Scheduled Pinned Locked Moved C / C++ / MFC
game-devannouncement
8 Posts 3 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    Alexander Kindel
    wrote on last edited by
    #1

    I'm cleaning up a chess program of mine, which includes a clock feature. For those unfamiliar with chess clocks, both players start the game with a certain amount of time, and each of their clocks counts down when and only when it's currently their turn. The program has GUI, and the displays for the timers have to be prompted to redraw every time a second flips. I had previously implemented this by having a timer thread that stores the time when a turn starts, then constantly re-checks the time in a loop, using this to update the active player's time on every iteration. This worked like a charm, but I understand that spinning like that is bad practice for wasting cycles, so I tried redoing it using wait_until(), roughly like this:

    HWND g_mainWindowHandle;
    std::condition_variable g_timerConditional;
    std::mutex g_timerLock;
    std::atomicg_turnIsOver;
    std::atomicg_blackPlayerNanosecondsLeft;
    std::atomicg_whitePlayerNanosecondsLeft;

    void turnTimer(std::atomic*activePlayerNanosecondsLeft,
    Rect*displayRect)
    {
    std::chrono::nanoseconds currentNanosecondsLeft{ activePlayerNanosecondsLeft->load() };
    std::chrono::nanoseconds nanosecondsToNextSecond{ currentNanosecondsLeft % 1000000000 };
    std::chrono::steady_clock::time_point nextSecondBoundary{
    std::chrono::steady_clock::now() + nanosecondsToNextSecond };
    std::unique_locklock(g_timerLock);
    std::cv_status status{
    g_timerConditional.std::condition_variable::wait_until(lock, nextSecondBoundary) };
    while (!g_turnIsOver.load())
    {
    if (status == std::cv_status::timeout)
    {
    currentNanosecondsLeft -= nanosecondsToNextSecond;
    *activePlayerNanosecondsLeft = currentNanosecondsLeft;
    nanosecondsToNextSecond = std::chrono::nanoseconds(1000000000);
    nextSecondBoundary += nanosecondsToNextSecond;
    InvalidateRect(g_mainWindowHandle, displayRect, FALSE);
    }
    status = g_timerConditional.std::condition_variable::wait_until(lock, nextSecondBoundary);
    }
    currentNanosecondsLeft -=
    std::chrono::steady_clock::now() - (nextSecondBoundary - nanosecondsToNextSecond);
    *activePlayerNanosecondsLeft = currentNanosecondsLeft;
    g_turnIsOver = false;
    InvalidateRect(g_mainWindowHandle, displayRect, FALSE);
    }

    Each time the main thread moves a piece, it sets g_

    D 1 Reply Last reply
    0
    • A Alexander Kindel

      I'm cleaning up a chess program of mine, which includes a clock feature. For those unfamiliar with chess clocks, both players start the game with a certain amount of time, and each of their clocks counts down when and only when it's currently their turn. The program has GUI, and the displays for the timers have to be prompted to redraw every time a second flips. I had previously implemented this by having a timer thread that stores the time when a turn starts, then constantly re-checks the time in a loop, using this to update the active player's time on every iteration. This worked like a charm, but I understand that spinning like that is bad practice for wasting cycles, so I tried redoing it using wait_until(), roughly like this:

      HWND g_mainWindowHandle;
      std::condition_variable g_timerConditional;
      std::mutex g_timerLock;
      std::atomicg_turnIsOver;
      std::atomicg_blackPlayerNanosecondsLeft;
      std::atomicg_whitePlayerNanosecondsLeft;

      void turnTimer(std::atomic*activePlayerNanosecondsLeft,
      Rect*displayRect)
      {
      std::chrono::nanoseconds currentNanosecondsLeft{ activePlayerNanosecondsLeft->load() };
      std::chrono::nanoseconds nanosecondsToNextSecond{ currentNanosecondsLeft % 1000000000 };
      std::chrono::steady_clock::time_point nextSecondBoundary{
      std::chrono::steady_clock::now() + nanosecondsToNextSecond };
      std::unique_locklock(g_timerLock);
      std::cv_status status{
      g_timerConditional.std::condition_variable::wait_until(lock, nextSecondBoundary) };
      while (!g_turnIsOver.load())
      {
      if (status == std::cv_status::timeout)
      {
      currentNanosecondsLeft -= nanosecondsToNextSecond;
      *activePlayerNanosecondsLeft = currentNanosecondsLeft;
      nanosecondsToNextSecond = std::chrono::nanoseconds(1000000000);
      nextSecondBoundary += nanosecondsToNextSecond;
      InvalidateRect(g_mainWindowHandle, displayRect, FALSE);
      }
      status = g_timerConditional.std::condition_variable::wait_until(lock, nextSecondBoundary);
      }
      currentNanosecondsLeft -=
      std::chrono::steady_clock::now() - (nextSecondBoundary - nanosecondsToNextSecond);
      *activePlayerNanosecondsLeft = currentNanosecondsLeft;
      g_turnIsOver = false;
      InvalidateRect(g_mainWindowHandle, displayRect, FALSE);
      }

      Each time the main thread moves a piece, it sets g_

      D Offline
      D Offline
      Daniel Pfeffer
      wrote on last edited by
      #2

      I would use a different approach: 1. Have an event queue (practically - a single element) that contains pairs - 2. When a player makes a move, insert a pair into the queue 3. Have a timer thread that updates the clock display every milliseconds 4. Before updating the clock, it: 4.1 Reads the current time using the same clock used by the player threads 4.2 Checks the event queue to see if a move was made, and when it was made 4.3 If a move was made, it updates the players' times, stops one clock, and starts the other There will always be an unavoidable lag in the visual display of the clock because of the O/S's scheduling requirements, but the internal counts will be accurate.

      Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

      A 2 Replies Last reply
      0
      • D Daniel Pfeffer

        I would use a different approach: 1. Have an event queue (practically - a single element) that contains pairs - 2. When a player makes a move, insert a pair into the queue 3. Have a timer thread that updates the clock display every milliseconds 4. Before updating the clock, it: 4.1 Reads the current time using the same clock used by the player threads 4.2 Checks the event queue to see if a move was made, and when it was made 4.3 If a move was made, it updates the players' times, stops one clock, and starts the other There will always be an unavoidable lag in the visual display of the clock because of the O/S's scheduling requirements, but the internal counts will be accurate.

        Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

        A Offline
        A Offline
        Alexander Kindel
        wrote on last edited by
        #3

        Having a timer activate every millisecond involves using something like sleep_for, right? Is that less susceptible to delays than wait_until is? Either way, it does certainly having the advantage that any timer-related delays won't block the main thread.

        L 1 Reply Last reply
        0
        • A Alexander Kindel

          Having a timer activate every millisecond involves using something like sleep_for, right? Is that less susceptible to delays than wait_until is? Either way, it does certainly having the advantage that any timer-related delays won't block the main thread.

          L Offline
          L Offline
          Lost User
          wrote on last edited by
          #4

          Hi, I doubt your condition_variable::wait_until code is causing the redraw delay based on what you've shown. Invalidating the rect simply creates an update region and then marks an internal 'dirty bit' associated with the window. The operating system is somewhat lazy updating the window... it could be redrawn a few milliseconds later... or several seconds later depending on how many messages are in the message queue. If you want an immediate redraw add a call to the [UpdateWindow function](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-updatewindow) immediately following your invalidation. Best Wishes, -David Delaune

          A 1 Reply Last reply
          0
          • L Lost User

            Hi, I doubt your condition_variable::wait_until code is causing the redraw delay based on what you've shown. Invalidating the rect simply creates an update region and then marks an internal 'dirty bit' associated with the window. The operating system is somewhat lazy updating the window... it could be redrawn a few milliseconds later... or several seconds later depending on how many messages are in the message queue. If you want an immediate redraw add a call to the [UpdateWindow function](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-updatewindow) immediately following your invalidation. Best Wishes, -David Delaune

            A Offline
            A Offline
            Alexander Kindel
            wrote on last edited by
            #5

            While I must admit that I don't know how to gracefully debug the timer, I'm pretty sure the join call is producing at least some of the noticeable delays. I tried just adding an UpdateWindow call after the InvalidateRect call in turnTimer as a simple test, but that deadlocked the program, which led me to read about how it's flatly incorrect to do a lot of kinds of window manipulations from another thread. I'm not sure if InvalidateRect is one of them and I just kept getting lucky with the original implementation, which also called it from the timer thread, but I suppose the fact that I'm not sure is enough reason to switch to something like Daniel described even if I weren't getting the lags. I'll try it and see what happens.

            L 1 Reply Last reply
            0
            • A Alexander Kindel

              While I must admit that I don't know how to gracefully debug the timer, I'm pretty sure the join call is producing at least some of the noticeable delays. I tried just adding an UpdateWindow call after the InvalidateRect call in turnTimer as a simple test, but that deadlocked the program, which led me to read about how it's flatly incorrect to do a lot of kinds of window manipulations from another thread. I'm not sure if InvalidateRect is one of them and I just kept getting lucky with the original implementation, which also called it from the timer thread, but I suppose the fact that I'm not sure is enough reason to switch to something like Daniel described even if I weren't getting the lags. I'll try it and see what happens.

              L Offline
              L Offline
              Lost User
              wrote on last edited by
              #6

              Hi, Yep, Daniel gave you some great advice. You don't really need a second thread. My assessment is the same... InvalidateRect does not redraw the window, it simply marks it as dirty. Your main source of visual delay is most likely waiting for a WM_PAINT message. If you use the event driven model Daniel suggested maybe you could add a redraw event handler that invalidates and redraws the window. Best Wishes, -David Delaune

              1 Reply Last reply
              0
              • D Daniel Pfeffer

                I would use a different approach: 1. Have an event queue (practically - a single element) that contains pairs - 2. When a player makes a move, insert a pair into the queue 3. Have a timer thread that updates the clock display every milliseconds 4. Before updating the clock, it: 4.1 Reads the current time using the same clock used by the player threads 4.2 Checks the event queue to see if a move was made, and when it was made 4.3 If a move was made, it updates the players' times, stops one clock, and starts the other There will always be an unavoidable lag in the visual display of the clock because of the O/S's scheduling requirements, but the internal counts will be accurate.

                Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

                A Offline
                A Offline
                Alexander Kindel
                wrote on last edited by
                #7

                I've implemented this approach, and it's working for me. It proved to be easier to factor for multi-platform support than what I had before would have been, too.

                D 1 Reply Last reply
                0
                • A Alexander Kindel

                  I've implemented this approach, and it's working for me. It proved to be easier to factor for multi-platform support than what I had before would have been, too.

                  D Offline
                  D Offline
                  Daniel Pfeffer
                  wrote on last edited by
                  #8

                  I'm happy to have helped.

                  Freedom is the freedom to say that two plus two make four. If that is granted, all else follows. -- 6079 Smith W.

                  1 Reply Last reply
                  0
                  Reply
                  • Reply as topic
                  Log in to reply
                  • Oldest to Newest
                  • Newest to Oldest
                  • Most Votes


                  • Login

                  • Don't have an account? Register

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • World
                  • Users
                  • Groups