More info about Timer component?

Could someone explain to me what exactly is updated when Timer component is added to a ghpython component?
if the component is in GH_Component SDK mode:

Which part of the code is updated with the Timer?

  • the class MyComponent(component)?
  • def RunScript(self,x,y)?
  • or the whole script?

Reason for the question:
Is it possible that I define a global variable that is no affected by this update (from the Timer)?

After a few tests, I think the answer is:

  • Running the whole script again. Bummer!

So how can I feed a variable unaffected by the timer?

Maybe with some boolean flag?

The problem is I want to create a random number on every update and if that number is smaller or greater than a certain value I want to change the domain of the randomizer. In a way mimic Galapagos functionality.

Since this updates the whole script I always get the same values for min and max of the domain (the initial values) because the script is always run from the beginning from the Timer.

I tried to use two components one generating random values, and other one defining the min and max values, but that leads to a cycle error.

Edit:
@piac, @DavidRutten is this even possible without compiling or maybe using Timer is the wrong way in this case?

Sounds like you’re looking for persistent variables, have a look at these posts (and their links to the old GH forum):

The last example also demonstrates how to write a timer inside the GHPython component acting on itself. Might be useful.

2 Likes

Hi @ivelin.peychev

no, the whole script is NOT executed again. Your class is instantiated only once at the beginning, and then RunScript() is run once for every time it is needed (may be thousand of times per solution).

If you want to set some logic at component creation or deletion, you can override Python’s initialization/deletion methods:

def __init__(self):
def __enter__(self): #alternative
def __exit__(self):

Also, you may override:

def __del__(self):

but that one relies on the Garbage Collector, and may happen in a very remote time from apparent deletion of the component (use with extreme caution: throwing exceptions in a Garbage Collector call will immediately close the program, and bugs are hard to debug).

Do not rely on what happens in the external scope when using the SKD-mode (object-oriented mode). It’s an implementation detail.

See an example here.

init-exit-sample.gh (6.5 KB)

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com


EDIT: in 6.7 and onward, the external scope will only evaluate once to make the behavior more apparent. See RH-47124.

3 Likes

Oh wow, thank you Giulio, this does the job almost perfectly.

I just need to figure out how to feed floats as limits for System.Random.DoubleNext()

Currently I do System.Random.Next(System.Random.(), min,max)/1000
where
initial min = 0
and
initial max = 100000

but when I try to change min and max according to the random number it ends up with integer.

iteration_TST.gh (4.3 KB)
(Updated)

UPDATE:
Never mind, I soved it :slight_smile:

RH-47124 is fixed in the latest Service Release Candidate.

I did not quite understand what that fix will do. Timer will not affect the class just runscript()?

RH-47124 is an optimization that I added. The outer scope will no longer be evaluated before invoking the RunScript() def, when in SDK mode. Only at the reset of the script it will be re-evaluated.

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

So, yes if in SDK mode?

If I don’t understand this:

How can I understand this:

It’s pretty much using the same words.
I don’t know what is the “Outer Scope”

Will that add an additional mechanism to re-run the script like a right-click re-run? Or do we need to open the editor again and click Test?

Yes this optimization is only possible in SDK mode. In short, that mode will be a little faster.

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

1 Like

Hi Anders,

Sorry for the delay, it was faster to use what Giulio suggested so I started with that.

Thanks for sharing this solution, it is really useful.
I have one question though, how can I slow the update? Not that I want to make it ineffective I just want some more time to debug :smiley:

UPDATE:
Never mind, I got it. time.sleep(1) inside the update definition.

That’ll simply pause the Python interpreter, you probably want to set a higher interval on the ghDoc.ScheduleSolution() call instead. Also, I find that recording (using the standard GH recorder) the GHPython output parameter is a good way of debugging dynamic/iterative/time-dependent components.

2 Likes

Btw I found out that using time.sleep in fact makes grasshopper to stutter.
If my definition is running and self-updating and has time.sleep in the update when zooming in grasshopper it does so every second at a time.

Another strange thing is, if Timer component is apparent and active on the canvas, the interval of ghDoc.ScheduleSolution() is ignored.

@DavidRutten, is this a bug or as designed?

Yes, that’s what pausing the Python interpreter will do.

The scheduling works as designed. Everyone is allowed to schedule a solution, but only the earliest schedule is remembered. So if you tell GH you want a solution one second from now, and then some other component requests a solution 10 milliseconds from now, there will be another solution 10ms from now and that’s it. You either have to do the work you were planning to do right there and then (earlier than expected, but maybe that doesn’t matter?), or you must schedule another solution 990ms from now. Keep doing that until a solution starts that is close enough to your desired time.

how does GH then figure out which is first and which second assuming both are at the same place in the algorithm, does location on the canvas matter?

Perhaps I didn’t understand quite well but if I have a component scheduled for 1sec and another for 10 ms. Which one will “fire” first?

10ms then 1sec or 1 sec then 10 ms?

10ms will fire first. And then nothing will fire unless another solution is scheduled during or after that 10ms solution.

Here, in a nutshell, is the scheduling logic:

private TimeSpan _schedule;
private List<delegate> _callbacks = new List<delegate>();

public void ScheduleSolution(TimeSpan delay, delegate callback)
{
  _callbacks.Add(callback);
  if (delay < _schedule)
    _schedule = delay;

  if (!SolutionRunning)
    BeginScheduleTimer();
}

public void RunSolution()
{
  SolutionRunning = true;
  _schedule = TimeSpan.Zero;
  foreach (delegate callback in _callbacks)
     callback.Invoke();  
  _callbacks.Clear();

  // Iterate over all expired objects and solve them.
  // Some of them may schedule solutions while running.

  SolutionRunning = false;
  if (_schedule != TimeSpan.Zero)
    BeginScheduleTimer(); 
}
1 Like