omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Two instances of labels updated by time()

    Pythonista
    6
    15
    8857
    Loading More Posts
    • 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.
    • Webmaster4o
      Webmaster4o last edited by

      @Vertec - first of all please format your code as code with three back ticks before and after ( ` ).

      Second, don't use sleep with UI. It's better to use ui.delay. That will make your problem much easier.

      1 Reply Last reply Reply Quote 1
      • JonB
        JonB last edited by

        The problem is that ui.in_background uses a shared queue. While this executes in parallel with the main thread, everything that uses in_background executes in serial! This leads to confusing problems

        import threading
        import time,ui
        
        @ui.in_background
        def ping():
        	for i in range(5):
        		print('I am thread number 1')
        		time.sleep(1)
        @ui.in_background
        def pong():
        	for i in range(5):
        		print('I am thread number 2')
        		time.sleep(1)
        ping()
        pong()
        

        One might expect these two workers to be interspersed... but they are not. ping() must finish before pong() can run at all. trying to treat these as threads with Locks/Semaphores etc will lead to deadlocks. Also ui_inbackground is a common reason wait_modal fails.

        My favorite "drop in" replacement wrapper that works the way you expect looks like

        def run_async(func):
           from threading import Thread
           from functools import wraps
        
           @wraps(func)
           def async_func(*args, **kwargs):
              func_hl = Thread(target = func, args = args, kwargs = kwargs)
              func_hl.start()
              return func_hl
        
           return async_func
        

        Other options are to use a ui.delay or threading.Timer. ui.delay may be a nice option because it is slightly easier to cancel all delays, so you may be able to get away without an abort method (the other threading approaches need to check for an abort signal to break out of the thread) . the forums have a few examples of timers use delay.

        1 Reply Last reply Reply Quote 0
        • ccc
          ccc last edited by

          Guys... The use case is a chess timer... Only one of the two timers is running at a time so race conditions should not be a problem.

          Phuket2 1 Reply Last reply Reply Quote 0
          • Phuket2
            Phuket2 @ccc last edited by

            @ccc , yes I did mention that the "timers" would not be running together for chess. That's why I didn't test ui.delays, as I this case it wouldn't matter. A start on timer should stop the other timer.

            1 Reply Last reply Reply Quote 0
            • JonB
              JonB last edited by

              My point was you cannot run two ui.in_backgrounds simultaneously, regardless of if you are only updating one. For chess, I would think there would be just one timer loop, which keeps track of the delta time since the last call, and then subtract from the appropriate variable, and update both.

              1 Reply Last reply Reply Quote 1
              • Vertec
                Vertec last edited by

                Unfortunately for me I'm a little green for yours answers. Been coding for a total of 8 weeks and only in 1hr byte sessions.

                At this point I'm only using the custom gui module and adding references to drag and dropped elements.

                As a beginner I find this manageable and am slowly learning the code as I wrote and rewrite little apps borrowing sections of code from here and the examples.

                I can easily make a digital clock. Or customise the analogue clock. What I can't do is make two and count them backwards.

                Ideally I'd like to reference ui.labels in the module for clock displays simply because it's at "my level" of understanding.

                Thanks again all

                Tizzy 2 Replies Last reply Reply Quote 0
                • Tizzy
                  Tizzy @Vertec last edited by

                  @Vertec im a little green join the club! :D

                  1 Reply Last reply Reply Quote 0
                  • Tizzy
                    Tizzy @Vertec last edited by Tizzy

                    @Vertec @jonb @Webmaster4o @Phuket2 this is my go at it, what do you think?

                    UPDATE: streamlined the code a little bit by making there be discreet "active" and "inactive" players, with a currentActivePlayer.otherPlayer attribute pointing to the other player to get rid of some unnecessary conditionals.

                    
                    import ui,time
                    import bz2
                    from base64 import b64decode
                    
                    #versions oldest - newest
                    #https://gist.github.com/c61951cc318e7beaf378f6c292f94883
                    #https://gist.github.com/b8e6f213d19e9d4668a7f28bbd1efe9f
                    #https://gist.github.com/712b62011ad0b76409f8faf77a3add8c
                    
                    class clockster(object):
                    	
                    	def __init__(self):
                    		
                    		#_______PACKAGED UI ______
                    		compressedui = '''\
                    QlpoOTFBWSZTWeGcOhAAAz3fgFUQUGd/9T+E3Yq/r976QAL82kAisQyQ0mJhNRlT01DQPU
                    e1T0mE0AAbUCJRqVP0U09pTygAPUaAPUAMh6TeqYHMAmmATIYAAmCYAAAIpTU9IT9JMm1A
                    9Q0AAAAABpSsGxSEROVLA/s9kiUJei8kAkksSaVEhIYQZzK1AQrUBuUGYwQQwLiohYHlmE
                    GlLV+A/vRmcjTMwkHNmBgqD+wxhADgXkoDGvhvLCICx2jSuNSNdcDqff28dZk5ENgjuTXu
                    TcU2KIxqiwl7wswixLUMsli7ULESKam7yePbJHSF0sl0hAOtEQMBGjSg19jAAvtVAsSRIF
                    6DhPl0Q93YzqpQL2wYnXvZj1HPablEQp+AhI0Gyo0QWOAf18kQMJIXEkTvCBCW2N3zUdkw
                    aPDyJVBr3ODSrA1Duxob4YBHsjW+gxvdpGDJxIis9Ews2YGwVKBrp7UcXMEmBSICZjuq4x
                    tYyWEE2CW5sZWDYCQyZVrJrsgeiwdV7lNmTAnGdIQjGVc0ybkHZNuHEggOP8K3OmNYIKwW
                    W2j6Jhjm9Zd5wnlQDOlM52yze50sUc11IPut5YrOlQ1YXR0c68D9ZA6npDWn4HlyV5U+QH
                    hx4kd7+76wDeO/hxFEfgNAYqZKUMAnBZzjG0U5nYNyksuOO0feXiaYSI9SX/XTErAqDkM1
                    jgBdy7Dm3eoDuLYdelsw8I6BTI6B553ByI5xPM4g5EW00BRhI8B/gXioeDpvLBA3YNnY2U
                    J4GhrxPdTb0vE3HB0YgWc3SOo7bWVBiAtccjUoNGgN9SqY2RapMHCN8DImkpc/hkYhChkH
                    QZNo4W6JkGU9TGEiBnWFg5akpaPSGBMtzA4AM7iSh8pCe5DBBiw0KX9aeyudSg3nyI8vNY
                    nAn3kTrF3JFOFCQ4Zw6EA=
                    '''
                    		pyui = bz2.decompress(b64decode(compressedui))
                    		v = ui.load_view_str(pyui.decode('utf-8'))
                    		self.v = v
                    		#_______END PACKAGED UI________
                    		
                    		
                    		#self.v = ui.load_view()
                    		self.v.present('sheet')
                    		
                    		print(self.v["playerOne"])
                    		
                    		self.startTime = time.time()
                    		self.gameBegun = False
                    		
                    		
                    		self.playerOne = self.v["playerOne"]
                    		self.playerTwo = self.v["playerTwo"]
                    		self.playerOne.timeLabel = self.v["playerOneTime"]
                    		self.playerTwo.timeLabel = self.v["playerTwoTime"]
                    		
                    		self.playerOne.totalTimeElapsed = 0
                    		self.playerTwo.totalTimeElapsed = 0 
                    		self.playerOne.latestTurnElapsed = 0
                    		self.playerTwo.latestTurnElapsed = 0
                    		
                    		self.playerOne.otherPlayer = self.playerTwo
                    		self.playerTwo.otherPlayer = self.playerOne
                    		
                    		
                    		self.currentActivePlayer = "NOBODY"
                    		
                    		
                    		self.mainLoop()
                    	
                    	def togglePlayer(self,sender):
                    		print(sender)
                    		print(self.currentActivePlayer)
                    		if self.currentActivePlayer == sender:
                    			print("no change....")
                    			pass
                    			
                    		elif self.currentActivePlayer == "NOBODY":
                    			print("this means THE GAME HAS BEGUN!")
                    			self.gameBegun = True
                    			self.startTime = time.time()
                    			self.currentActivePlayer = sender
                    			self.currentInactivePlayer = self.currentActivePlayer.otherPlayer
                    			
                    			self.lastTimeToggled = time.time()
                    			print('...first move by: ',sender.name)
                    			
                    		else:
                    			print('currentActivePlayer changing....')
                    			#cleaning up old active plyayer
                    			self.currentActivePlayer.totalTimeElapsed = self.currentActivePlayer.totalTimeElapsed+self.currentActivePlayer.latestTurnElapsed
                    			
                    			#new active/inactive player
                    			self.currentActivePlayer = sender
                    			self.currentInactivePlayer = sender.otherPlayer
                    			
                    			self.lastTimeToggled = time.time()
                    			print("player toggled to",sender.name)
                    			
                    			
                    	def switchClockOnForSelectedPlayer(self):
                    		self.currentActivePlayer.latestTurnElapsed= time.time()-self.lastTimeToggled
                    		self.currentActivePlayer.timeToDisplay= self.currentActivePlayer.totalTimeElapsed + self.currentActivePlayer.latestTurnElapsed
                    		self.currentActivePlayer.timeLabel.text = str(round(self.currentActivePlayer.timeToDisplay))
                    		
                    		self.totalElapsedTimeBySumming = self.currentActivePlayer.timeToDisplay + self.currentInactivePlayer.totalTimeElapsed
                    		
                    		
                    	@ui.in_background						
                    	def mainLoop(self):
                    		
                    		while self.v.on_screen:
                    			
                    			if self.gameBegun is True:
                    				self.switchClockOnForSelectedPlayer()
                    				
                    				timeElapsed = round(time.time()-self.startTime,0)
                    				print(round(time.time()-self.startTime))
                    				#self.v["totalTime"].text = str(timeElapsed)
                    				#CHANGED THE ABOVE TO BELOW SO TIMES ADD UP!
                    				self.v["totalTime"].text = str(round(self.totalElapsedTimeBySumming))
                    				
                    			time.sleep(1)
                    		
                    		
                    		
                    if __name__ == "__main__":
                    	a = clockster()
                    	
                    
                    
                    
                    Phuket2 1 Reply Last reply Reply Quote 1
                    • Phuket2
                      Phuket2 @Tizzy last edited by

                      @Tizzy , I didn't actually run your code. And look maybe I am wrong, the more experienced guys can comment if I am. But with ui Programming, I would have thought it would not be so good to be in a tight loop like you did. My approach was something more akin to a state/stateless machine. But in my mind it will allow the rest of the ui to respond a lot more normally to other events. Eg, the timer is going. Well, it's not really a timer. It's just the difference between 2 points in time and updating a label to show the time elapsed. But, it might be valid to hit a menu item or other ui controls also. In my mind the tight while loop is a over kill for this.
                      Again, I am still learning.

                      1 Reply Last reply Reply Quote 0
                      • JonB
                        JonB last edited by JonB

                        This is a good start.

                        while True with a sleep is a fine pattern in python ( check cpu usage time.clock(), it is the same for a ui.delay based approach and a time.sleep approach). time.sleep gives up control to the os. There needs to be a way to break out of the loop though.

                        It probably should be while self.v.on_screen to stop the main loop when the view is closed.

                        Also, a thread would be safer than in_background, since no other in_background items can run until this one exits-- eventually if this is integrated into a chess game you might need ui.in_background for other reasons, so the mainloop should not be background based. Again, this is just sort of a misnomer of ui.in_background, all in_background calls share a single queue.

                        There is some imprecision in this approach when tapping very quickly -- the individual timers do not add up to the total. I think the main issue was that tapping the button did not update the labels, and the label update routine only updated one set at a time, so you can get into a condition where the labels get out of sync if tapping back and forth.

                        For this type of thing it is important to minimize the number of places time() is called for precision. Also, the action should update the labels, since it might occur on a half second boundary.

                        I made some minor updates:

                        • check self.on_screen to kill mainloop
                        • used a thread rather than in_background for main loop
                        • Minimize calls to time() to ensure everything is consistent. Basicslly once in the action, once in the label update routine. Also update labels whenever a button is pressed so all info is up to date
                          - I formatted the labels to look more like a chess clock.
                          - changed update interval to 0.01 sec, because... it is more exciting
                          - clocks count backwards instead of forward. Also, usually a chess clock pressing your button starts your opponents timer, so i updated that
                          - added sound effects :)

                        https://gist.github.com/6b7dcc13b39440217abfab7a51105d68

                        1 Reply Last reply Reply Quote 0
                        • Tizzy
                          Tizzy last edited by Tizzy

                          @Phuket2 @JonB thanks for the feedback! Lots to think about..

                          To start somewhere -

                          • In order to make sure the total elapsed time always adds up I made it be a summation of each of the player elapsed times. This is probably cheating?
                          • Also I got rid of some conditionals by making discrete active and inactive player objects.
                          • Also added self.v.on_screen as the condition for the while loop.

                          And I meant to ask, too - is it considered good practice to use UI objects as the main objects of your data structure like I did adding attributes onto them willy nilly?

                          1 Reply Last reply Reply Quote 0
                          • JonB
                            JonB last edited by

                            Dunno whether it is good practice or not, but it is darn convienent. i say go for it! It means your action code does not even need to know which player it is

                            If you look at the gist i posted, the times always add up perfectly, because i only call time() once, and make sure to update in the tap action with any fractional times. Also when displaying, be sure to display both sets of labels ( and total time) all using the same time() call.

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post
                            Powered by NodeBB Forums | Contributors