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
    8894
    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.
    • Vertec
      Vertec last edited by

      I'm trying to develop a simple chess clock - first I'm just having trouble running two instances of time - then I need to figure out how to rotate a label and have them count backwards still a total beginner

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

        #using ui module
        import ui
        from time import time,sleep,strftime,localtime,gmtime

        #t = time()

        def reset_action(sender):
        v = sender.superview
        '@type sender: ui.Button'
        global t
        t = time()
        start_time = 0.0
        stop_time = 0.0

        @ui.in_background
        def update_time():
        while True:
        sleep(1)
        v['countdown_timer'].text = strftime('%M:%S', gmtime(time()-t))
        start_time = 0.0
        stop_time = 0.0

        #ts = time()

        def reset_action2(sender):
        v = sender.superview
        '@type sender: ui.Button'
        global ts
        ts = time()
        start_time = 0.0
        stop_time = 0.0

        @ui.in_background
        def update_time2():
        while True:
        #sleep(1)
        v['countdown_timer2'].text = strftime('%M%S', gmtime(time()-ts))
        start_time = 0.0
        stop_time = 0.0

        v = ui.load_view('Chessclock')
        reset_action(v['reset_button'])
        reset_action2(v['reset_button2'])
        update_time()
        update_time2()

        v.present(orientations=['portrait'])

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

          @Vertec, not sure the below is helpful for you or not. I just did one timer without formatting etc. but the main point is that it has the entry points. Look I didn't spend a lot of time on it. But I would do something like this. But with the different entry points you should be able to accumulate the time etc. I am not sure how ui.delay handles running concurrently, but I thought for a chess timer you wouldn't run them at the same time. So as you started a timer , you would stop the other timer before starting the other one.

          Anyway, hope it's useful.

          import ui
          
          import datetime as dt
          
          class TimerExample(ui.View):
          	def __init__(self, *args, **kwargs):
          		ui.View.__init__(self, *args, **kwargs)
          		self.timer1_label = None
          		self.timer1_start_time= 0
          		self.timer1_btn = None
          		self.timer1_running = False
          		self.make_view()
          		
          	def make_view(self):
          		lb = ui.Label() 
          		lb.font = ('Arial Rounded MT Bold', 44)
          		lb.alignment = ui.ALIGN_CENTER
          		lb.text = '00:00:00'
          		lb.size_to_fit()
          		lb.x = 10
          		lb.y = 10
          		
          		self.add_subview(lb)
          		self.timer1_label = lb
          		
          		btn= ui.Button()
          		btn.font = lb.font
          		btn.title = 'Start'
          		btn.border_width = 2
          		btn.corner_radius = 6
          		btn.size_to_fit()
          		btn.x = lb.x + lb.width + 20
          		self.add_subview(btn)
          		btn.action = self.toggle_timer1
          		self.timer1_btn = btn
          		
          	
          	def update_timer1(self):
          		if not self.timer1_running:
          			return
          			
          		td = dt.datetime.now() - self.timer1_start_time
          		self.timer1_label.text = str(td.seconds)
          		ui.delay(self.update_timer1, 1)
          		
          			
          	def toggle_timer1(self, sender):
          		if not self.timer1_running:
          			self.timer1_running = True
          			self.timer1_btn.title = 'Stop'
          			self.timer1_start_time = dt.datetime.now()
          			ui.delay(self.update_timer1, 0)
          		else:
          			self.timer1_running = False
          			ui.cancel_delays()
          			self.timer1_btn.title = 'Start'
          			
          	def will_close(self):
          		# window is going to close save anything you need to here
          		# also stop ui.delay
          		ui.cancel_delays()
                  self.timer1_running = False
          		
          		
          		
          		
          if __name__ == '__main__':
          	f = (0, 0, 600, 800)
          	v = TimerExample(frame = f, bg_color = 'white')
          	v.present('sheet')	
          

          Edited ; will_close, self.timer1_running = False

          1 Reply Last reply Reply Quote 0
          • 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