Here is the code, the question is centered around the mainLoop() function at the bottom. I'm wondering if there is a way for this function to be called in a non-blocking manner on an interval based on the 'fps' variable.
I tried something that should technically work, I imported 'threading' and in the mainLoop() function, I set a threading.Timer to call the mainLoop() function again after 1/fps seconds. The only problem with this is that the window closes right after opening, and I know that if I called tkinter's root.mainloop() function, although the window would stay open, it would block and halt the rest of the code.
(I also tried a while True loop and have time.sleep(1/fps) at the end, but this requires everything to be finished before calling the code again (it is blocking))
Is there a better way to do this, maybe using asyncio or something similar? Doing this in JavaScript would just be a matter of 'setInterval(mainLoop, 1000/fps)'.
#! /usr/bin/python
from tkinter import *
from threading import Timer
import math
# setup window + canvas
root = Tk()
root.title("Pendulum Simulation")
root.resizable(0,0)
canvas = Canvas(root, width = 600, height = 600, bg="grey", bd=0)
canvas.pack()
root.update()
# world variables
fps = 60
gravity = -1250
# Pendulum variables
length = 240
radius = 15
pinX = canvas.winfo_width()/2
pinY = canvas.winfo_height()/2
a = 179.9 # angle to vertical
a *= math.pi/180 # conversion to radians, needed for math
aV = 0 # angular velocity
# two wrapper functions to draw shapes
def drawLine(x1,y1,x2,y2):
id = canvas.create_line(x1,canvas.winfo_height()-y1,x2,canvas.winfo_height()-y2, width = 2)
def drawCircle(x,y,r,c):
id = canvas.create_oval(x - r,canvas.winfo_height()-y -r,x+r,canvas.winfo_height()-y+r, fill = c, width = 2)
def drawObjects():
x = pinX + length*math.sin(a)
y = pinY - length*math.cos(a)
drawLine(pinX,pinY, pinX + length*math.sin(a), pinY - length*math.cos(a))
drawCircle(pinX,pinY,2,"black")
drawCircle(x,y,radius, "crimson")
def moveObjects():
global aV
global a
aA = gravity/length * math.sin(a)
aV += aA/fps
a += aV/fps
def mainLoop():
canvas.delete(ALL)
drawObjects()
root.update()
moveObjects()
Timer(round(1/fps,2), mainLoop).start()
mainLoop()
Tkinter has its own main loop. You can actually hook your own code into that loop, with the after method of your root window. First, which I believe you haven't done, start the mainloop. In your looping function, you can use the after method, after it is called.
def mainLoop():
root.after(1000, mainLoop) #Calls mainLoop every 1 second.
(...)
mainLoop()
root.mainloop()
This is non-blocking and shall execute every second.