How to prevent operations from interfering with one another?

With the assistance I got here, and assurance here, I’ve created a two player game, with the game’s state saved in a data table (state of the game board, card deck, two hands, and whose turn it is; one row, 5 cells).

A player’s turn happens when the screen is touched/clicked, triggering the mouse down event. This event is where changes to the board, hand, etc. are processed and sent to the data table on the server.

To keep everything synced between the players, a timer starts a method that pulls in the current state data and updates players’ UI’s.

I think every once in awhile the update function starts before the canvas_1_mouse_down function has finished executing. This causes issues sometimes re: the data; hands (string lists) end up being updated twice. So, I want to be sure the two methods are never operating at the same time.

I tried setting self.timer_1.interval=0 at the beginning of canvas_1_mouse_down, to turn off the timer, and then setting the timer’s interval back at the end of the method. I still had issues.

Then I tried adding a flag, self.is_mouse_down, setting it to True at the beginning of canvas_1_mouse_down, and False at the end of the method. Meanwhile, the update method begins with
if not self.is_mouse_down:
This combination helped. I had fewer issues, but not none.

Finally, I tried adding a flag, self.is_updating, setting it to True at the beginning of update, and False at the end of the method. Meanwhile, the canvas_1_mouse_down method begins with if not self.is_updating

This combination seems to have eliminated the problem. However, unless I set the timer interval to ~5 seconds I run into issues sometimes where a player’s touch/click isn’t initially recognized, i.e., you have to click a few times to make a move. I assume the update function’s blocking the mouse down event.

Do you think my reasoning is sound? Is there something better I should do to address the issue?

For completeness, here’s some of the code (I can explain methods, if necessary):

  def update(self):
    if not self.is_mouse_down:
      self.is_updating = True
      with anvil.server.no_loading_indicator: 
        game_state = anvil.server.call('update')
        if game_state is None:
          return
        if self.player_color=="green":
          if game_state['GreenHand']!=self.hand:
            self.hand = anvil.server.call('get_hand',"green")
        if self.player_color=="blue":
          if game_state['BlueHand']!=self.hand:
            self.hand = anvil.server.call('get_hand',"blue")
        if game_state['Board'] != self.model:
          self.model = game_state['Board'] 
        if game_state['IsGreenTurn']!=self.is_green_turn:
          self.is_green_turn = game_state['IsGreenTurn']
      
      self.display_turn_message()
      self.update_hand_display(self.hand)
      self.canvas_1_reset()
      self.is_updating = False

canvas_1_mouse_down is lengthy. It processes the effects of tapping/clicking the canvas (game board) in various spots, changing local variables, updating the display, and calling methods on the server to update values in the data table. It ends this way:

      anvil.server.call_s('save_board',self.model)
      self.canvas_1_reset()
      # On the server, add new card to hand, update hand (and deck) in data table
      anvil.server.call_s('update_hand',self.player_color,self.hand)
      # Get updated hand to display
      self.hand = anvil.server.call_s('get_hand',self.player_color)
      self.update_hand_display(self.hand)
      self.change_player() # updates data table re: whose turn it is
      self.timer_1.interval=constants.TIMER_INTERVAL
      self.is_mouse_down=False

Is the two-player game synchronous? So when a player is taking their turn the other player isn’t going to be making changes to the state? If that’s the case, you can keep track of whether it’s the player’s turn, and disallow either the update or the mouse down, since only one of them makes sense depending on whose turn it is.

2 Likes

Nice idea, Jay. It’s working well. Thank you—yet again :grin:

1 Like

I’ve addressed this issue by adding a timestamp column.

When the client retrieves the state, it also receives the corresponding timestamp.

When the client sends an update to the server, the server compares the timestamp in the request with the one currently stored in the database. If they match, the update proceeds. If not, the server returns the latest state so the client can respond with something like, “sorry, too late, try again.”

Don’t forget to wrap the server function in a transaction!

I use this approach in an app that manages panel production and shipment, used concurrently by multiple operators across different departments. The app displays the shipment status, stored in a simple object column. For example, when the assembly department adds a part, the crating department sees the update through a polling timer. If both departments try to add different parts at the same time, the first update goes through, while the second receives a message like “the shipment has been modified by Joe, reloading it now.”

It’s not as advanced as real-time collaboration in Google Docs, but it works seamlessly for 4–5 users working on the same shipment.

3 Likes