Using "run" To Delay Python Code - 2022-12-11 15:37

Using “run” To Delay Python Code

Link to main site

A little trick that I like to do when I use the run command. Instead of composing the comand using concatination or f-strings, i just pass a function as one of the arguments
run( "args[0]()", lambda : print("foobar") )
using closures I can easily pass refferences around without having to create badly readable scripts in string format.
For example in an extension

def update_par(self, value):
    self.ownerComp.par.Foo.val = value

def trigger_delayed_update(self):
    run( "args[0]()", lambda: self.update_par("uno") )
    run( "args[0]()", lambda: self.update_par("dos"), delayFrames = 200 )
    run( "args[0]()", lambda: self.update_par("tres"), delayFrames = 400 )
7 Likes

great trick!

Oh wow, that’s awesome.
I feel like this should be the default behaviour, to be honest

1 Like

@alphamoonbase what’s the point of using lamda here - simply readability? i just pass all the args into the run string, regardless of type. seems the most simplistic/readable to me.

    def update_par(self, value):
        self.ownerComp.par.Foo.val = value

	def trigger_delayed_update(self):
		run('args[0](args[1])', self.update_par, 'uno')
		run('args[0](args[1])', self.update_par, 'dos', delayFrames=200)
		run('args[0](args[1])', self.update_par, 'tres', delayFrames=400)

@Ivan one thing that has always bothered me is that delayMilliseconds does not run agnostically of the frame rate - which logically one would presume when specifying something in milliseconds vs frames. obviously, this causes issues when using run delays in non-realtime systems w/uncapped framerates. my workaround is to pipe the current rate into the delay value…

run('args[0](args[1])', self.foo, 'bar', delayMilliseconds=2*me.time.rate)

however, it seems more logical and less prone to confusion to respect the true milliseconds - regardless of the frame rate. or, add another argument to run to specify how the delay timing is evaluated - ie. realtime=BOOL

@choy That’s very strange about delayMilliSeconds. Can you create a simple case that shows it? I wasn’t able to. Here is the test I ran
timeTest.1.toe (3.9 KB)

@Ivan sure. here’s a non-realtime .toe w/vsync disabled demonstrating the issue. RunUpdate toggles a red background color with a delay of 5 seconds. you’ll note in perform mode it’s blasting through nearly instantly at uncapped FPS rates, whilst out of perform it acts as intended with the 5 second delay…

RunTime.toe (4.1 KB)

Hi milliseconds is converted to frames on the timeline.
This is similar to our parameters that allow for specifying frames, seconds or samples in menus.

We’ll make note of allowing for realtime / elapsed system time delays as well.

Cheers

Ps. We’re actively investigating how to streamline lambda function arguments as described in the earlier posts.

I think the main problem is that all bets are off for absolute time when you turn realtime off.

@choy can you give an example where you are running non-realtime, but you want a real-timed delay to occur?

@malcolm in a non-realtime queue-based render system using callbacks/requests to external systems wherein it is either not possible or particularly cumbersome to ascertain a particular external state needed to trigger a subsequent action, etc. in this case, realtime delays/run loop checks with simple exception handling have been useful.

also, in a few instances - quickly setting a simple delay of N before beginning a file xfer of a completed render as to ensure the file has been completely written to disk and not still in use by the OS, without having to write a file checker to verify the status before doing so.

in most cases - more robust callback/verification code is eventually added to replace the functionality of a simple delay - however, during development/debugging i’ve found it to be quite useful.

@alphamoonbase

You’ll be allowed to provide a string or a callable as the first argument in the next experimental.

2 Likes

that is a great addition @ivan thanks

This is great stuff. One comment on this line of example code in the docs and the article under Timing of Evaluation of Arguments Vs Script,

run('print("hello, Venus. Frame:", absTime.frame)', absTime.frame, delayFrames=180)

Passing absTime.frame as the second argument of run() seems a little redundant as it isn’t referenced in the run statement, right? I was a little confused by this.

Another example code question, in Single Update Design Pattern Example

def update():
	# the actual update function
	print('Run updater!')
	scheduledUpdateRun = None

should this be:

def update():
	# the actual update function
    print('Run updater!')
    global scheduledUpdateRun
	scheduledUpdateRun = None

especially if scheduleUpdate() is called from a different script?

Good corrections on both counts. Wiki updated!

1 Like

Note that in the new experimental you can use callables as the first argument to run. There are examples in the experimental wiki page here: Experimental:Run Command Examples - Derivative

2 Likes