Two instances of labels updated by time()
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.
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 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.
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.
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 last edited by
im a little greenjoin the club! :D
Tizzy last edited by Tizzy
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 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.
JonB last edited by JonB
This is a good start.
while Truewith 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_screento 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 :)
Tizzy last edited by Tizzy
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?
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.